From 5ffc20450e85b0a274bc69b9392907ed05793528 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Mon, 19 Oct 2026 20:55:04 -0700 Subject: [PATCH 1/8] Compile SDK-declared data types, any-valued members and Pick views of classes Message types from SDK declarations often use shapes scriptc refused: - `{ [key: string]: any }` and `Dictionary` (also a bare `Dictionary`): the overflow values are checked-dynamic, like `Record`'s. - `any[]` members an interface, type literal or class declares, also optional or nullable: the checked-dynamic array, as `unknown[]` maps. An object literal's inferred `any[]` member keeps its existing story. - A class a types-only package declares with public fields only (aws-sdk's `declare class PresignedPost { url: string; fields: Fields }`, with a merged namespace of types): it describes JSON data and is never constructed, so it maps structurally like an interface. - `Pick` and `Omit` over a program class, when the view exposes a method: like an interface the class matches, it keeps a live receiver, so calls through the view run on the instance. A record blocked by a member deep inside it now names that member's path (`member 'poll().jobs[].handle' has type ...`) instead of the outermost member only; a member holding `any` is named itself. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/class-protocols.ts | 25 ++- packages/compiler/src/frontend/type-mapper.ts | 189 ++++++++++++++++-- .../6412-any-valued-dictionaries.ts.json | 6 + .../6413-sdk-declared-data-class/main.ts.json | 6 + .../6414-pick-class-method-views.ts.json | 6 + .../nested-member-blocker-path.ts.json | 6 + tests/corpus/6412-any-valued-dictionaries.ts | 50 +++++ .../6413-sdk-declared-data-class/main.ts | 27 +++ .../node_modules/upload-sdk/index.d.ts | 13 ++ .../node_modules/upload-sdk/package.json | 1 + tests/corpus/6414-pick-class-method-views.ts | 39 ++++ .../diagnostics/nested-member-blocker-path.ts | 19 ++ .../nested-member-blocker-path.ts.txt | 17 ++ 13 files changed, 386 insertions(+), 18 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6412-any-valued-dictionaries.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6413-sdk-declared-data-class/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6414-pick-class-method-views.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/nested-member-blocker-path.ts.json create mode 100644 tests/corpus/6412-any-valued-dictionaries.ts create mode 100644 tests/corpus/6413-sdk-declared-data-class/main.ts create mode 100644 tests/corpus/6413-sdk-declared-data-class/node_modules/upload-sdk/index.d.ts create mode 100644 tests/corpus/6413-sdk-declared-data-class/node_modules/upload-sdk/package.json create mode 100644 tests/corpus/6414-pick-class-method-views.ts create mode 100644 tests/diagnostics/nested-member-blocker-path.ts create mode 100644 tests/harness/__snapshots__/nested-member-blocker-path.ts.txt diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts index 5a27f0815..a3f2ffb4d 100644 --- a/packages/compiler/src/frontend/lowering/class-protocols.ts +++ b/packages/compiler/src/frontend/lowering/class-protocols.ts @@ -570,6 +570,19 @@ export class ClassProtocols { } } + /** `Pick` or `Omit` over a program class instance type: a + * view of the class's own members, spelled inline instead of as an + * interface. Like an interface the class matches, a view that exposes a + * method needs a live receiver (see protocol). */ + private pickOfProgramClass(type: ts.Type): boolean { + const alias = type.getAliasSymbol(); + if (alias === undefined || (alias.name !== "Pick" && alias.name !== "Omit")) return false; + if (!alias.declarations.every((declaration) => declaration.getSourceFile().isDeclarationFile)) + return false; + const source = type.getAliasTypeArguments()[0]; + return source !== undefined && this.isProgramClassInstance(source); + } + private isProgramClassInstance(type: ts.Type): boolean { const symbol = type.getSymbol(); return ( @@ -701,11 +714,13 @@ export class ClassProtocols { const properties = this.checker.getPropertiesOfType(type); if (symbol && protocols.has(symbol)) return true; if ( - !symbol || - !symbol.declarations.some( - (declaration) => - ts.isInterfaceDeclaration(declaration) && !declaration.getSourceFile().isDeclarationFile, - ) + !this.pickOfProgramClass(type) && + (!symbol || + !symbol.declarations.some( + (declaration) => + ts.isInterfaceDeclaration(declaration) && + !declaration.getSourceFile().isDeclarationFile, + )) ) return false; for (const property of properties) { diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 437aba2b8..58434ef6b 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1834,7 +1834,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d) || ts.isTypeLiteralNode(d) || - ts.isMappedTypeNode(d)), + ts.isMappedTypeNode(d) || + isDeclaredDataClass(d) || + (decls.some(isDeclaredDataClass) && isTypeOnlyNamespace(d))), ); const aliasDecls = packageOnly(widened.getAliasSymbol()); const ownDecls = packageOnly(widened.getSymbol()); @@ -5502,6 +5504,48 @@ function isMappedShape(t: ts.Type): boolean { * 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). */ +/** A class an SDK declares only to describe data (aws-sdk's + * `declare class PresignedPost { url: string; fields: Fields }`, which + * arrives as JSON and is never constructed): public instance fields and + * nothing else — no constructor, methods, accessors, index signatures, + * static, private or protected members, base classes, implemented + * interfaces or type parameters. Its instances are exactly the records + * of those fields, so the type maps structurally like an interface. */ +function isDeclaredDataClass(d: ts.Node): boolean { + if (!ts.isClassDeclaration(d) || !d.getSourceFile().isDeclarationFile) return false; + if (d.heritageClauses !== undefined && d.heritageClauses.nodes.length > 0) return false; + if (d.typeParameters !== undefined && d.typeParameters.nodes.length > 0) return false; + return d.members.nodes.every( + (member) => + ts.isPropertyDeclaration(member) && + !ts.isPrivateIdentifier(member.name) && + !(member.modifiers?.nodes ?? []).some( + (modifier) => + modifier.kind === ts.SyntaxKind.StaticKeyword || + modifier.kind === ts.SyntaxKind.PrivateKeyword || + modifier.kind === ts.SyntaxKind.ProtectedKeyword || + modifier.kind === ts.SyntaxKind.AbstractKeyword, + ), + ); +} + +/** A namespace merged into a declared data class that holds only types + * (`namespace PresignedPost { interface Fields { … } }`): it adds no value + * the program could reach. */ +function isTypeOnlyNamespace(d: ts.Node): boolean { + if (!ts.isModuleDeclaration(d)) return false; + const body = d.body; + if (body === undefined) return true; + if (ts.isModuleDeclaration(body)) return isTypeOnlyNamespace(body); + if (!ts.isModuleBlock(body)) return false; + return body.statements.nodes.every( + (statement) => + ts.isInterfaceDeclaration(statement) || + ts.isTypeAliasDeclaration(statement) || + isTypeOnlyNamespace(statement), + ); +} + function packageTypeFile(sf: ts.SourceFile, ctx: Pick): boolean { return ( sf.isDeclarationFile && /[\\/]node_modules[\\/]/.test(sf.fileName) && !ctx.isStdlibFile(sf) @@ -5968,10 +6012,11 @@ function mapRecordTypeInner( let v: IrType | null = null; for (const info of indexInfos) { if (!stringKey(info.keyType) && !(info.keyType.flags & ts.TypeFlags.Number)) return null; - const iv = mapType(info.valueType, ctx); - - // JSONValue>` (a package's own JSON alias) and `Record` - + // An `any`-valued signature (`{ [key: string]: any }`, an SDK's + // `Dictionary`) holds whatever the program stores: its + // overflow values are checked-dynamic, exactly `Record`'s. + const iv = info.valueType.flags & ts.TypeFlags.Any ? DYN : mapType(info.valueType, ctx); if (!iv || !isSupportedIndexValue(iv)) return null; if (v !== null && !typeEquals(v, iv)) return null; v = iv; @@ -6197,6 +6242,22 @@ function mapRecordTypeInner( pt = arrayOf(unitOnlyUnion(ctx.unions)); } if (pt === null && (fieldTs.flags & ts.TypeFlags.Any) !== 0) pt = DYN; + // An `any[]` field (`list: any[]` in an SDK's message type, also + // optional or nullable) is the checked-dynamic array, exactly as + // `unknown[]` maps; the checked-dynamic value holds the units too. + // Declared members only: an object literal's inferred `any[]` member + // keeps its existing story. + if ( + pt === null && + declaredMember(p) && + (fieldTs.isUnionType() ? ts.constituentTypes(fieldTs) : [fieldTs]).every( + (part) => + isAnyArrayType(part, checker) || + (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null)) !== 0, + ) && + !isUnitOnlyTsType(fieldTs, ctx.resolveTypeParam) + ) + pt = DYN; // Unit-only FIELDS (`{ msg?: undefined }` — the discriminated-union // absent-field idiom — and `{ p: undefined }` spellings): the // unit-only union. The runtime value is the interned unit, JSON @@ -6556,15 +6617,117 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx 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); - if (pt === undefined) continue; - if (pt?.kind === "void" && isUnitOnlyTsType(fieldTs, ctx.resolveTypeParam)) - pt = unitOnlyUnion(ctx.unions); - if (!pt || pt.kind === "void") { - return `the record shape is supported, but its member '${p.name}' has type '${checker.typeToString(fieldTs)}', which does not compile`; + if (memberMaps(fieldTs, ctx, p)) continue; + // Name the deepest member the failure comes from (`order.deployment. + // builds[].config`): a large message type otherwise points only at + // its outermost member. + const inner = innerBlockedMember(fieldTs, ctx, new Set()); + if (inner !== null && inner.path !== "") { + return `the record shape is supported, but its member '${p.name}${inner.path}' has type '${checker.typeToString(inner.type)}', which does not compile`; } + return `the record shape is supported, but its member '${p.name}' has type '${checker.typeToString(fieldTs)}', which does not compile`; } return null; } + +/** A member an interface, type literal or class declares (not one an + * object literal's inferred type carries). */ +function declaredMember(member: ts.Symbol): boolean { + return ( + member.declarations.length > 0 && + member.declarations.every((d) => ts.isPropertySignature(d) || ts.isPropertyDeclaration(d)) + ); +} + +/** Whether a record member of type `fieldTs` maps, by the member rules of + * mapRecordTypeInner (generic members erase; `any` and `any[]` members + * are checked-dynamic; unit-only members are the unit union). Overloaded + * members answer true: describeOverloadSetBlocker tells their story. */ +function memberMaps(fieldTs: ts.Type, ctx: TypeMapperCtx, member?: ts.Symbol): boolean { + const { checker } = ctx; + if (isOverloadedCallableMemberType(fieldTs, checker)) return true; + const pt = isGenericCallableMemberType(fieldTs, checker) + ? erasedGenericMemberType(fieldTs, ctx) + : mapType(fieldTs, ctx); + if (pt === undefined) return true; + if (pt === null && (fieldTs.flags & ts.TypeFlags.Any) !== 0) return true; + if ( + pt === null && + (member === undefined || declaredMember(member)) && + (fieldTs.isUnionType() ? ts.constituentTypes(fieldTs) : [fieldTs]).every( + (part) => + isAnyArrayType(part, checker) || + (part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null)) !== 0, + ) && + !isUnitOnlyTsType(fieldTs, ctx.resolveTypeParam) + ) + return true; + if (pt?.kind === "void" && isUnitOnlyTsType(fieldTs, ctx.resolveTypeParam)) return true; + return pt !== null && pt.kind !== "void"; +} + +/** The member path inside the unmapped type `t` that its failure comes + * from, and that member's type: `.name` steps into record members, `[]` + * into array elements, `()` into a function's return; union arms and + * promise payloads are transparent. The empty path means `t` itself is + * the blocker; null means `t` maps (or is `any`). */ +function innerBlockedMember( + t: ts.Type, + ctx: TypeMapperCtx, + seen: Set, +): { path: string; type: ts.Type } | null { + const { checker } = ctx; + // `any` is no component to name: the member holding it is the blocker. + if ((t.flags & ts.TypeFlags.Any) !== 0 || mapType(t, ctx) !== null) return null; + const here = { path: "", type: t }; + if (seen.has(t) || seen.size >= 16) return here; + seen.add(t); + const step = (prefix: string, inner: ts.Type): { path: string; type: ts.Type } => { + const found = innerBlockedMember(inner, ctx, seen); + return found === null ? here : { path: prefix + found.path, type: found.type }; + }; + if (t.isUnionType()) { + const UNIT = ts.TypeFlags.Undefined | ts.TypeFlags.Void | ts.TypeFlags.Null; + for (const part of ts.constituentTypes(t)) { + if ((part.flags & UNIT) !== 0 || mapType(part, ctx) !== null) continue; + return step("", part); + } + return here; + } + if ((t.flags & ts.TypeFlags.Object) === 0) return here; + if (checker.isArrayType(t)) { + const elem = checker.getTypeArguments(t as ts.TypeReference)[0]; + return elem === undefined ? here : step("[]", elem); + } + const symbol = t.getSymbol(); + if ( + symbol?.name === "Promise" && + symbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + ) { + const payload = checker.getTypeArguments(t as ts.TypeReference)[0]; + return payload === undefined ? here : step("", payload); + } + const calls = checker.getCallSignatures(t); + if (calls.length === 1) { + const sig = calls[0]!; + if (sig.getTypeParameters().length > 0) return here; + if (sig.getParameters().some((p) => mapType(checker.getTypeOfSymbol(p), ctx) === null)) + return here; + return step("()", checker.getReturnTypeOfSignature(sig)); + } + if ( + calls.length > 0 || + checker.getConstructSignatures(t).length > 0 || + checker.isTupleType(t) || + checker.getIndexInfosOfType(t).length > 0 + ) + return here; + for (const p of checker.getPropertiesOfType(t)) { + if ((p.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor)) !== 0) continue; + if (p.name.startsWith("__@")) continue; + const fieldTs = checker.getTypeOfSymbol(p); + if (memberMaps(fieldTs, ctx, p)) continue; + return step(`.${p.name}`, fieldTs); + } + return here; +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6412-any-valued-dictionaries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6412-any-valued-dictionaries.ts.json new file mode 100644 index 000000000..70a5823f4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6412-any-valued-dictionaries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6412-any-valued-dictionaries.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6413-sdk-declared-data-class/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6413-sdk-declared-data-class/main.ts.json new file mode 100644 index 000000000..83fe76101 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6413-sdk-declared-data-class/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6413-sdk-declared-data-class/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6414-pick-class-method-views.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6414-pick-class-method-views.ts.json new file mode 100644 index 000000000..c5b440992 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6414-pick-class-method-views.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6414-pick-class-method-views.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/nested-member-blocker-path.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/nested-member-blocker-path.ts.json new file mode 100644 index 000000000..b52b29f92 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/nested-member-blocker-path.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/nested-member-blocker-path.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6412-any-valued-dictionaries.ts b/tests/corpus/6412-any-valued-dictionaries.ts new file mode 100644 index 000000000..0cbdc6aad --- /dev/null +++ b/tests/corpus/6412-any-valued-dictionaries.ts @@ -0,0 +1,50 @@ +// Message types whose members are typed with `any`: string-keyed +// dictionaries of `any` values (`{ [key: string]: any }`, a `Dictionary` alias used bare), and `any[]` lists, also optional, nested in +// arrays of records. Values keep whatever the program or JSON stored. +type Dictionary = { [key: string]: T }; + +interface Build { + use: string; + src?: string; + config?: Dictionary; +} +interface Order { + id: string; + builds?: Build[]; + upload: { url: string; fields: { [key: string]: any } }; + tags: any[]; + extra?: any[]; +} + +function tags(): Dictionary { + return { a: 1, b: "x", c: [1, 2] }; +} +async function meta(): Promise { + return { n: 3 }; +} + +async function main(): Promise { + const order: Order = JSON.parse( + '{"id":"o1","builds":[{"use":"node","config":{"zero":true,"n":2}},{"use":"static","src":"a/**"}],' + + '"upload":{"url":"u","fields":{"k":"v","n":2}},"tags":[1,"two",null]}', + ); + console.log(order.id, order.upload.url, String(order.upload.fields.k), order.upload.fields["n"] + 1); + for (const build of order.builds ?? []) { + console.log(build.use, build.src ?? "-", JSON.stringify(build.config ?? null)); + } + console.log(order.tags.length, order.extra === undefined, JSON.stringify(order.tags)); + + const t = tags(); + for (const k of Object.keys(t)) console.log(k, JSON.stringify(t[k])); + t.d = { deep: true }; + console.log(JSON.stringify(t)); + const m = await meta(); + console.log(m.n * 2, JSON.stringify(m)); + + const made: Order = { id: "o2", upload: { url: "w", fields: {} }, tags: [], extra: [0] }; + made.upload.fields.signature = "s"; + made.tags.push({ k: 1 }); + console.log(JSON.stringify(made)); +} +void main(); diff --git a/tests/corpus/6413-sdk-declared-data-class/main.ts b/tests/corpus/6413-sdk-declared-data-class/main.ts new file mode 100644 index 000000000..32509da57 --- /dev/null +++ b/tests/corpus/6413-sdk-declared-data-class/main.ts @@ -0,0 +1,27 @@ +// A types-only SDK package declares the shape of JSON data as a class with +// fields only (`declare class UploadForm { url: string; fields: ... }`, +// with a merged namespace holding the fields interface). The program never +// constructs it: values arrive through JSON.parse or object literals, so +// they compile as records of the declared fields. +import type { UploadForm } from "upload-sdk"; + +interface Order { + id: string; + upload?: UploadForm; + forms: UploadForm[]; +} + +function describe(form: UploadForm | undefined): string { + if (!form) return "none"; + return `${form.url} ${form.fields.Policy} ${form.fields["X-Signature"]} ${form.fields["key"] ?? "-"} ${Object.keys(form.fields).join(",")}`; +} + +const order = JSON.parse( + '{"id":"o1","upload":{"url":"u","fields":{"Policy":"p","X-Signature":"s","key":"k"}},"forms":[]}', +) as Order; +console.log(order.id, describe(order.upload)); +const made: UploadForm = { url: "v", fields: { Policy: "q", "X-Signature": "t" } }; +order.forms.push(made); +made.fields.extra = "e"; +console.log(describe(made), JSON.stringify(order.forms)); +console.log(describe(undefined)); diff --git a/tests/corpus/6413-sdk-declared-data-class/node_modules/upload-sdk/index.d.ts b/tests/corpus/6413-sdk-declared-data-class/node_modules/upload-sdk/index.d.ts new file mode 100644 index 000000000..5c72897ab --- /dev/null +++ b/tests/corpus/6413-sdk-declared-data-class/node_modules/upload-sdk/index.d.ts @@ -0,0 +1,13 @@ +export declare class UploadForm { + /** The URL the form posts to. */ + url: string; + /** The hidden form fields. */ + fields: UploadForm.Fields; +} +export declare namespace UploadForm { + interface Fields { + Policy: string; + "X-Signature": string; + [key: string]: string; + } +} diff --git a/tests/corpus/6413-sdk-declared-data-class/node_modules/upload-sdk/package.json b/tests/corpus/6413-sdk-declared-data-class/node_modules/upload-sdk/package.json new file mode 100644 index 000000000..093d34b91 --- /dev/null +++ b/tests/corpus/6413-sdk-declared-data-class/node_modules/upload-sdk/package.json @@ -0,0 +1 @@ +{ "name": "upload-sdk", "version": "1.0.0", "types": "index.d.ts" } diff --git a/tests/corpus/6414-pick-class-method-views.ts b/tests/corpus/6414-pick-class-method-views.ts new file mode 100644 index 000000000..a2d61c836 --- /dev/null +++ b/tests/corpus/6414-pick-class-method-views.ts @@ -0,0 +1,39 @@ +// `Pick` and `Omit` over a class, used as structural parameter +// and field types: a view of the instance's own members. Calls through the +// view run the instance's methods on the instance itself, so its state is +// shared with every other reference. +class ApiClient { + private readonly base: string; + calls = 0; + constructor(base: string) { + this.base = base; + } + request(path: string): Promise { + this.calls++; + return Promise.resolve(this.base + path); + } + close(): void { + console.log("closed after", this.calls); + } +} + +interface Context { + client: Pick; + name: string; +} + +async function run(ctx: Context): Promise { + console.log(ctx.name, await ctx.client.request("/x"), await ctx.client.request("/y")); +} +function finish(client: Pick): void { + client.close(); +} +function count(client: Omit): number { + return client.calls; +} + +const client = new ApiClient("https://h"); +void run({ client, name: "ctx" }).then(() => { + finish(client); + console.log(count(client), client.calls); +}); diff --git a/tests/diagnostics/nested-member-blocker-path.ts b/tests/diagnostics/nested-member-blocker-path.ts new file mode 100644 index 000000000..60edd053c --- /dev/null +++ b/tests/diagnostics/nested-member-blocker-path.ts @@ -0,0 +1,19 @@ +// A record blocked by a member deep inside it names the member's path: +// through a function's result (`()`), the promise and union around it, +// record members (`.name`) and array elements (`[]`). +interface Job { + id: string; + handle: Promise & { cancel(): void }; +} +interface Order { + id: string; + jobs: Job[]; +} +interface Provider { + name: string; + poll: () => Promise; +} +function makeProvider(): Provider { + return { name: "p", poll: async () => undefined }; +} +console.log(makeProvider().name); diff --git a/tests/harness/__snapshots__/nested-member-blocker-path.ts.txt b/tests/harness/__snapshots__/nested-member-blocker-path.ts.txt new file mode 100644 index 000000000..e2d83b47c --- /dev/null +++ b/tests/harness/__snapshots__/nested-member-blocker-path.ts.txt @@ -0,0 +1,17 @@ +nested-member-blocker-path.ts:16:10 - error SC2009: values of type 'Provider' cannot be compiled: the record shape is supported, but its member 'poll().jobs[].handle' has type 'Promise & { cancel(): void; }', which does not compile + + 15 | } + 16 | function makeProvider(): Provider { + | ^~~~~~~~~~~~ + 17 | return { name: "p", poll: async () => undefined }; + + 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') + +nested-member-blocker-path.ts:19:13 - error SC2004: uses of 'makeProvider' inherit the blocker on its declaration + + 18 | } + 19 | console.log(makeProvider().name); + | ^~~~~~~~~~~~ + 20 | + + hint: the declaration of 'makeProvider' did not compile — fix the diagnostic reported there and these sites clear with it \ No newline at end of file From f4a7c4629d0a4f6a8a2bed1432b2e6fbd5bf589c Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 01:45:28 -0500 Subject: [PATCH 2/8] Keep a live Pick or Omit class view's identity when it flows into another structural type A Pick or Omit view of a program class that holds the instance (its view exposes a method) now carries the class's identity in the flow analysis, and its values count as instances of that class. Assigning such a view to another structural type keeps the instance, so reads through the second type see the instance's later writes instead of a copy's. --- .../src/frontend/lowering/class-protocols.ts | 7 +++++++ tests/corpus/6414-pick-class-method-views.ts | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts index a3f2ffb4d..1108bf0fa 100644 --- a/packages/compiler/src/frontend/lowering/class-protocols.ts +++ b/packages/compiler/src/frontend/lowering/class-protocols.ts @@ -414,6 +414,11 @@ export class ClassProtocols { for (const part of ts.constituentTypes(type)) add(part); return; } + // A Pick or Omit view's values are instances of the class it views. + if (this.pickOfProgramClass(type)) { + add(type.getAliasTypeArguments()[0]!); + return; + } const symbol = type.getSymbol(); if (symbol === undefined) return; if (this.isProgramClassInstance(type)) { @@ -603,6 +608,8 @@ export class ClassProtocols { return element !== undefined && this.carriesIdentity(element, marked); } if (this.isProgramClassInstance(type)) return true; + // A live Pick or Omit view of a program class holds the instance itself. + if (this.pickOfProgramClass(type) && this.usesCheckedIdentity(type)) return true; const symbol = type.getSymbol(); if (symbol === undefined) return false; if (marked.has(symbol)) return true; diff --git a/tests/corpus/6414-pick-class-method-views.ts b/tests/corpus/6414-pick-class-method-views.ts index a2d61c836..26e89a9fe 100644 --- a/tests/corpus/6414-pick-class-method-views.ts +++ b/tests/corpus/6414-pick-class-method-views.ts @@ -37,3 +37,19 @@ void run({ client, name: "ctx" }).then(() => { finish(client); console.log(count(client), client.calls); }); + +// A live view that flows into another structural type keeps the instance: +// reads through the second view see the instance's later writes. +interface Counter { + calls: number; +} +function counted(view: Pick): Counter { + void view.request("/z"); + return view; +} +const second = new ApiClient("https://k"); +const counter = counted(second); +second.calls += 10; +const holder: { counter: Counter } = { counter: counted(second) }; +second.calls = 100; +console.log("counter", counter.calls, holder.counter.calls, second.calls); From 004034e4e9d3925e98f9f195c3cacd4cad0a6682 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 01:10:46 -0700 Subject: [PATCH 3/8] Monomorphize generic overload calls and call generic methods through Pick views - A call that resolves to an overload signature of a generic function or method with an implementation (`load(k, map: Mapper): Promise; load(k): Promise; load(k, map?) {...}`) monomorphizes the implementation under the bindings the overload's resolved signature proves; a type parameter the overload never mentions is unknown, and parameters the overload omits must be optional in the implementation. It was refused. - `view.m(...)` where `view` is a `Pick`/`Omit` view of a program class and `m` is a generic (or generically overloaded) method extracts the instance from the view's live receiver and calls the method statically, instead of a runtime refusal reading the method through the untyped view. - Binding inference pairs the arms of `T | Promise` with an instantiated `R | Promise`, and `unknown | Promise` maps to the checked-dynamic value the checker reduces it to. Co-Authored-By: Claude Opus 5.5 --- .../frontend/lowering/generic-functions.ts | 92 +++++++++++++++++++ .../src/frontend/lowering/lower-calls.ts | 34 +++++++ packages/compiler/src/frontend/type-mapper.ts | 4 + .../6495-generic-method-overloads.ts.json | 6 ++ .../corpus/6496-overload-signatures.ts.json | 6 ++ .../tests/diagnostics/overload-fences.ts.json | 6 -- tests/corpus/6495-generic-method-overloads.ts | 54 +++++++++++ .../6496-overload-signatures.ts} | 8 +- .../__snapshots__/overload-fences.ts.txt | 6 -- 9 files changed, 200 insertions(+), 16 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6495-generic-method-overloads.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6496-overload-signatures.ts.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overload-fences.ts.json create mode 100644 tests/corpus/6495-generic-method-overloads.ts rename tests/{diagnostics/overload-fences.ts => corpus/6496-overload-signatures.ts} (66%) delete mode 100644 tests/harness/__snapshots__/overload-fences.ts.txt diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index f588faf6d..b6eb52a8a 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -268,10 +268,13 @@ export function genericCallInstance( { const rdecl = rsig ? rsig.declaration : undefined; if ( + rsig && rdecl && (ts.isFunctionDeclaration(rdecl) || ts.isMethodDeclaration(rdecl)) && !rdecl.body ) { + const overload = overloadCallInstance(lowerer, expr, info, rsig, rdecl); + if (overload) return overload; lowerer.unsupported( "SC1090", expr, @@ -342,6 +345,74 @@ export function genericCallInstance( ); } +/** A call that resolved to an OVERLOAD signature of a generic function + * or method with an implementation (`request(p: WithHandler): + * Promise; request(p: Plain): Promise; request(p: Params) + * {...}`): the implementation monomorphizes under the bindings the + * overload's resolved signature proves for its type parameters (the + * structural unification every generic call uses, run against the + * overload; unbound parameters take their declared defaults). The + * instance's parameters and result are the IMPLEMENTATION's under those + * bindings — the body was checked against them — and the call's + * arguments and result coerce across, exactly as a non-generic overload + * call reconciles with its implementation (parameters the overload omits + * must be optional in the implementation). Null when the overload does + * not belong to this implementation or the arities do not line up. */ +function overloadCallInstance( + lowerer: Lowerer, + expr: ts.CallExpression, + info: GenericFnInfo, + rsig: ts.Signature, + rdecl: ts.FunctionDeclaration | ts.MethodDeclaration, +): GenericInstance | null { + const impl = info.decl; + if ( + rdecl.parent !== impl.parent || + rdecl.name === undefined || + impl.name === undefined || + rdecl.name.getText() !== impl.name.getText() || + rdecl.parameters.nodes.length > impl.parameters.nodes.length || + impl.parameters.nodes + .slice(rdecl.parameters.nodes.length) + .some((p) => p.questionToken === undefined && p.initializer === undefined) || + rdecl.parameters.nodes.some((p) => p.dotDotDotToken !== undefined) + ) + return null; + const declSig = lowerer.checker.getSignatureFromDeclaration(impl); + if (!declSig) return null; + const tsBindings = new Map(); + const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings); + // A type parameter the overload never mentions (a handler the overload + // omits) is `unknown`, the checker's own answer for an uninferred one. + for (const tp of info.typeParams) { + if (bindings.has(tp)) continue; + bindings.set(tp, DYN); + tsBindings.set(tp, lowerer.checker.getUnknownType()); + } + const previous = lowerer.typeParamBindings; + const previousTs = lowerer.typeParamTsBindings; + lowerer.typeParamBindings = bindings; + lowerer.typeParamTsBindings = tsBindings; + let params: ParamShape[]; + let returnType: IrType | null; + try { + params = paramShapes( + lowerer, + impl.parameters.nodes, + declSig, + (_param, i) => expr.arguments.nodes[i] ?? expr, + ); + returnType = lowerer.mapTypeOf(lowerer.checker.getReturnTypeOfSignature(declSig)); + } finally { + lowerer.typeParamBindings = previous; + lowerer.typeParamTsBindings = previousTs; + } + if (!returnType) return null; + return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { + tsBindings, + }); +} + /** 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 @@ -705,6 +776,27 @@ function unifySignatureBindings( // so match it against the whole remaining union. Removing null // too is safe only when no undeclared null arm remains. unify(dParts[0]!, lowerer.checker.getNonNullableType(inst), depth + 1); + } else if (dParts.length > 1 && iParts.length === dParts.length) { + // `T | Promise` against `Res | Promise`: each declared + // part that names a generic target (Promise, an interface, an + // alias) pairs with the one instantiated part of the same target; + // a single remaining bare type parameter takes the single + // remaining instantiated part. + const remaining = [...iParts]; + const bare: ts.Type[] = []; + for (const d of dParts) { + if (d.flags & ts.TypeFlags.TypeParameter) { + bare.push(d); + continue; + } + const target = d.getSymbol() ?? d.getAliasSymbol(); + if (target === undefined) return; + const matches = remaining.filter((i) => (i.getSymbol() ?? i.getAliasSymbol()) === target); + if (matches.length !== 1) return; + remaining.splice(remaining.indexOf(matches[0]!), 1); + unify(d, matches[0]!, depth + 1); + } + if (bare.length === 1 && remaining.length === 1) unify(bare[0]!, remaining[0]!, depth + 1); } return; } diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 5a4838782..40ffcdb93 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -3309,6 +3309,8 @@ function lowerDynReceiverMethodCall( // any-elemented array). Typed receivers keep their own lowerings. const recvTs = lowerer.typeOf(access.expression); if (lowerer.classImplementedProtocol(recvTs)) { + const viewed = pickViewGenericMethodCall(lowerer, call, access, recvTs); + if (viewed) return viewed; const property = lowerer.checker.getPropertyOfType(recvTs, access.name.text); if ( property && @@ -10092,3 +10094,35 @@ function maybePromiseHandlerResult( } return stmt; } + +/** `view.m(args)` where `view` is a `Pick` / `Omit` view of a + * program class C and `m` is one of C's GENERIC methods (its overloads + * included): generic methods have no dispatchable method value, so the + * view's live receiver is extracted as the C instance it holds (a checked + * extraction — a value of another class throws Node-style TypeError + * text naming C) and the call monomorphizes statically against C, the + * same lowering a C-typed receiver gets. Null for other receivers. */ +function pickViewGenericMethodCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + recvTs: ts.Type, +): IrExpr | null { + const alias = recvTs.getAliasSymbol(); + if (alias === undefined || (alias.name !== "Pick" && alias.name !== "Omit")) return null; + if (!alias.declarations.every((d) => d.getSourceFile().isDeclarationFile)) return null; + const source = recvTs.getAliasTypeArguments()[0]; + const symbol = source?.getSymbol(); + const info = symbol ? lowerer.classBySymbol.get(symbol) : undefined; + if (!info || call.questionDotToken || access.questionDotToken) return null; + const found = findGenericMethodOn(lowerer, info, access.name.text); + if (!found) return null; + const loc = locOf(access.expression); + const lowered = lowerer.lowerExpr(access.expression); + const instance: IrType = { kind: "object", className: info.def.name }; + const receiver: IrExpr = + lowered.type.kind === "object" + ? lowered + : { kind: "dynCheck", value: lowerer.coerceToExpected(lowered, DYN), type: instance, loc }; + return lowerClassGenericMethodCall(lowerer, call, access, info, found, receiver); +} diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 58434ef6b..2f054fb71 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -6340,6 +6340,10 @@ function dynSubsumableUnionArm(arm: IrType, ctx: TypeMapperCtx): boolean { return true; case "record": case "array": + // `T | Promise` with T bound to `unknown`: the checked-dynamic value + // holds a promise by reference, so `unknown` absorbs the promise arm + // exactly as the checker's own union reduction does. + case "promise": return canConvertToDyn( arm, (id) => ctx.shapes.get(id), diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6495-generic-method-overloads.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6495-generic-method-overloads.ts.json new file mode 100644 index 000000000..98806a53c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6495-generic-method-overloads.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6495-generic-method-overloads.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6496-overload-signatures.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6496-overload-signatures.ts.json new file mode 100644 index 000000000..8bc601bd6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6496-overload-signatures.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6496-overload-signatures.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overload-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overload-fences.ts.json deleted file mode 100644 index 1515ceeb9..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/overload-fences.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/diagnostics/overload-fences.ts" - ], - "diags": [] -} diff --git a/tests/corpus/6495-generic-method-overloads.ts b/tests/corpus/6495-generic-method-overloads.ts new file mode 100644 index 000000000..34bcfa712 --- /dev/null +++ b/tests/corpus/6495-generic-method-overloads.ts @@ -0,0 +1,54 @@ +// Calls that resolve to OVERLOAD signatures of a generic method or +// function: the implementation monomorphizes under what the overload +// proves for its type parameters (a parameter the overload never mentions +// is unknown), directly and through a `Pick` view of the class, whose +// live receiver keeps the instance's state. `T | Promise` callbacks +// bind T from either arm. +interface Reply { + code: number; + text: string; +} +type Mapper = (reply: Reply) => T | Promise; + +class Store { + loads = 0; + async load(key: string, map: Mapper): Promise; + async load(key: string): Promise; + async load(key: string, map?: Mapper): Promise { + this.loads++; + const reply: Reply = { code: 200, text: key }; + if (map === undefined) return reply; + return await map(reply); + } +} + +interface Services { + store: Pick; +} + +function pick(items: T[], index: number): T; +function pick(items: T[]): T; +function pick(items: T[], index = 0): T { + return items[index]!; +} + +async function settle(reply: Reply, map?: Mapper): Promise { + return map ? await map(reply) : reply; +} + +async function main(): Promise { + const store = new Store(); + const plain = await store.load("alpha"); + console.log(plain.code, plain.text); + const length = await store.load("beta", (reply) => reply.text.length); + console.log(length * 2); + const services: Services = { store }; + const viaView = await services.store.load("gamma"); + const upper = await services.store.load("delta", async (reply) => reply.text.toUpperCase()); + console.log(viaView.text, upper, store.loads); + console.log(pick(["x", "y"]), pick([1, 2, 3], 2)); + console.log(JSON.stringify(await settle({ code: 1, text: "s" }))); + console.log(await settle({ code: 2, text: "tt" }, (reply) => reply.code + reply.text.length)); +} + +main(); diff --git a/tests/diagnostics/overload-fences.ts b/tests/corpus/6496-overload-signatures.ts similarity index 66% rename from tests/diagnostics/overload-fences.ts rename to tests/corpus/6496-overload-signatures.ts index 0cf1f7e15..5902d8c19 100644 --- a/tests/diagnostics/overload-fences.ts +++ b/tests/corpus/6496-overload-signatures.ts @@ -1,7 +1,7 @@ -// The overload fences that REMAIN now that overload signatures lower: -// generic overload calls (the per-instantiation body would be typed -// against a signature it was never checked under), and the multi- -// signature type surfacing where a binding needs it spelled. +// Overload signatures: a generic overload call monomorphizes the +// implementation under the bindings the overload proves (a parameter the +// overload omits is optional in the implementation), and an immutable +// alias of an overloaded declaration projects onto the implementation. function twice(x: T): T[]; function twice(x: T, n: number): T[]; function twice(x: T, n?: number): T[] { diff --git a/tests/harness/__snapshots__/overload-fences.ts.txt b/tests/harness/__snapshots__/overload-fences.ts.txt deleted file mode 100644 index 6c4b2f405..000000000 --- a/tests/harness/__snapshots__/overload-fences.ts.txt +++ /dev/null @@ -1,6 +0,0 @@ -overload-fences.ts:12:13 - error SC1090: calls selecting an overload signature of a generic function (only the implementation signature monomorphizes) is not supported yet - - 11 | } - 12 | console.log(twice("q").length); - | ^~~~~~~~~~ - 13 | function pick(x: "a"): string; \ No newline at end of file From 19835975eae1b9f1c140965581c5d8e31dbaae80 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 12:25:57 -0500 Subject: [PATCH 4/8] Bind generic overload calls by the overload's own type parameters --- .../frontend/lowering/generic-functions.ts | 61 ++++++++++++++----- .../src/frontend/lowering/lower-calls.ts | 14 +++++ .../6497-generic-overload-bindings.ts.json | 6 ++ .../pick-view-generic-methods.ts.json | 6 ++ .../corpus/6497-generic-overload-bindings.ts | 34 +++++++++++ .../diagnostics/pick-view-generic-methods.ts | 16 +++++ .../pick-view-generic-methods.ts.txt | 6 ++ .../6495-generic-method-overloads.ts.json | 1 + .../6496-overload-signatures.ts.json | 1 + .../6497-generic-overload-bindings.ts.json | 1 + 10 files changed, 131 insertions(+), 15 deletions(-) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6497-generic-overload-bindings.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pick-view-generic-methods.ts.json create mode 100644 tests/corpus/6497-generic-overload-bindings.ts create mode 100644 tests/diagnostics/pick-view-generic-methods.ts create mode 100644 tests/harness/__snapshots__/pick-view-generic-methods.ts.txt create mode 100644 tests/harness/corpus-costs/6495-generic-method-overloads.ts.json create mode 100644 tests/harness/corpus-costs/6496-overload-signatures.ts.json create mode 100644 tests/harness/corpus-costs/6497-generic-overload-bindings.ts.json diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index b6eb52a8a..446ffa2bd 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -320,23 +320,11 @@ export function genericCallInstance( // different fields). Non-literal bindings (a key union, plain string) // share one runtime-keyed instance per IR signature, exactly the // widened discipline. - const keyofTps = keyofConstrainedTypeParams(info); - if (keyofTps.size > 0) { + if (keyofConstrainedTypeParams(info).size > 0) { const tsBindings = new Map(); const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings); - const litKey = info.typeParams - .filter((tp) => keyofTps.has(tp)) - .map((tp) => { - const bound = tsBindings.get(tp); - return bound?.isStringLiteralType() - ? JSON.stringify(bound.value) - : bound?.isNumberLiteralType() - ? String(bound.value) - : "*"; - }) - .join(","); return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { - extraKey: `@${litKey}`, + extraKey: keyofLiteralKey(info, tsBindings), tsBindings, }); } @@ -381,7 +369,26 @@ function overloadCallInstance( const declSig = lowerer.checker.getSignatureFromDeclaration(impl); if (!declSig) return null; const tsBindings = new Map(); - const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings); + const bindings = new Map(); + unifySignatureBindings(lowerer, info, rsig, bindings, tsBindings); + // Explicit type arguments name the OVERLOAD's type parameters, which + // need not match the implementation's in order: the resolved overload + // already carries them, so unification binds first, and an explicit + // argument binds only a still-unbound implementation parameter of the + // same name. + const overloadTps = rdecl.typeParameters?.nodes ?? []; + expr.typeArguments?.nodes.forEach((ta, i) => { + const name = overloadTps[i]?.name.text; + const tp = info.typeParams.find((p) => p.name === name); + if (!tp || bindings.has(tp)) return; + const taT = lowerer.checker.getTypeFromTypeNode(ta); + const mapped = lowerer.mapTypeOf(taT); + if (mapped) { + bindings.set(tp, mapped); + tsBindings.set(tp, taT); + } + }); + bindDefaultTypeParams(lowerer, info.typeParams, impl.typeParameters?.nodes, bindings, tsBindings); // A type parameter the overload never mentions (a handler the overload // omits) is `unknown`, the checker's own answer for an uninferred one. for (const tp of info.typeParams) { @@ -408,7 +415,12 @@ function overloadCallInstance( lowerer.typeParamTsBindings = previousTs; } if (!returnType) return null; + // keyof-constrained parameters key their instance on the bound literal, + // as on the implementation-signature path. return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { + ...(keyofConstrainedTypeParams(info).size > 0 + ? { extraKey: keyofLiteralKey(info, tsBindings) } + : {}), tsBindings, }); } @@ -641,6 +653,25 @@ function keyofConstrainedTypeParams(info: GenericFnInfo): Set { return out; } +/** The instance-key suffix of the literals bound to `info`'s + * keyof-constrained type parameters (`@"a"`; `*` for a non-literal + * binding): two literals can map to one IR signature while their bodies + * read different fields. */ +function keyofLiteralKey(info: GenericFnInfo, tsBindings: Map): string { + const keyofTps = keyofConstrainedTypeParams(info); + const literals = info.typeParams + .filter((tp) => keyofTps.has(tp)) + .map((tp) => { + const bound = tsBindings.get(tp); + return bound?.isStringLiteralType() + ? JSON.stringify(bound.value) + : bound?.isNumberLiteralType() + ? String(bound.value) + : "*"; + }); + return `@${literals.join(",")}`; +} + /** Type-parameter symbol → concrete IR type for one instantiation. * Explicit type arguments bind directly; the rest come from structurally * matching each DECLARED param/return type (which mentions the type diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 40ffcdb93..316ebadf9 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -10120,6 +10120,20 @@ function pickViewGenericMethodCall( const loc = locOf(access.expression); const lowered = lowerer.lowerExpr(access.expression); const instance: IrType = { kind: "object", className: info.def.name }; + // A view that statically holds an instance of another class (a + // structurally compatible one) calls that class's method in Node, not + // C's: the call is refused rather than extracted as a C instance. + if ( + lowered.type.kind === "object" && + lowered.type.className !== info.def.name && + !lowerer.isSubclassOf(lowered.type.className, info.def.name) + ) { + lowerer.unsupported( + "SC1090", + access, + `calling the generic method '${access.name.text}' through a '${alias.name}' view of '${info.def.name}' that holds a '${lowerer.fmt(lowered.type)}'`, + ); + } const receiver: IrExpr = lowered.type.kind === "object" ? lowered diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6497-generic-overload-bindings.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6497-generic-overload-bindings.ts.json new file mode 100644 index 000000000..06e7bb17f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6497-generic-overload-bindings.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6497-generic-overload-bindings.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pick-view-generic-methods.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pick-view-generic-methods.ts.json new file mode 100644 index 000000000..1dafe9a3a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pick-view-generic-methods.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/pick-view-generic-methods.ts" + ], + "diags": [] +} diff --git a/tests/corpus/6497-generic-overload-bindings.ts b/tests/corpus/6497-generic-overload-bindings.ts new file mode 100644 index 000000000..e27e94e20 --- /dev/null +++ b/tests/corpus/6497-generic-overload-bindings.ts @@ -0,0 +1,34 @@ +// Bindings of generic overload calls: explicit type arguments name the +// overload's type parameters, which can be declared in another order than +// the implementation's, and keyof-constrained parameters bound to +// different literals through an overload read their own fields. +function tag(value: A, label: B): string; +function tag(value: A, label?: B): string { + return `${typeof value}:${typeof label}:${String(value)}${String(label)}`; +} +console.log(tag(5, "x")); +console.log(tag("y", true)); + +class Labels { + prefix = "#"; + render(value: A, label: B): string; + render(value: A, label?: B): string { + return `${this.prefix}${typeof value}/${typeof label}`; + } +} +console.log(new Labels().render("z", 7)); + +interface Row { + a: number; + b: number; + name: string; +} +function read(row: T, key: K): T[K]; +function read(row: T, key: K, fallback: T[K]): T[K]; +function read(row: T, key: K, fallback?: T[K]): T[K] { + const value = row[key]; + return value === undefined ? fallback! : value; +} +const row: Row = { a: 1, b: 2, name: "r" }; +console.log(read(row, "a"), read(row, "b"), read(row, "name")); +console.log(read(row, "b", 0) + read(row, "a", 0)); diff --git a/tests/diagnostics/pick-view-generic-methods.ts b/tests/diagnostics/pick-view-generic-methods.ts new file mode 100644 index 000000000..912867dc6 --- /dev/null +++ b/tests/diagnostics/pick-view-generic-methods.ts @@ -0,0 +1,16 @@ +// A generic method called through a `Pick` view of a class compiles +// against that class. A view that holds an instance of a structurally +// compatible, unrelated class is refused: Node calls that class's own +// method. +class Store { + load(key: string, map: (text: string) => T): T { + return map(key); + } +} +class Mirror { + load(key: string, map: (text: string) => T): T { + return map(`${key}!`); + } +} +const view: Pick = new Mirror(); +console.log(view.load("k", (text) => text.length)); diff --git a/tests/harness/__snapshots__/pick-view-generic-methods.ts.txt b/tests/harness/__snapshots__/pick-view-generic-methods.ts.txt new file mode 100644 index 000000000..59e7ed642 --- /dev/null +++ b/tests/harness/__snapshots__/pick-view-generic-methods.ts.txt @@ -0,0 +1,6 @@ +pick-view-generic-methods.ts:16:13 - error SC1090: calling the generic method 'load' through a 'Pick' view of 'Store' that holds a 'Mirror' is not supported yet + + 15 | const view: Pick = new Mirror(); + 16 | console.log(view.load("k", (text) => text.length)); + | ^~~~~~~~~ + 17 | \ No newline at end of file diff --git a/tests/harness/corpus-costs/6495-generic-method-overloads.ts.json b/tests/harness/corpus-costs/6495-generic-method-overloads.ts.json new file mode 100644 index 000000000..89eeb5b0c --- /dev/null +++ b/tests/harness/corpus-costs/6495-generic-method-overloads.ts.json @@ -0,0 +1 @@ +{ "plain": 2783, "san": 2211 } diff --git a/tests/harness/corpus-costs/6496-overload-signatures.ts.json b/tests/harness/corpus-costs/6496-overload-signatures.ts.json new file mode 100644 index 000000000..5b1f193e3 --- /dev/null +++ b/tests/harness/corpus-costs/6496-overload-signatures.ts.json @@ -0,0 +1 @@ +{ "plain": 2032, "san": 1924 } diff --git a/tests/harness/corpus-costs/6497-generic-overload-bindings.ts.json b/tests/harness/corpus-costs/6497-generic-overload-bindings.ts.json new file mode 100644 index 000000000..a94adc8be --- /dev/null +++ b/tests/harness/corpus-costs/6497-generic-overload-bindings.ts.json @@ -0,0 +1 @@ +{ "plain": 2061, "san": 1900 } From 77da2ea47444a27d55f21b8c67cdec70e0d0538e Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 13:50:32 -0500 Subject: [PATCH 5/8] Count every class a Pick or Omit view accepts among its values' origins A Pick or Omit view of a program class also accepts other program classes that have the picked members. When such a view flows into another structural type, the classes behind that type now include those classes, so writes to their instances keep the second type a live view instead of a copy. --- .../src/frontend/lowering/class-protocols.ts | 31 ++++++++++++++++++- tests/corpus/6414-pick-class-method-views.ts | 28 +++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts index 1108bf0fa..6208ab9f1 100644 --- a/packages/compiler/src/frontend/lowering/class-protocols.ts +++ b/packages/compiler/src/frontend/lowering/class-protocols.ts @@ -6,6 +6,8 @@ export class ClassProtocols { private implemented: Set | null = null; private readonly methods = new Map(); private readonly answers = new Map(); + /** The program classes each Pick or Omit view accepts (classesAcceptedBy). */ + private readonly accepted = new Map(); /** What markIdentityFlows learned about the program, while it runs. */ private facts: FlowFacts | null = null; @@ -414,9 +416,12 @@ export class ClassProtocols { for (const part of ts.constituentTypes(type)) add(part); return; } - // A Pick or Omit view's values are instances of the class it views. + // A Pick or Omit view's values are instances of the class it views, + // or of any other program class that has the view's members. if (this.pickOfProgramClass(type)) { add(type.getAliasTypeArguments()[0]!); + for (const other of this.classesAcceptedBy(type)) + for (const related of this.classFamily(other, facts)) out.add(related); return; } const symbol = type.getSymbol(); @@ -434,6 +439,30 @@ export class ClassProtocols { return out; } + /** The program classes with methods whose instances are assignable to + * `type`. */ + private classesAcceptedBy(type: ts.Type): ts.Symbol[] { + const known = this.accepted.get(type); + if (known !== undefined) return known; + this.index(); + const found = new Set(); + for (const entries of this.methods.values()) + for (const declaration of entries) { + const symbol = declaration.name + ? this.checker.getSymbolAtLocation(declaration.name) + : undefined; + if ( + symbol !== undefined && + !found.has(symbol) && + this.checker.isTypeAssignableTo(this.checker.getDeclaredTypeOfSymbol(symbol), type) + ) + found.add(symbol); + } + const result = [...found]; + this.accepted.set(type, result); + return result; + } + /** A class symbol with its base classes and the program classes that * extend it. */ private classFamily(symbol: ts.Symbol, facts: FlowFacts): ts.Symbol[] { diff --git a/tests/corpus/6414-pick-class-method-views.ts b/tests/corpus/6414-pick-class-method-views.ts index 26e89a9fe..6bbbbf780 100644 --- a/tests/corpus/6414-pick-class-method-views.ts +++ b/tests/corpus/6414-pick-class-method-views.ts @@ -53,3 +53,31 @@ second.calls += 10; const holder: { counter: Counter } = { counter: counted(second) }; second.calls = 100; console.log("counter", counter.calls, holder.counter.calls, second.calls); + +// A view also accepts other classes that have its members. Their instances +// flow through it the same way. +class Meter { + label = "meter"; + reads = 0; + read(): number { + return this.reads; + } +} +class Gauge { + reads = 0; + read(): number { + return this.reads; + } +} +interface Reading { + reads: number; +} +function reading(view: Pick): Reading { + console.log("read", view.read()); + return view; +} +const gauge = new Gauge(); +const result = reading(gauge); +gauge.reads = 50; +const meter = new Meter(); +console.log("reading", result.reads, reading(meter).reads, meter.label); From 32fce771f340839fdf28ffc6622dd09fc88db016 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 14:38:45 -0500 Subject: [PATCH 6/8] Return undefined from overload calls whose generic body misses an index A call that selects an overload signature of a generic function now gives its instance the same result type as an implementation-signature call: when the body can return an indexed read that misses, the result includes undefined, as in JavaScript. Before, such a call failed at the missing read instead of returning undefined. --- .../frontend/lowering/generic-functions.ts | 23 +++++++++++-------- .../corpus/6497-generic-overload-bindings.ts | 13 +++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index 446ffa2bd..ca4664f20 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -303,14 +303,7 @@ export function genericCallInstance( fenceGenericSignatureResult(lowerer, expr, retTs); lowerer.badType(expr, retTs); } - // A body that can return an indexed read that misses returns undefined - // there, as in JavaScript, whatever the declared result says. An async - // body's promise is always present and settles with undefined there. - const returnType = lowerer.runtimeOptionalGenericReturns.has(info.decl) - ? mappedReturn.kind === "promise" - ? { kind: "promise" as const, inner: lowerer.runtimeOptionalType(mappedReturn.inner) } - : lowerer.runtimeOptionalType(mappedReturn) - : mappedReturn; + const returnType = runtimeReturnType(lowerer, info, mappedReturn); // keyof-constrained type parameters (`K extends keyof T`): the bound // LITERAL is semantic — the instance body reads the named field — so @@ -417,7 +410,8 @@ function overloadCallInstance( if (!returnType) return null; // keyof-constrained parameters key their instance on the bound literal, // as on the implementation-signature path. - return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { + const runtimeReturn = runtimeReturnType(lowerer, info, returnType); + return internGenericInstance(lowerer, expr, info, params, runtimeReturn, () => bindings, { ...(keyofConstrainedTypeParams(info).size > 0 ? { extraKey: keyofLiteralKey(info, tsBindings) } : {}), @@ -425,6 +419,17 @@ function overloadCallInstance( }); } +/** An instance's result type: a body that can return an indexed read + * that misses returns undefined there, as in JavaScript, whatever the + * declared result says. An async body's promise is always present and + * settles with undefined there. */ +function runtimeReturnType(lowerer: Lowerer, info: GenericFnInfo, mapped: IrType): IrType { + if (!lowerer.runtimeOptionalGenericReturns.has(info.decl)) return mapped; + return mapped.kind === "promise" + ? { kind: "promise", inner: lowerer.runtimeOptionalType(mapped.inner) } + : lowerer.runtimeOptionalType(mapped); +} + /** 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 diff --git a/tests/corpus/6497-generic-overload-bindings.ts b/tests/corpus/6497-generic-overload-bindings.ts index e27e94e20..0d33acaae 100644 --- a/tests/corpus/6497-generic-overload-bindings.ts +++ b/tests/corpus/6497-generic-overload-bindings.ts @@ -32,3 +32,16 @@ function read(row: T, key: K, fallback?: T[K]): T[K] { const row: Row = { a: 1, b: 2, name: "r" }; console.log(read(row, "a"), read(row, "b"), read(row, "name")); console.log(read(row, "b", 0) + read(row, "a", 0)); + +// An overload call whose implementation returns an indexed read that +// misses returns undefined, as the implementation's own calls do. +function at(items: T[], index: number): T; +function at(items: T[], index: number, label: string): T; +function at(items: T[], index: number, label?: string): T { + if (label !== undefined) console.log(label); + return items[index]; +} +console.log(at([1, 2, 3], 1), at([1, 2, 3], 5)); +console.log(at(["a", "b"], 0, "strings"), at(["a", "b"], 9, "strings")); +const missing = at([{ id: 1 }], 3); +console.log(missing === undefined, typeof missing); From b70cadb4024d813cf9a89860c85646dc7efc3c24 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 17:56:44 -0500 Subject: [PATCH 7/8] Merge main --- .github/workflows/ci.yml | 126 +- .github/workflows/release.yml | 6 + CHANGELOG.md | 69 +- benchmarks/runtime/README.md | 1 + benchmarks/runtime/workloads.json | 7 + .../runtime/workloads/channel-messages.ts | 21 + docs/content/docs/cli.mdx | 5 +- docs/content/docs/coverage.mdx | 1 - docs/content/docs/dependencies.mdx | 16 +- docs/content/docs/limitations.mdx | 14 + .../content/limitations/builtin-apis/date.mdx | 6 +- .../limitations/builtin-apis/eventemitter.mdx | 1 + .../limitations/builtin-apis/exit-status.mdx | 3 +- .../limitations/builtin-apis/globals.mdx | 2 + .../builtin-apis/object-freezing.mdx | 5 + .../builtin-apis/performance-entries.mdx | 2 + .../limitations/builtin-apis/prototypes.mdx | 4 + .../builtin-apis/streams-and-readline.mdx | 12 + .../limitations/builtin-apis/test-mocks.mdx | 3 +- .../limitations/builtin-apis/test-runner.mdx | 3 +- docs/content/limitations/builtin-apis/v8.mdx | 5 + .../limitations/builtin-function-values.mdx | 14 +- .../limitations/child-process-workers.mdx | 4 + .../console-and-process-streams.mdx | 3 +- .../limitations/dates-and-timezones.mdx | 6 +- .../content/limitations/dns-lookup-errors.mdx | 1 + docs/content/limitations/graceful-fs.mdx | 4 + .../language-features/function-values.mdx | 2 +- .../language-features/generics.mdx | 6 + .../limitations/module-path-globals.mdx | 4 + .../limitations/runtime-code-evaluation.mdx | 9 +- .../null-and-optional-values.mdx | 3 +- .../limitations/unix-domain-sockets.mdx | 2 + .../limitations/webassembly-modules.mdx | 36 +- .../src/generated/node-v24-compatibility.json | 130 +- .../generated/node-v24-backlog.json | 411 +- .../generated/node-v24-internal.json | 331 +- internal/compatibility/static-support.json | 30 +- package.json | 1 + packages/cli-darwin-arm64/package.json | 2 +- packages/cli-darwin-x64/package.json | 2 +- packages/cli-linux-arm64-gnu/package.json | 2 +- packages/cli-linux-arm64-musl/package.json | 2 +- packages/cli-linux-x64-gnu/package.json | 2 +- packages/cli-linux-x64-musl/package.json | 2 +- packages/cli-win32-x64-msvc/package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/scripts/install-native.mjs | 2 + packages/cli/src/bootstrap.ts | 8 + .../ambient/scriptc-node-fallback.d.ts | 222 +- packages/compiler/package.json | 3 +- .../src/backend/executable-features.ts | 7 + packages/compiler/src/backend/library-pack.ts | 4 + .../compiler/src/backend/llvm/byte-numbers.ts | 7 +- .../compiler/src/backend/llvm/bytes-alias.ts | 23 + .../backend/llvm/bytes-loop-versions.test.ts | 278 + .../src/backend/llvm/bytes-loop-versions.ts | 578 ++ .../src/backend/llvm/counted-loops.ts | 51 + packages/compiler/src/backend/llvm/dyn.ts | 110 +- .../src/backend/llvm/emit-function.ts | 1352 ++++ .../compiler/src/backend/llvm/emit-module.ts | 2255 ++++++ .../src/backend/llvm/emit-statements.ts | 2083 ++++++ packages/compiler/src/backend/llvm/emitter.ts | 6354 +---------------- .../compiler/src/backend/llvm/expr-bytes.ts | 64 +- .../src/backend/llvm/expr-callbacks.ts | 71 +- .../src/backend/llvm/expr-containers.ts | 2 +- .../compiler/src/backend/llvm/expr-dynamic.ts | 53 +- .../src/backend/llvm/expr-primitives.ts | 46 +- .../src/backend/llvm/expr-stream-bridges.ts | 57 +- .../src/backend/llvm/expr-stream-callbacks.ts | 42 +- .../compiler/src/backend/llvm/lib-datagram.ts | 4 + .../compiler/src/backend/llvm/lib-dispatch.ts | 46 +- .../src/backend/llvm/lib-filesystem.ts | 24 + .../compiler/src/backend/llvm/lib-network.ts | 47 + .../compiler/src/backend/llvm/lib-process.ts | 53 +- .../compiler/src/backend/llvm/lib-shared.ts | 83 +- .../compiler/src/backend/llvm/lib-stream.ts | 34 +- .../compiler/src/backend/llvm/receivers.ts | 409 ++ .../compiler/src/backend/llvm/wasm-modules.ts | 77 + .../src/backend/native-link-info-core.ts | 15 + .../compiler/src/backend/native/contracts.ts | 24 + .../compiler/src/backend/native/executable.ts | 407 +- .../compiler/src/backend/runtime-pack-core.ts | 6 + .../src/backend/runtime-pack-targets.test.ts | 4 + .../compiler/src/backend/runtime-pack.test.ts | 4 + .../src/backend/stack-observations.test.ts | 126 + .../src/backend/stack-observations.ts | 166 +- .../src/backend/target-diagnostics.ts | 8 + packages/compiler/src/backend/warm-cache.ts | 4 + packages/compiler/src/coverage/report.ts | 8 +- .../compiler/src/coverage/surface-manifest.ts | 9 +- .../compiler/src/diagnostics/diagnostic.ts | 38 +- .../src/executable/executable-cache.test.ts | 7 +- .../src/executable/executable-cache.ts | 16 + .../compiler/src/frontend/builtin-modules.ts | 53 +- packages/compiler/src/frontend/cjs-syntax.ts | 43 +- .../compiler/src/frontend/contract-types.ts | 2 +- .../src/frontend/experimental-import.ts | 6 +- .../compiler/src/frontend/fork-target.test.ts | 3 +- packages/compiler/src/frontend/fork-target.ts | 9 +- .../src/frontend/helper-tokens.test.ts | 8 +- .../compiler/src/frontend/helper-tokens.ts | 15 +- .../src/frontend/inferred-js-diagnostics.ts | 14 +- .../compiler/src/frontend/input-tracker.ts | 34 +- .../compiler/src/frontend/lib-contract.ts | 20 +- packages/compiler/src/frontend/lib-exports.ts | 2 +- .../src/frontend/lowering/array-values.ts | 73 +- .../src/frontend/lowering/assert-instances.ts | 2 +- .../frontend/lowering/assertion-members.ts | 168 + .../src/frontend/lowering/binding-analysis.ts | 34 +- .../src/frontend/lowering/builtin-calls.ts | 45 + .../lowering/builtin-function-fences.ts | 2 +- .../lowering/builtins/async-context.ts | 2 +- .../src/frontend/lowering/builtins/blob.ts | 8 +- .../lowering/builtins/builtin-class-dyn.ts | 159 + .../lowering/builtins/callable-aliases.ts | 10 +- .../builtins/child-process-methods.ts | 301 +- .../lowering/builtins/class-values.ts | 7 +- .../frontend/lowering/builtins/compression.ts | 4 +- .../frontend/lowering/builtins/constants.ts | 2 +- .../src/frontend/lowering/builtins/crypto.ts | 12 +- .../src/frontend/lowering/builtins/dates.ts | 5 +- .../lowering/builtins/disposable-stack.ts | 7 +- .../src/frontend/lowering/builtins/errors.ts | 82 +- .../frontend/lowering/builtins/filesystem.ts | 4 +- .../frontend/lowering/builtins/fs-streams.ts | 8 +- .../src/frontend/lowering/builtins/ipc.ts | 58 +- .../src/frontend/lowering/builtins/json.ts | 2 +- .../frontend/lowering/builtins/key-object.ts | 4 +- .../lowering/builtins/module-bindings.ts | 19 +- .../lowering/builtins/module-calls.ts | 6 +- .../lowering/builtins/module-resolution.ts | 6 +- .../frontend/lowering/builtins/navigator.ts | 3 +- .../frontend/lowering/builtins/net-address.ts | 8 +- .../frontend/lowering/builtins/performance.ts | 317 +- .../src/frontend/lowering/builtins/process.ts | 297 +- .../frontend/lowering/builtins/promises.ts | 271 +- .../lowering/builtins/promisify-callback.ts | 377 + .../frontend/lowering/builtins/properties.ts | 42 +- .../frontend/lowering/builtins/readline.ts | 133 +- .../lowering/builtins/receiver-types.ts | 4 +- .../lowering/builtins/runtime-constructor.ts | 181 + .../frontend/lowering/builtins/text-codecs.ts | 13 +- .../src/frontend/lowering/builtins/timers.ts | 4 +- .../src/frontend/lowering/builtins/url.ts | 2 +- .../src/frontend/lowering/builtins/v8.ts | 111 + .../frontend/lowering/builtins/webassembly.ts | 286 +- .../frontend/lowering/builtins/webcrypto.ts | 6 +- .../src/frontend/lowering/builtins/workers.ts | 4 +- .../src/frontend/lowering/call-arguments.ts | 76 +- .../src/frontend/lowering/call-signatures.ts | 220 +- .../frontend/lowering/checked-promise-all.ts | 2 +- .../frontend/lowering/cjs-exports-object.ts | 144 +- .../frontend/lowering/cjs-inline-require.ts | 6 +- .../src/frontend/lowering/cjs-tsc-interop.ts | 8 +- .../src/frontend/lowering/class-attributes.ts | 440 ++ .../src/frontend/lowering/class-callbacks.ts | 6 +- .../frontend/lowering/class-construction.ts | 48 +- .../frontend/lowering/class-descriptors.ts | 2 +- .../lowering/class-dynamic-dispatch.ts | 285 +- .../src/frontend/lowering/class-iterables.ts | 35 + .../lowering/class-method-mutations.ts | 34 +- .../frontend/lowering/class-method-values.ts | 23 +- .../lowering/class-protocol-dispatch.ts | 26 +- .../src/frontend/lowering/class-protocols.ts | 24 +- .../src/frontend/lowering/class-prototypes.ts | 56 +- .../frontend/lowering/class-static-data.ts | 2 +- .../src/frontend/lowering/code-generation.ts | 146 + .../lowering/coercions/function-adapters.ts | 155 +- .../lowering/coercions/structural-plans.ts | 14 +- .../frontend/lowering/coercions/structural.ts | 13 + .../frontend/lowering/collection-ingestion.ts | 2 +- .../frontend/lowering/collection-narrowing.ts | 7 +- .../lowering/construction-escapes.test.ts | 4 +- .../frontend/lowering/construction-escapes.ts | 8 +- .../frontend/lowering/constructor-returns.ts | 19 +- .../lowering/containers/array-callbacks.ts | 44 +- .../lowering/containers/array-construction.ts | 37 +- .../lowering/containers/array-methods.ts | 6 +- .../src/frontend/lowering/containers/bytes.ts | 26 +- .../lowering/containers/callback-arguments.ts | 60 + .../containers/collection-grouping.ts | 2 +- .../lowering/containers/collection-methods.ts | 38 +- .../lowering/containers/dynamic-receivers.ts | 2 +- .../lowering/containers/exec-result-uses.ts | 2 +- .../lowering/containers/indexed-objects.ts | 112 +- .../lowering/containers/iterator-helpers.ts | 16 +- .../lowering/containers/match-row-uses.ts | 2 +- .../lowering/containers/string-and-regexp.ts | 81 +- .../lowering/deferred-module-initializers.ts | 53 +- .../src/frontend/lowering/divergences.ts | 100 +- .../src/frontend/lowering/dyn-streams.ts | 326 + .../src/frontend/lowering/error-name-order.ts | 98 + .../lowering/expressions/array-literals.ts | 18 +- .../frontend/lowering/expressions/binary.ts | 53 +- .../lowering/expressions/element-access.ts | 35 +- .../lowering/expressions/hosted-narrowing.ts | 157 + .../lowering/expressions/identifiers.ts | 114 +- .../expressions/keyed-field-targets.ts | 271 + .../lowering/expressions/narrowing.ts | 167 +- .../lowering/expressions/native-proxy.ts | 2 +- .../expressions/nullish-assignment.ts | 56 + .../lowering/expressions/object-literals.ts | 172 +- .../lowering/expressions/optional-chains.ts | 23 +- .../lowering/expressions/property-access.ts | 592 +- .../lowering/expressions/relational.ts | 316 +- .../frontend/lowering/expressions/typeof.ts | 54 +- .../lowering/expressions/union-fields.ts | 2 +- .../frontend/lowering/expressions/updates.ts | 43 +- .../src/frontend/lowering/function-context.ts | 22 +- .../src/frontend/lowering/function-returns.ts | 40 +- .../src/frontend/lowering/general-streams.ts | 136 + .../frontend/lowering/generic-functions.ts | 98 +- .../src/frontend/lowering/import-meta.ts | 2 +- .../src/frontend/lowering/intern-tables.ts | 2 +- .../frontend/lowering/js-dynamic-objects.ts | 14 +- .../src/frontend/lowering/json-require.ts | 2 +- .../frontend/lowering/key-order-witness.ts | 20 +- .../src/frontend/lowering/lower-assert.ts | 6 +- .../lowering/lower-async-iteration.ts | 4 +- .../src/frontend/lowering/lower-calls.ts | 538 +- .../src/frontend/lowering/lower-classes.ts | 803 ++- .../src/frontend/lowering/lower-comptime.ts | 119 +- .../src/frontend/lowering/lower-console.ts | 2 +- .../src/frontend/lowering/lower-dgram.ts | 137 +- .../src/frontend/lowering/lower-enums.ts | 125 +- .../frontend/lowering/lower-event-emitter.ts | 142 +- .../src/frontend/lowering/lower-expando.ts | 24 +- .../src/frontend/lowering/lower-exprs.ts | 219 +- .../src/frontend/lowering/lower-functions.ts | 34 +- .../src/frontend/lowering/lower-generators.ts | 10 +- .../frontend/lowering/lower-global-value.ts | 6 +- .../src/frontend/lowering/lower-inspect.ts | 120 +- .../src/frontend/lowering/lower-mixins.ts | 11 +- .../src/frontend/lowering/lower-mock.ts | 77 +- .../src/frontend/lowering/lower-modules.ts | 78 +- .../src/frontend/lowering/lower-namespaces.ts | 63 +- .../frontend/lowering/lower-node-module.ts | 20 +- .../src/frontend/lowering/lower-server.ts | 332 +- .../src/frontend/lowering/lower-stdlib.ts | 83 +- .../src/frontend/lowering/lower-stmts.ts | 714 +- .../src/frontend/lowering/lower-stream.ts | 433 +- .../src/frontend/lowering/lower-test.ts | 4 +- .../src/frontend/lowering/lower-timers.ts | 2 +- .../compiler/src/frontend/lowering/lowerer.ts | 891 ++- .../frontend/lowering/module-member-this.ts | 222 +- .../src/frontend/lowering/nan-coded-reads.ts | 30 +- .../frontend/lowering/narrowing-stability.ts | 21 +- .../src/frontend/lowering/native-ffi.ts | 6 +- .../frontend/lowering/object-factory-new.ts | 2 +- .../src/frontend/lowering/private-brands.ts | 328 + .../lowering/promise-executor-adapter.ts | 102 + .../lowering/require-initialization.ts | 4 +- .../lowering/runtime-optional-bounds.ts | 6 +- .../lowering/runtime-optional-elements.ts | 2 +- .../lowering/runtime-optional-guards.ts | 2 +- .../lowering/runtime-optional-ownership.ts | 4 +- .../lowering/runtime-optional-provenance.ts | 4 +- .../lowering/runtime-optional-records.test.ts | 2 +- .../lowering/runtime-optional-records.ts | 14 +- .../src/frontend/lowering/split-consumers.ts | 2 +- .../lowering/static-function-names.ts | 219 + .../src/frontend/lowering/static-this.ts | 12 +- .../src/frontend/lowering/strict-mode.ts | 34 + .../frontend/lowering/string-index-bounds.ts | 16 +- .../src/frontend/lowering/surfaces.ts | 78 +- .../src/frontend/lowering/symbol-fields.ts | 247 +- .../src/frontend/lowering/symbol-methods.ts | 30 +- .../src/frontend/lowering/thread-slots.ts | 9 +- .../src/frontend/lowering/typed-arguments.ts | 516 ++ .../src/frontend/lowering/var-hoisting.ts | 4 +- .../src/frontend/lowering/width-lift.ts | 1 + packages/compiler/src/frontend/mock-shapes.ts | 19 +- .../frontend/module-source-candidates.test.ts | 3 +- .../npm-static-bundled-chunks.test.ts | 318 + .../src/frontend/npm-static-bundled-chunks.ts | 910 +++ .../src/frontend/npm-static-bundled-cjs.ts | 19 +- .../frontend/npm-static-bundled-esm.test.ts | 230 + .../src/frontend/npm-static-bundled-esm.ts | 826 +++ .../npm-static-cjs-exports-alias.test.ts | 81 + .../frontend/npm-static-cjs-exports-alias.ts | 192 + .../frontend/npm-static-cjs-exports-var.ts | 239 + .../frontend/npm-static-declaration-syntax.ts | 70 +- .../src/frontend/npm-static-rewrite-host.ts | 28 +- .../src/frontend/npm-static-rewrite-syntax.ts | 327 +- .../src/frontend/npm-static-subtyping.ts | 8 +- packages/compiler/src/frontend/npm-static.ts | 151 +- packages/compiler/src/frontend/pipeline.ts | 38 +- packages/compiler/src/frontend/program.ts | 425 +- packages/compiler/src/frontend/resolve.ts | 24 +- .../compiler/src/frontend/services.test.ts | 3 +- packages/compiler/src/frontend/services.ts | 54 +- .../compiler/src/frontend/tier-analysis.ts | 25 +- .../src/frontend/ts7/api-conformance.ts | 297 + .../src/frontend/ts7/ast-guards.generated.ts | 3 +- .../src/frontend/ts7/ast-node.test.ts | 30 +- .../compiler/src/frontend/ts7/ast-node.ts | 50 +- .../src/frontend/ts7/ast-types.generated.ts | 76 +- .../compiler/src/frontend/ts7/ast-types.ts | 7 +- packages/compiler/src/frontend/ts7/ast.ts | 1 + .../src/frontend/ts7/checker-backend.test.ts | 79 + .../src/frontend/ts7/checker-backend.ts | 111 + .../compiler/src/frontend/ts7/checker.test.ts | 10 +- packages/compiler/src/frontend/ts7/checker.ts | 53 +- .../compiler/src/frontend/ts7/diagnostic.ts | 38 + .../src/frontend/ts7/node-functions.ts | 23 + .../compiler/src/frontend/ts7/program-host.ts | 18 +- packages/compiler/src/frontend/ts7/rpc-api.ts | 10 +- .../src/frontend/ts7/semantic-checker.test.ts | 13 +- .../src/frontend/ts7/semantic-checker.ts | 48 +- .../src/frontend/ts7/semantic-model.test.ts | 14 +- .../src/frontend/ts7/semantic-model.ts | 97 +- .../src/frontend/ts7/semantic-types.ts | 29 +- .../compiler/src/frontend/ts7/session.test.ts | 16 +- packages/compiler/src/frontend/ts7/session.ts | 25 +- .../src/frontend/ts7/source-parser.test.ts | 42 +- .../src/frontend/ts7/source-parser.ts | 33 +- packages/compiler/src/frontend/type-mapper.ts | 2060 +++++- .../compiler/src/frontend/worker-target.ts | 3 +- packages/compiler/src/index.ts | 17 + packages/compiler/src/ir/build.ts | 4 +- packages/compiler/src/ir/builtin-effects.ts | 47 + .../compiler/src/ir/builtin-signatures.ts | 256 +- packages/compiler/src/ir/capture-values.ts | 2 +- .../src/ir/function-references.test.ts | 4 + packages/compiler/src/ir/int32-slots.ts | 2 + packages/compiler/src/ir/ir.ts | 77 +- packages/compiler/src/ir/runtime-features.ts | 815 ++- packages/compiler/src/ir/traverse.test.ts | 27 +- packages/compiler/src/ir/unshare.ts | 70 +- packages/compiler/src/ir/validate.ts | 64 +- packages/compiler/src/library/ir-policy.ts | 6 + packages/compiler/src/native/cli.ts | 4 + packages/compiler/src/native/prepare.ts | 4 + .../compiler/src/native/sanitizer.test.ts | 7 + .../compiler/src/native/toolchain.test.ts | 52 +- packages/compiler/src/native/toolchain.ts | 22 +- packages/compiler/src/startup-cache.ts | 1 + packages/compiler/src/wasm/base64.ts | 61 + packages/compiler/src/wasm/binary.ts | 23 +- packages/compiler/src/wasm/discover.ts | 67 +- packages/compiler/src/wasm/module.ts | 676 ++ packages/compiler/src/wasm/translate.test.ts | 247 + packages/compiler/src/wasm/translate.ts | 2296 ++++++ packages/compiler/surface-manifest.json | 58 +- .../test/bytes-field-borrow-emission.test.ts | 94 + .../compiler/test/class-attributes.test.ts | 84 + .../test/class-method-mutations.test.ts | 21 + .../local-class-private-boundaries.test.ts | 63 + .../test/native-callable-values.test.ts | 12 +- .../test/native-class-descriptors.test.ts | 38 +- .../speculative-hoisted-functions.test.ts | 8 +- .../5467-typed-array-loop-versions.ts.json | 6 + .../5468-typed-array-loop-aliasing.ts.json | 6 + ...d-array-loop-initializer-rebinding.ts.json | 6 + .../corpus/5607-string-char-compare.ts.json | 6 + .../corpus/5608-string-char-switch.ts.json | 6 + .../5609-string-switch-optional.ts.json | 6 + .../5730-thread-slots-widened-values.ts.json | 6 + .../5750-string-from-char-codes.ts.json | 6 + .../corpus/5751-string-length-cache.ts.json | 6 + .../tests/corpus/5775-md5-lengths.ts.json | 6 + .../tests/corpus/5779-sha512-lengths.ts.json | 6 + .../5816-fixed-into-unknown-rest.ts.json | 6 + ...7-constructor-function-instanceof.cjs.json | 6 + ...tructor-function-emitter-defaults.cjs.json | 6 + .../main.ts.json | 7 + .../main.ts.json | 7 + .../5876-readline-dispose-instanceof.ts.json | 6 + .../5877-readline-question-query.cjs.json | 6 + .../5878-readline-dynamic-streams.mjs.json | 6 + .../5891-typed-array-field-borrows.ts.json | 6 + .../5916-dyn-error-stack-property.ts.json | 6 + .../corpus/5983-runtime-constructor.ts.json | 6 + .../6004-class-instance-integrity.ts.json | 6 + ...5-class-instance-integrity-sloppy.cjs.json | 6 + .../6055-class-generator-iterables.ts.json | 6 + .../6056-class-symbol-method-calls.ts.json | 6 + .../6058-class-view-asserted-bindings.ts.json | 6 + .../6061-type-only-class-fields/main.ts.json | 7 + .../6063-union-class-field-checks.ts.json | 6 + .../6126-punycode-dependency/main.cjs.json | 2 +- .../corpus/6230-date-parse-legacy.ts.json | 6 + .../corpus/6231-date-local-fields.ts.json | 6 + .../tests/corpus/6232-date-fields-js.cjs.json | 6 + .../6233-object-identity-dynamic.cjs.json | 6 + .../6234-object-prototype-values.cjs.json | 6 + .../corpus/6235-object-identity-typed.ts.json | 6 + .../6236-builtin-function-values.cjs.json | 6 + .../corpus/6341-throw-array-identity.ts.json | 6 + .../6342-throw-builtin-identity.ts.json | 6 + .../corpus/6345-inspect-dyn-holes.ts.json | 6 + .../corpus/6346-inspect-dyn-dataview.ts.json | 6 + .../corpus/6347-dataview-methods-js.cjs.json | 6 + .../corpus/6348-errno-code-own-field.ts.json | 6 + .../corpus/6356-format-integer-dyn.ts.json | 6 + ...0-variadic-wrapper-arguments-apply.ts.json | 6 + .../6381-variadic-wrapper-async.ts.json | 6 + ...6382-variadic-wrapper-methods-this.ts.json | 6 + .../6383-variadic-wrapper-rest-tuple.ts.json | 6 + .../6384-variadic-wrapper-nested.ts.json | 6 + ...ariadic-wrapper-shared-local-names.ts.json | 6 + .../6390-catch-binding-predicate.ts.json | 6 + .../6391-nullish-function-default.ts.json | 6 + ...6392-optional-chain-protocol-calls.ts.json | 6 + .../6393-function-types-any-params.ts.json | 6 + .../6394-in-key-error-subclasses.ts.json | 6 + .../6395-generic-object-entries.ts.json | 6 + .../6396-generic-any-instantiation.ts.json | 6 + .../corpus/6397-event-loop-metrics.ts.json | 6 + .../6398-conditional-fixed-arity.ts.json | 6 + .../6400-dyn-error-stack-property-js.js.json | 6 + .../corpus/6400-override-any-base.ts.json | 6 + .../6401-dyn-error-own-property-names.ts.json | 6 + .../6401-override-param-variance.ts.json | 6 + ...02-dyn-error-own-property-names-js.js.json | 6 + .../corpus/6402-override-js-base/main.ts.json | 7 + .../corpus/6403-error-code-write.ts.json | 6 + .../corpus/6403-error-own-name-order.ts.json | 6 + .../corpus/6404-error-in-and-assign.ts.json | 6 + .../6404-error-own-name-order-js.js.json | 6 + .../6405-wrapped-default-parameters.ts.json | 6 + .../6406-npm-instance-receiver/main.ts.json | 6 + .../tests/corpus/6407-dyn-bytes-in.ts.json | 6 + .../tests/corpus/6408-dyn-bytes-in-js.js.json | 6 + .../corpus/6409-catch-binding-shapes.ts.json | 6 + .../main.ts.json | 6 + .../6410-wrapper-untyped-results.ts.json | 6 + .../main.ts.json | 7 + ...11-union-array-tuple-destructuring.ts.json | 6 + .../main.ts.json | 7 + .../6413-override-js-async-base/main.ts.json | 7 + .../6414-catch-values-roundtrip.ts.json | 6 + .../6415-error-code-dynamic-writes.ts.json | 6 + .../corpus/6416-tls-session-reused.ts.json | 6 + .../6417-object-assign-class-target.ts.json | 6 + .../6418-nullish-assign-index-record.ts.json | 6 + .../corpus/6420-process-send-presence.ts.json | 6 + .../main.ts.json | 6 + .../corpus/6422-process-exitcode-read.ts.json | 6 + .../6423-process-exitcode-default.ts.json | 6 + .../6424-process-exit-keeps-exitcode.ts.json | 6 + .../6425-stream-hook-wider-chunk.ts.json | 6 + .../6426-promise-race-void-entry.ts.json | 6 + .../6427-http-request-options-type.ts.json | 6 + .../6428-promisify-callback-functions.ts.json | 6 + .../tests/corpus/6429-esm-js-dirname.mjs.json | 6 + .../main.ts.json | 6 + .../6431-cjs-exports-rebinding/main.cjs.json | 8 + .../corpus/6440-http-socket-path.ts.json | 6 + .../6441-http-request-options-record.ts.json | 6 + .../6442-https-request-options-record.ts.json | 6 + .../6443-net-lookup-http-close-text.ts.json | 6 + .../6445-rest-function-fixed-slot.ts.json | 6 + ...6-rest-function-nested-declaration.ts.json | 6 + .../6450-builtin-base-class-values.ts.json | 6 + .../6450-promise-race-never-entry.ts.json | 6 + .../6451-array-length-fill-spread.ts.json | 6 + ...51-builtin-base-class-expressions.cjs.json | 6 + ...6453-constructor-function-emitter.cjs.json | 6 + ...constructor-function-stream-bases.cjs.json | 6 + .../6460-in-union-optional-slots.ts.json | 6 + ...1-predicate-intersection-narrowing.ts.json | 6 + .../6462-assertion-attached-members.ts.json | 6 + ...463-node-readable-stream-producers.ts.json | 6 + ...464-node-writable-stream-producers.ts.json | 6 + .../6465-node-stream-js-boundary/main.ts.json | 7 + ...ional-members-over-index-signature.ts.json | 6 + .../6466-never-assertion-argument.ts.json | 6 + .../6466-node-stdio-stream-surface.ts.json | 6 + .../6467-fromentries-record-assertion.ts.json | 6 + .../main.ts.json | 7 + ...468-generic-unknown-record-entries.ts.json | 6 + .../6468-node-stdio-writable-slot.ts.json | 6 + .../main.ts.json | 7 + .../corpus/6470-any-destructuring.ts.json | 6 + .../6470-promise-reject-async-return.ts.json | 6 + ...stream-transform-any-data-callback.ts.json | 6 + .../corpus/6471-any-method-chains.ts.json | 6 + .../6471-stream-data-buffer-or-string.ts.json | 6 + ...-union-literal-results-into-record.ts.json | 6 + .../corpus/6472-asserted-member-read.ts.json | 6 + .../6472-child-data-unknown-chunk.ts.json | 6 + .../6472-namespace-destructuring/main.ts.json | 7 + .../corpus/6473-any-parameter-values.ts.json | 6 + ...3-unknown-spread-into-index-record.ts.json | 6 + .../6474-async-any-callbacks/main.ts.json | 6 + .../6474-index-record-spread-literals.ts.json | 6 + .../6475-dynamic-spread-literals.ts.json | 6 + .../main.ts.json | 6 + .../corpus/6480-boolean-callbacks.ts.json | 6 + .../6480-catch-typeof-untyped-throws.ts.json | 6 + ...-generic-callback-extended-results.ts.json | 6 + .../main.ts.json | 7 + .../corpus/6481-enum-runtime-keys.ts.json | 6 + .../main.ts.json | 7 + .../6482-node-stream-dyn-helpers/main.ts.json | 7 + .../corpus/6482-promise-all-tuples.ts.json | 6 + .../6483-nullable-regex-receivers.ts.json | 6 + .../corpus/6490-events-once-signal.ts.json | 6 + .../main.ts.json | 6 + .../6490-literal-key-field-updates.ts.json | 6 + .../corpus/6491-catch-any-bindings.ts.json | 6 + .../corpus/6491-events-once-streams.ts.json | 6 + .../corpus/6491-index-record-updates.ts.json | 6 + .../corpus/6492-promise-race-combined.ts.json | 6 + .../6493-stream-function-callbacks.ts.json | 6 + .../main.ts.json | 9 + .../main.ts.json | 7 + ...560-constructor-function-readable.cjs.json | 6 + ...561-constructor-function-writable.cjs.json | 6 + ...62-constructor-function-transform.cjs.json | 6 + ...onstructor-function-stream-errors.cjs.json | 6 + ...ructor-function-stream-late-hooks.cjs.json | 6 + .../tests/corpus/761-any-objects.ts.json | 6 + .../cjs-class-hierarchy-npm/main.ts.json | 6 + .../corpus/cjs-open-options-npm/main.ts.json | 6 + .../corpus/cjs-strict-arguments.cjs.json | 6 + .../cjs-symbol-key-collisions/main.js.json | 10 + .../corpus/cjs-symbol-key-table/main.js.json | 9 + .../corpus/cjs-unmapped-arguments.cjs.json | 6 + .../code-generation-disallowed.cjs.json | 6 + .../corpus/code-generation-disallowed.ts.json | 6 + .../corpus/comma-null-union-return.cjs.json | 6 + .../tests/corpus/dns-promises-lookup.ts.json | 6 + .../corpus/dns-promises-untyped.cjs.json | 6 + .../function-local-detached-methods.js.json | 6 + .../function-local-detached-methods.ts.json | 6 + ...function-local-private-evaluations.js.json | 6 + .../function-local-private-fields.js.json | 6 + .../corpus/graceful-fs-alias/main.cjs.json | 6 + .../local-class-detached-method.ts.json | 6 + .../object-literal-accessors-dyn.ts.json | 6 + .../private-in-checked-primitives.js.json | 6 + .../required-module-truthiness.cjs.json | 6 + .../tests/corpus/stream-web-readable.ts.json | 6 + .../tests/corpus/stream-web-require.cjs.json | 6 + .../strict-arguments-parameters.cjs.json | 6 + .../corpus/temporal-absent-probe.js.json | 6 + .../tests/corpus/temporal-absent.ts.json | 6 + .../tests/corpus/v8-heap-statistics.ts.json | 6 + .../tests/corpus/v8-require-optional.cjs.json | 6 + .../tests/corpus/webassembly-api.mjs.json | 6 + .../corpus/webassembly-dylink/main.mjs.json | 7 + .../webassembly-externref-lifetimes.mjs.json | 6 + .../tests/corpus/webassembly-jsapi.mjs.json | 6 + .../corpus/webassembly-lifetimes.mjs.json | 6 + .../webassembly-spec-control/main.mjs.json | 8 + .../webassembly-spec-float/main.mjs.json | 8 + .../webassembly-spec-imports/main.mjs.json | 8 + .../corpus/webassembly-spec-int/main.mjs.json | 8 + .../webassembly-spec-memory/main.mjs.json | 8 + .../webassembly-spec-table/main.mjs.json | 8 + .../tests/corpus/webassembly-typed.ts.json | 6 + .../tests/corpus/webassembly-worker.ts.json | 6 + .../corpus/webassembly-wpt-jsapi.mjs.json | 6 + .../boot-path-node-surface-fences.ts.json | 6 + ...builtin-base-class-value-arguments.ts.json | 6 + .../builtin-constructor-members.ts.json | 6 + .../cjs-member-alias-writes/main.ts.json | 6 + .../class-field-accessor-descriptor.ts.json | 6 + .../fromentries-aliased-entries.ts.json | 6 + .../tests/diagnostics/imports.ts.json | 2 +- ...nferred-callback-drops-conditional.ts.json | 6 + .../inferred-callback-drops-satisfies.ts.json | 6 + .../inferred-callback-drops.ts.json | 6 + .../diagnostics/module-out-of-scope.ts.json | 68 +- .../object-assign-class-copy.ts.json | 6 + .../object-literal-accessor-values.ts.json | 6 + .../pkg-import-fences/main.ts.json | 6 +- .../retired-engine-761-any-objects.ts.json | 6 - .../stream-completion-callback-error.ts.json | 6 + .../variadic-wrapper-fences.ts.json | 6 + .../node-types/child-stdin-fences.ts.json | 6 - .../node-types/child-stdin-streams.ts.json | 6 + .../node-types/event-loop-metrics.ts.json | 6 + .../events-once-signal-race.ts.json | 6 + .../node-types/fetch-init-members.ts.json | 6 + .../node-types/http-socket-path.ts.json | 6 + .../fixtures/node-types/import-fences.ts.json | 8 +- .../lookup-and-header-entries.ts.json | 6 + .../node-types/node-stream-interfaces.ts.json | 6 + .../process-versions-absent.ts.json | 6 + .../promisify-callback-functions.ts.json | 6 + .../node-types/stream-construction.ts.json | 6 + .../node-types/stream-subclass-hooks.ts.json | 6 + .../fixtures/node-types/timeout-type.ts.json | 6 + .../node-types/tls-session-timings.ts.json | 6 + .../cases/2558-bundled-chunks/main.ts.json | 6 + .../npm/cases/2559-cjs-cycle/main.ts.json | 6 + .../2560-cjs-cycle-partner-first/main.ts.json | 6 + .../2561-cjs-cycle-early-read/main.ts.json | 6 + .../npm/cases/2562-subpath-leaf/main.ts.json | 6 + .../cases/2563-bundled-esm-inits/main.ts.json | 6 + .../2564-chunk-helper-shadowing/main.ts.json | 6 + .../2565-chunk-shared-function/main.ts.json | 6 + .../2566-cjs-cycle-indirect-call/main.ts.json | 6 + .../2567-esm-namespace-this/main.ts.json | 6 + .../main.ts.json | 6 + .../npm/cases/2580-lazy-barrel/main.ts.json | 6 + .../2581-lazy-barrel-require/main.ts.json | 6 + .../2582-npm-alias-two-releases/main.ts.json | 6 + .../2583-cjs-function-exports/main.ts.json | 6 + .../2584-cjs-conditional-exports/main.ts.json | 6 + .../npm/cases/cjs-exports-var/main.ts.json | 6 + .../npm/cases/tsc-default-object/main.ts.json | 6 + packages/compiler/test/ts7/parity.test.ts | 7 +- packages/compiler/test/ts7/rpc-api.test.ts | 39 +- packages/experimental/package.json | 2 +- packages/llvm-darwin-arm64/package.json | 2 +- packages/llvm-darwin-x64/package.json | 2 +- packages/llvm-linux-arm64-gnu/package.json | 2 +- packages/llvm-linux-arm64-musl/package.json | 2 +- packages/llvm-linux-x64-gnu/package.json | 2 +- packages/llvm-linux-x64-musl/package.json | 2 +- packages/llvm-win32-x64-msvc/package.json | 2 +- packages/runtime-android-arm64/package.json | 2 +- packages/runtime-darwin-arm64/package.json | 2 +- .../runtime-pack-matrix.mjs | 147 +- packages/runtime-darwin-x64/package.json | 2 +- packages/runtime-ios-arm64/package.json | 2 +- .../runtime-ios-simulator-arm64/package.json | 2 +- packages/runtime-linux-arm64-gnu/package.json | 2 +- .../runtime-linux-arm64-musl/package.json | 2 +- packages/runtime-linux-x64-gnu/package.json | 2 +- packages/runtime-linux-x64-musl/package.json | 2 +- .../runtime-pack-matrix.mjs | 38 +- packages/runtime-wasm32-wasi/package.json | 2 +- .../runtime-wasm32-wasi/scripts/build.mjs | 2 + packages/runtime-win32-x64-msvc/package.json | 2 +- packages/runtime/package.json | 2 +- packages/runtime/src/scr_alloc.c | 102 +- packages/runtime/src/scr_alloc.test.c | 87 +- packages/runtime/src/scr_async.c | 63 +- packages/runtime/src/scr_async.h | 14 + packages/runtime/src/scr_async_dyn.c | 15 +- packages/runtime/src/scr_child.c | 29 +- packages/runtime/src/scr_child.h | 25 +- packages/runtime/src/scr_crypto.h | 1 + packages/runtime/src/scr_cycle.c | 32 +- packages/runtime/src/scr_dgram.h | 6 + packages/runtime/src/scr_dns_promises.c | 312 + packages/runtime/src/scr_dyn.h | 87 +- packages/runtime/src/scr_dyn_builtin_class.c | 1596 +++++ packages/runtime/src/scr_dyn_builtin_stream.c | 1717 +++++ packages/runtime/src/scr_dyn_invoke.c | 368 +- packages/runtime/src/scr_error.c | 6 + packages/runtime/src/scr_error.h | 18 + packages/runtime/src/scr_events_emitter.c | 40 +- packages/runtime/src/scr_events_emitter.h | 15 + packages/runtime/src/scr_events_once_signal.c | 303 + packages/runtime/src/scr_exception.c | 7 +- packages/runtime/src/scr_exception.h | 7 +- packages/runtime/src/scr_fetch.c | 21 +- packages/runtime/src/scr_fs_stream.c | 1 + packages/runtime/src/scr_http.c | 505 +- packages/runtime/src/scr_http.h | 34 + packages/runtime/src/scr_inspect.c | 87 +- packages/runtime/src/scr_json.c | 640 +- packages/runtime/src/scr_lib.c | 1222 +++- packages/runtime/src/scr_lib.h | 20 + packages/runtime/src/scr_mailbox.c | 7 +- packages/runtime/src/scr_message.c | 17 +- packages/runtime/src/scr_net.c | 366 +- packages/runtime/src/scr_net.h | 10 + packages/runtime/src/scr_number.h | 15 +- packages/runtime/src/scr_perf_loop.c | 327 + packages/runtime/src/scr_rc.h | 4 + packages/runtime/src/scr_readline.c | 147 +- packages/runtime/src/scr_readline.h | 16 +- packages/runtime/src/scr_readline_internal.h | 14 + packages/runtime/src/scr_readline_tty.c | 8 +- packages/runtime/src/scr_runtime.h | 2 + packages/runtime/src/scr_stream.c | 147 +- packages/runtime/src/scr_stream.h | 21 + packages/runtime/src/scr_stream_dyn.c | 389 + packages/runtime/src/scr_string.c | 49 +- packages/runtime/src/scr_test.c | 20 +- packages/runtime/src/scr_tls.c | 68 + packages/runtime/src/scr_wasm.c | 2553 +++++++ packages/runtime/src/scr_wasm.h | 31 + packages/runtime/src/scr_wasm_abi.h | 221 + packages/runtime/src/scr_worker_events.c | 5 +- packages/runtime/test/sha-kernels.test.ts | 4 +- packages/runtime/test/test_cycle.c | 30 + packages/runtime/test/test_sha_kernels.c | 100 +- packages/threads/package.json | 2 +- packages/types/package.json | 2 +- pnpm-lock.yaml | 86 + pnpm-workspace.yaml | 8 + scripts/build-native-cli.mts | 4 +- scripts/checker-identity.mjs | 156 + scripts/ci-lanes.mjs | 23 +- scripts/ci-test-plan.mjs | 40 +- scripts/generate-ts7-ast-schema.mjs | 7 +- scripts/generate-ts7-ast-views.mjs | 15 +- scripts/native-cli-assets.mts | 20 +- scripts/package-native-cli.mjs | 1 + scripts/verify-native-cli.mjs | 1 + scripts/zero-cost-compare.mjs | 33 + scripts/zero-cost-gate.mjs | 32 +- tests/corpus/1052-comptime-records.ts | 17 + .../corpus/5467-typed-array-loop-versions.ts | 298 + .../corpus/5468-typed-array-loop-aliasing.ts | 107 + ...-typed-array-loop-initializer-rebinding.ts | 46 + tests/corpus/5607-string-char-compare.ts | 37 + tests/corpus/5608-string-char-switch.ts | 95 + tests/corpus/5609-string-switch-optional.ts | 57 + .../5730-thread-slots-widened-values.ts | 45 + tests/corpus/5750-string-from-char-codes.ts | 27 + tests/corpus/5751-string-length-cache.ts | 36 + tests/corpus/5775-md5-lengths.ts | 34 + tests/corpus/5779-sha512-lengths.ts | 35 + tests/corpus/5816-fixed-into-unknown-rest.ts | 39 + .../5817-constructor-function-instanceof.cjs | 15 + ...-constructor-function-emitter-defaults.cjs | 53 + .../5819-node-stream-dyn-direct-pipe/main.ts | 59 + .../sources.js | 45 + .../main.ts | 35 + .../sources.js | 39 + .../5876-readline-dispose-instanceof.ts | 28 + tests/corpus/5877-readline-question-query.cjs | 51 + .../corpus/5878-readline-dynamic-streams.mjs | 41 + .../corpus/5891-typed-array-field-borrows.ts | 68 + tests/corpus/5916-dyn-error-stack-property.ts | 147 + tests/corpus/5983-runtime-constructor.ts | 48 + tests/corpus/6004-class-instance-integrity.ts | 169 + .../6005-class-instance-integrity-sloppy.cjs | 40 + .../corpus/6055-class-generator-iterables.ts | 86 + .../corpus/6056-class-symbol-method-calls.ts | 78 + .../6058-class-view-asserted-bindings.ts | 78 + .../6061-type-only-class-fields/emitted.ts | 23 + .../6061-type-only-class-fields/holder.ts | 13 + .../6061-type-only-class-fields/main.ts | 17 + tests/corpus/6063-union-class-field-checks.ts | 65 + tests/corpus/6230-date-parse-legacy.ts | 113 + tests/corpus/6231-date-local-fields.ts | 36 + tests/corpus/6232-date-fields-js.cjs | 50 + tests/corpus/6233-object-identity-dynamic.cjs | 105 + tests/corpus/6234-object-prototype-values.cjs | 25 + tests/corpus/6235-object-identity-typed.ts | 24 + tests/corpus/6236-builtin-function-values.cjs | 55 + tests/corpus/6341-throw-array-identity.ts | 56 + tests/corpus/6342-throw-builtin-identity.ts | 56 + tests/corpus/6345-inspect-dyn-holes.ts | 29 + tests/corpus/6346-inspect-dyn-dataview.ts | 41 + tests/corpus/6347-dataview-methods-js.cjs | 25 + tests/corpus/6348-errno-code-own-field.ts | 46 + tests/corpus/6356-format-integer-dyn.ts | 9 + .../6380-variadic-wrapper-arguments-apply.ts | 77 + tests/corpus/6381-variadic-wrapper-async.ts | 81 + .../6382-variadic-wrapper-methods-this.ts | 46 + .../6383-variadic-wrapper-rest-tuple.ts | 54 + tests/corpus/6384-variadic-wrapper-nested.ts | 37 + ...385-variadic-wrapper-shared-local-names.ts | 75 + tests/corpus/6390-catch-binding-predicate.ts | 42 + tests/corpus/6391-nullish-function-default.ts | 13 + .../6392-optional-chain-protocol-calls.ts | 47 + .../corpus/6393-function-types-any-params.ts | 8 + tests/corpus/6394-in-key-error-subclasses.ts | 31 + tests/corpus/6395-generic-object-entries.ts | 7 + .../corpus/6396-generic-any-instantiation.ts | 18 + tests/corpus/6397-event-loop-metrics.ts | 70 + tests/corpus/6398-conditional-fixed-arity.ts | 66 + .../6400-dyn-error-stack-property-js.js | 148 + tests/corpus/6400-override-any-base.ts | 35 + .../6401-dyn-error-own-property-names.ts | 44 + tests/corpus/6401-override-param-variance.ts | 33 + .../6402-dyn-error-own-property-names-js.js | 44 + tests/corpus/6402-override-js-base/main.ts | 26 + tests/corpus/6402-override-js-base/pool.js | 17 + tests/corpus/6403-error-code-write.ts | 39 + tests/corpus/6403-error-own-name-order.ts | 108 + tests/corpus/6404-error-in-and-assign.ts | 144 + tests/corpus/6404-error-own-name-order-js.js | 94 + .../corpus/6405-wrapped-default-parameters.ts | 67 + .../corpus/6406-npm-instance-receiver/main.ts | 7 + .../node_modules/corked-writer/index.d.ts | 4 + .../node_modules/corked-writer/index.js | 45 + .../node_modules/corked-writer/package.json | 1 + tests/corpus/6407-dyn-bytes-in.ts | 28 + tests/corpus/6408-dyn-bytes-in-js.js | 35 + tests/corpus/6409-catch-binding-shapes.ts | 69 + .../6410-json-mixed-scalar-tables/extra.json | 1 + .../6410-json-mixed-scalar-tables/main.ts | 26 + .../node_modules/code-table/index.d.ts | 2 + .../node_modules/code-table/index.js | 10 + .../node_modules/code-table/package.json | 1 + .../node_modules/code-table/table.json | 1 + .../node_modules/range-map/index.d.ts | 2 + .../node_modules/range-map/index.js | 37 + .../node_modules/range-map/package.json | 1 + .../node_modules/range-map/table.json | 1 + tests/corpus/6410-wrapper-untyped-results.ts | 50 + .../issues.js | 27 + .../6411-prototype-reset-constructors/main.ts | 33 + .../6411-union-array-tuple-destructuring.ts | 42 + .../6412-js-error-tostring-override/errors.js | 22 + .../6412-js-error-tostring-override/main.ts | 9 + .../6413-override-js-async-base/lock.js | 22 + .../6413-override-js-async-base/main.ts | 30 + tests/corpus/6414-catch-values-roundtrip.ts | 114 + .../corpus/6415-error-code-dynamic-writes.ts | 64 + tests/corpus/6416-tls-session-reused.ts | 33 + .../corpus/6417-object-assign-class-target.ts | 36 + .../6418-nullish-assign-index-record.ts | 29 + tests/corpus/6420-process-send-presence.ts | 24 + .../6421-process-send-presence-fork/main.ts | 13 + .../6421-process-send-presence-fork/worker.ts | 6 + tests/corpus/6422-process-exitcode-read.ts | 22 + tests/corpus/6423-process-exitcode-default.ts | 10 + .../6424-process-exit-keeps-exitcode.ts | 5 + tests/corpus/6425-stream-hook-wider-chunk.ts | 46 + tests/corpus/6426-promise-race-void-entry.ts | 31 + .../corpus/6427-http-request-options-type.ts | 10 + .../6428-promisify-callback-functions.ts | 80 + tests/corpus/6429-esm-js-dirname.mjs | 15 + .../main.ts | 19 + .../node_modules/metrics-client/index.d.ts | 14 + .../node_modules/metrics-client/index.js | 1 + .../node_modules/metrics-client/lib/client.js | 50 + .../node_modules/metrics-client/package.json | 1 + .../6431-cjs-exports-rebinding/extended.cjs | 3 + .../corpus/6431-cjs-exports-rebinding/lib.cjs | 10 + .../6431-cjs-exports-rebinding/main.cjs | 9 + tests/corpus/6440-http-socket-path.ts | 109 + .../6441-http-request-options-record.ts | 79 + .../6442-https-request-options-record.ts | 61 + .../corpus/6443-net-lookup-http-close-text.ts | 75 + tests/corpus/6445-rest-function-fixed-slot.ts | 49 + .../6446-rest-function-nested-declaration.ts | 26 + .../corpus/6450-builtin-base-class-values.ts | 63 + tests/corpus/6450-promise-race-never-entry.ts | 22 + tests/corpus/6451-array-length-fill-spread.ts | 15 + .../6451-builtin-base-class-expressions.cjs | 46 + .../6453-constructor-function-emitter.cjs | 114 + ...6454-constructor-function-stream-bases.cjs | 65 + tests/corpus/6460-in-union-optional-slots.ts | 53 + .../6461-predicate-intersection-narrowing.ts | 70 + .../corpus/6462-assertion-attached-members.ts | 58 + .../6463-node-readable-stream-producers.ts | 75 + .../6464-node-writable-stream-producers.ts | 68 + .../6465-node-stream-js-boundary/consume.js | 12 + .../6465-node-stream-js-boundary/main.ts | 33 + ...5-optional-members-over-index-signature.ts | 73 + tests/corpus/6466-never-assertion-argument.ts | 31 + .../corpus/6466-node-stdio-stream-surface.ts | 45 + .../6467-fromentries-record-assertion.ts | 43 + .../6467-node-stream-untyped-sources/main.ts | 58 + .../sources.js | 20 + .../6468-generic-unknown-record-entries.ts | 33 + tests/corpus/6468-node-stdio-writable-slot.ts | 49 + .../6469-node-stdio-writable-js-pipe/main.ts | 36 + .../6469-node-stdio-writable-js-pipe/pipe.js | 4 + tests/corpus/6470-any-destructuring.ts | 124 + .../6470-promise-reject-async-return.ts | 17 + ...6470-stream-transform-any-data-callback.ts | 60 + tests/corpus/6471-any-method-chains.ts | 24 + .../6471-stream-data-buffer-or-string.ts | 47 + .../6471-union-literal-results-into-record.ts | 37 + tests/corpus/6472-asserted-member-read.ts | 16 + tests/corpus/6472-child-data-unknown-chunk.ts | 87 + .../6472-namespace-destructuring/lib.ts | 21 + .../6472-namespace-destructuring/main.ts | 29 + .../6472-namespace-destructuring/shape.ts | 7 + tests/corpus/6473-any-parameter-values.ts | 23 + .../6473-unknown-spread-into-index-record.ts | 21 + tests/corpus/6474-async-any-callbacks/main.ts | 41 + .../node_modules/retry-lite/index.d.ts | 15 + .../node_modules/retry-lite/index.js | 36 + .../node_modules/retry-lite/package.json | 6 + .../6474-async-any-callbacks/tsconfig.json | 3 + .../6474-index-record-spread-literals.ts | 59 + tests/corpus/6475-dynamic-spread-literals.ts | 63 + .../6475-js-promise-resolve-adoption/main.ts | 23 + .../node_modules/calls/index.d.ts | 4 + .../node_modules/calls/index.js | 21 + .../node_modules/calls/package.json | 6 + tests/corpus/6480-boolean-callbacks.ts | 34 + .../6480-catch-typeof-untyped-throws.ts | 40 + .../6480-generic-callback-extended-results.ts | 41 + .../6480-node-stream-dyn-readable/main.ts | 72 + .../6480-node-stream-dyn-readable/sources.js | 138 + tests/corpus/6481-enum-runtime-keys.ts | 29 + .../6481-node-stream-dyn-writable/main.ts | 67 + .../6481-node-stream-dyn-writable/sources.js | 49 + .../6482-node-stream-dyn-helpers/main.ts | 98 + .../6482-node-stream-dyn-helpers/sources.js | 71 + tests/corpus/6482-promise-all-tuples.ts | 97 + tests/corpus/6483-nullable-regex-receivers.ts | 23 + tests/corpus/6490-events-once-signal.ts | 120 + .../6490-js-named-promise-executor/main.ts | 22 + .../node_modules/later/index.d.ts | 1 + .../node_modules/later/index.js | 31 + .../node_modules/later/package.json | 6 + .../tsconfig.json | 3 + .../corpus/6490-literal-key-field-updates.ts | 132 + tests/corpus/6491-catch-any-bindings.ts | 34 + tests/corpus/6491-events-once-streams.ts | 87 + tests/corpus/6491-index-record-updates.ts | 44 + tests/corpus/6492-promise-race-combined.ts | 72 + .../corpus/6493-stream-function-callbacks.ts | 66 + .../lib/date.js | 8 + .../lib/error.js | 7 + .../lib/index.js | 14 + .../6494-default-export-class-members/main.ts | 15 + .../package.json | 5 + .../6494-events-once-dyn-stream-slot/main.ts | 59 + .../sources.js | 21 + .../6560-constructor-function-readable.cjs | 121 + .../6561-constructor-function-writable.cjs | 141 + .../6562-constructor-function-transform.cjs | 143 + ...563-constructor-function-stream-errors.cjs | 137 + ...constructor-function-stream-late-hooks.cjs | 117 + .../761-any-objects.ts} | 6 +- tests/corpus/cjs-class-hierarchy-npm/main.ts | 58 + .../node_modules/dispatchkit/index.d.ts | 27 + .../node_modules/dispatchkit/index.js | 12 + .../node_modules/dispatchkit/lib/agent.js | 18 + .../node_modules/dispatchkit/lib/describe.js | 9 + .../dispatchkit/lib/dispatcher-base.js | 48 + .../dispatchkit/lib/dispatcher.js | 15 + .../node_modules/dispatchkit/lib/pool.js | 20 + .../node_modules/dispatchkit/lib/symbols.js | 11 + .../node_modules/dispatchkit/package.json | 6 + tests/corpus/cjs-open-options-npm/main.ts | 12 + .../node_modules/optkit/index.d.ts | 11 + .../node_modules/optkit/index.js | 21 + .../node_modules/optkit/package.json | 6 + tests/corpus/cjs-strict-arguments.cjs | 43 + .../cjs-symbol-key-collisions/client.js | 16 + .../cjs-symbol-key-collisions/core-symbols.js | 6 + .../corpus/cjs-symbol-key-collisions/main.js | 11 + .../cjs-symbol-key-collisions/mock-client.js | 24 + .../cjs-symbol-key-collisions/mock-symbols.js | 7 + .../cjs-symbol-key-collisions/package.json | 3 + tests/corpus/cjs-symbol-key-table/main.js | 19 + .../corpus/cjs-symbol-key-table/package.json | 3 + tests/corpus/cjs-symbol-key-table/queue.js | 37 + tests/corpus/cjs-symbol-key-table/symbols.js | 11 + .../cjs-symbol-key-table/upper-queue.js | 12 + tests/corpus/cjs-unmapped-arguments.cjs | 28 + tests/corpus/code-generation-disallowed.cjs | 36 + tests/corpus/code-generation-disallowed.ts | 44 + tests/corpus/comma-null-union-return.cjs | 15 + tests/corpus/dns-promises-lookup.ts | 40 + tests/corpus/dns-promises-untyped.cjs | 34 + .../corpus/function-local-detached-methods.js | 22 + .../corpus/function-local-detached-methods.ts | 19 + .../function-local-private-evaluations.js | 172 + tests/corpus/function-local-private-fields.js | 48 + tests/corpus/graceful-fs-alias/main.cjs | 34 + .../node_modules/graceful-fs/LICENSE | 15 + .../node_modules/graceful-fs/clone.js | 23 + .../node_modules/graceful-fs/graceful-fs.js | 448 ++ .../graceful-fs/legacy-streams.js | 118 + .../node_modules/graceful-fs/package.json | 53 + .../node_modules/graceful-fs/polyfills.js | 355 + tests/corpus/local-class-detached-method.ts | 16 + tests/corpus/object-literal-accessors-dyn.ts | 31 + tests/corpus/private-in-checked-primitives.js | 28 + tests/corpus/required-module-truthiness.cjs | 10 + tests/corpus/stream-web-readable.ts | 22 + tests/corpus/stream-web-require.cjs | 10 + tests/corpus/strict-arguments-parameters.cjs | 15 + tests/corpus/temporal-absent-probe.js | 17 + tests/corpus/temporal-absent.ts | 35 + tests/corpus/v8-heap-statistics.ts | 16 + tests/corpus/v8-require-optional.cjs | 18 + tests/corpus/webassembly-api.mjs | 179 + .../webassembly-dylink/LICENSE.quickjs-wasi | 21 + tests/corpus/webassembly-dylink/extensions.js | 372 + tests/corpus/webassembly-dylink/main.mjs | 101 + .../webassembly-externref-lifetimes.mjs | 44 + tests/corpus/webassembly-feature-probe.js | 5 +- tests/corpus/webassembly-jsapi.mjs | 356 + tests/corpus/webassembly-lifetimes.mjs | 43 + .../corpus/webassembly-spec-control/data.mjs | 48 + .../corpus/webassembly-spec-control/main.mjs | 5 + tests/corpus/webassembly-spec-float/data.mjs | 113 + tests/corpus/webassembly-spec-float/main.mjs | 5 + .../corpus/webassembly-spec-imports/data.mjs | 137 + .../corpus/webassembly-spec-imports/main.mjs | 5 + tests/corpus/webassembly-spec-int/data.mjs | 33 + tests/corpus/webassembly-spec-int/main.mjs | 5 + tests/corpus/webassembly-spec-memory/data.mjs | 175 + tests/corpus/webassembly-spec-memory/main.mjs | 5 + tests/corpus/webassembly-spec-table/data.mjs | 205 + tests/corpus/webassembly-spec-table/main.mjs | 5 + tests/corpus/webassembly-typed.ts | 51 + tests/corpus/webassembly-worker.ts | 68 + tests/corpus/webassembly-wpt-jsapi.mjs | 1117 +++ tests/coverage-fixtures/divergences.ts | 2 +- tests/coverage-fixtures/wasm/ext.so | Bin 0 -> 36 bytes tests/coverage-fixtures/wasm/glue.js | 9 + tests/coverage-fixtures/wasm/main.ts | 13 +- tests/coverage-fixtures/wasm/native.so | Bin 0 -> 25 bytes tests/diagnostics/arrow-bind.ts | 3 +- .../boot-path-node-surface-fences.ts | 23 + .../builtin-base-class-value-arguments.ts | 14 + .../builtin-constructor-members.ts | 14 + tests/diagnostics/child-process.ts | 10 + .../cjs-member-alias-writes/main.ts | 6 + .../node_modules/metrics/index.d.ts | 6 + .../node_modules/metrics/index.js | 3 + .../node_modules/metrics/lib.js | 8 + .../node_modules/metrics/package.json | 6 + .../class-field-accessor-descriptor.ts | 14 + tests/diagnostics/comptime.ts | 11 + tests/diagnostics/events-once-fences.ts | 9 +- .../fromentries-aliased-entries.ts | 13 + .../inferred-callback-drops-conditional.ts | 12 + .../inferred-callback-drops-satisfies.ts | 13 + tests/diagnostics/inferred-callback-drops.ts | 13 + tests/diagnostics/json-shape/main.ts | 8 +- tests/diagnostics/json-shape/nulled.json | 2 +- tests/diagnostics/local-class-boundaries.ts | 2 +- .../local-class-type-query-brand.ts | 6 +- tests/diagnostics/maybe-promise-fences.ts | 5 - tests/diagnostics/mock-fences.ts | 12 +- tests/diagnostics/module-out-of-scope.ts | 14 +- tests/diagnostics/object-assign-class-copy.ts | 11 + .../object-literal-accessor-values.ts | 21 + tests/diagnostics/promise-all-fences.ts | 17 +- tests/diagnostics/stdlib-fence.ts | 2 +- .../stream-completion-callback-error.ts | 11 + tests/diagnostics/thread-slots-fences.ts | 5 + tests/diagnostics/variadic-wrapper-fences.ts | 28 + tests/diagnostics/webassembly-fences.ts | 39 +- tests/fixtures/esm-path-globals/lib/helper.ts | 2 + tests/fixtures/esm-path-globals/main.ts | 12 + .../node-test/cases/mock-fn-default.ts | 72 + .../fixtures/node-test/cases/thrown-values.ts | 18 +- tests/fixtures/node-types/README.md | 1 + .../fixtures/node-types/child-stdin-fences.ts | 6 - .../node-types/child-stdin-streams.ts | 40 + .../fixtures/node-types/event-loop-metrics.ts | 49 + .../node-types/events-once-signal-race.ts | 86 + .../fixtures/node-types/fetch-init-members.ts | 68 + tests/fixtures/node-types/http-socket-path.ts | 80 + tests/fixtures/node-types/import-fences.ts | 8 +- .../node-types/lookup-and-header-entries.ts | 47 + .../node-types/node-stream-interfaces.ts | 61 + .../node-types/process-versions-absent.ts | 35 + .../promisify-callback-functions.ts | 67 + .../node-types/stream-construction.ts | 121 + .../node-types/stream-subclass-hooks.ts | 46 + tests/fixtures/node-types/timeout-type.ts | 30 + .../node-types/tls-session-timings.ts | 31 + .../node_modules/@types/twinstale/index.d.ts | 1 + .../@types/twinstale/package.json | 5 + .../node_modules/twinstale/index.js | 7 + .../node_modules/twinstale/package.json | 5 + tests/fixtures/npm-static/twinstale-cli.ts | 6 + .../npm/cases/2558-bundled-chunks/main.ts | 29 + .../fixtures/npm/cases/2559-cjs-cycle/main.ts | 11 + .../2560-cjs-cycle-partner-first/main.ts | 11 + .../cases/2561-cjs-cycle-early-read/main.ts | 6 + .../npm/cases/2562-subpath-leaf/main.ts | 10 + .../npm/cases/2563-bundled-esm-inits/main.ts | 16 + .../cases/2564-chunk-helper-shadowing/main.ts | 7 + .../cases/2565-chunk-shared-function/main.ts | 7 + .../2566-cjs-cycle-indirect-call/main.ts | 7 + .../npm/cases/2567-esm-namespace-this/main.ts | 7 + .../2571-cjs-cycle-stored-function/main.ts | 8 + .../npm/cases/2580-lazy-barrel/main.ts | 19 + .../cases/2581-lazy-barrel-require/main.ts | 9 + .../cases/2582-npm-alias-two-releases/main.ts | 7 + .../node_modules/gtroute | 1 + .../cases/2583-cjs-function-exports/main.ts | 12 + .../2584-cjs-conditional-exports/main.ts | 7 + .../npm/cases/cjs-exports-var/main.ts | 16 + .../npm/cases/tsc-default-object/main.ts | 7 + .../npm/node_modules/exportsvar/index.d.ts | 8 + .../npm/node_modules/exportsvar/lib/ip.js | 49 + .../npm/node_modules/exportsvar/package.json | 6 + .../npm/node_modules/gtchunk/dist/index.d.ts | 4 + .../npm/node_modules/gtchunk/dist/index.js | 132 + .../npm/node_modules/gtchunk/package.json | 10 + .../node_modules/gtchunk17/dist/index.d.ts | 4 + .../npm/node_modules/gtchunk17/dist/index.js | 132 + .../npm/node_modules/gtchunk17/package.json | 10 + .../gtchunkshadow/dist/index.d.ts | 2 + .../node_modules/gtchunkshadow/dist/index.js | 52 + .../node_modules/gtchunkshadow/package.json | 7 + .../gtchunkshared/dist/index.d.ts | 2 + .../node_modules/gtchunkshared/dist/index.js | 56 + .../node_modules/gtchunkshared/package.json | 7 + .../node_modules/gtchunkws/dist/index.d.ts | 4 + .../npm/node_modules/gtchunkws/dist/index.js | 1 + .../npm/node_modules/gtchunkws/package.json | 10 + .../npm/node_modules/gtcjsdist/dist/index.cjs | 43 + .../node_modules/gtcjsdist/dist/index.d.cts | 1 + .../npm/node_modules/gtcjsdist/package.json | 11 + .../npm/node_modules/gtcondexports/index.d.ts | 2 + .../npm/node_modules/gtcondexports/index.js | 14 + .../node_modules/gtcondexports/package.json | 7 + .../npm/node_modules/gtcycle/dist/append.d.ts | 1 + .../npm/node_modules/gtcycle/dist/append.js | 30 + .../npm/node_modules/gtcycle/dist/index.d.ts | 4 + .../npm/node_modules/gtcycle/dist/index.js | 44 + .../npm/node_modules/gtcycle/dist/merge.d.ts | 1 + .../npm/node_modules/gtcycle/dist/merge.js | 30 + .../npm/node_modules/gtcycle/package.json | 7 + .../node_modules/gtcyclealias/dist/index.d.ts | 3 + .../node_modules/gtcyclealias/dist/index.js | 37 + .../node_modules/gtcyclealias/dist/reader.js | 34 + .../node_modules/gtcyclealias/package.json | 7 + .../node_modules/gtcyclebad/dist/index.d.ts | 2 + .../npm/node_modules/gtcyclebad/dist/index.js | 33 + .../node_modules/gtcyclebad/dist/reader.js | 33 + .../npm/node_modules/gtcyclebad/package.json | 7 + .../node_modules/gtcyclebox/dist/index.d.ts | 3 + .../npm/node_modules/gtcyclebox/dist/index.js | 37 + .../node_modules/gtcyclebox/dist/reader.js | 34 + .../npm/node_modules/gtcyclebox/package.json | 7 + .../node_modules/gtcyclecall/dist/index.d.ts | 3 + .../node_modules/gtcyclecall/dist/index.js | 37 + .../node_modules/gtcyclecall/dist/reader.js | 33 + .../npm/node_modules/gtcyclecall/package.json | 7 + .../npm/node_modules/gtesm/dist/index.d.ts | 2 + .../npm/node_modules/gtesm/dist/index.js | 90 + .../npm/node_modules/gtesm/package.json | 10 + .../node_modules/gtesmthis/dist/index.d.ts | 2 + .../npm/node_modules/gtesmthis/dist/index.js | 94 + .../npm/node_modules/gtesmthis/package.json | 10 + .../npm/node_modules/gtesmws/dist/index.d.ts | 2 + .../npm/node_modules/gtesmws/dist/index.js | 1 + .../npm/node_modules/gtesmws/package.json | 10 + .../npm/node_modules/gtfnexports/index.d.ts | 13 + .../npm/node_modules/gtfnexports/index.js | 25 + .../npm/node_modules/gtfnexports/package.json | 7 + .../npm/node_modules/gtfnmembers/index.d.ts | 11 + .../npm/node_modules/gtfnmembers/index.js | 13 + .../npm/node_modules/gtfnmembers/package.json | 7 + .../npm/node_modules/gtlazy/dist/index.d.ts | 7 + .../npm/node_modules/gtlazy/dist/index.js | 116 + .../npm/node_modules/gtlazy/package.json | 7 + .../npm/node_modules/gtlazyuser/index.d.ts | 2 + .../npm/node_modules/gtlazyuser/index.js | 10 + .../npm/node_modules/gtlazyuser/package.json | 10 + .../npm/node_modules/gtlazyws/dist/index.d.ts | 7 + .../npm/node_modules/gtlazyws/dist/index.js | 3 + .../npm/node_modules/gtlazyws/package.json | 7 + .../npm/node_modules/gtsubpath/dist/data.d.ts | 2 + .../npm/node_modules/gtsubpath/dist/data.js | 6 + .../npm/node_modules/gtsubpath/dist/detect.js | 11 + .../node_modules/gtsubpath/dist/index.d.ts | 6 + .../npm/node_modules/gtsubpath/dist/index.js | 5 + .../npm/node_modules/gtsubpath/package.json | 10 + .../node_modules/tscdefault/build/index.d.ts | 8 + .../node_modules/tscdefault/build/index.js | 11 + .../tscdefault/build/registry-api.d.ts | 2 + .../tscdefault/build/registry-api.js | 5 + .../tscdefault/build/registry.d.ts | 5 + .../node_modules/tscdefault/build/registry.js | 20 + .../npm/node_modules/tscdefault/package.json | 6 + .../node_modules/gtpattern/lib/index.d.ts | 2 + .../node_modules/gtpattern/lib/index.js | 5 + .../node_modules/gtpattern/package.json | 6 + .../node_modules/gtpattern/lib/index.d.ts | 2 + .../node_modules/gtpattern/lib/index.js | 5 + .../node_modules/gtpattern/package.json | 6 + .../gtroute@1.0.0/node_modules/gtpattern | 1 + .../gtroute@1.0.0/node_modules/gtpattern-next | 1 + .../node_modules/gtroute/index.d.ts | 1 + .../node_modules/gtroute/index.js | 7 + .../node_modules/gtroute/package.json | 11 + .../self-hosting/frontend-collections.ts | 2 +- .../self-hosting/lowering-context-cases.ts | 2 +- .../self-hosting/ts7-checker-cases.ts | 8 +- .../fixtures/self-hosting/ts7-client-cases.ts | 3 +- .../self-hosting/ts7-semantic-cases.ts | 19 +- tests/harness/__snapshots__/arrow-bind.ts.txt | 17 +- .../boot-path-node-surface-fences.ts.txt | 35 + .../builtin-base-class-value-arguments.ts.txt | 17 + .../builtin-constructor-members.ts.txt | 71 + .../__snapshots__/child-process.ts.txt | 16 +- .../cjs-member-alias-writes/main.ts.txt | 13 + .../class-field-accessor-descriptor.ts.txt | 6 + tests/harness/__snapshots__/comptime.ts.txt | 16 +- .../__snapshots__/coverage-divergences.txt | 13 +- .../__snapshots__/coverage-import-fences.txt | 4 +- tests/harness/__snapshots__/coverage-wasm.txt | 19 +- .../__snapshots__/events-once-fences.ts.txt | 31 +- .../fromentries-aliased-entries.ts.txt | 8 + tests/harness/__snapshots__/imports.ts.txt | 2 +- ...inferred-callback-drops-conditional.ts.txt | 6 + .../inferred-callback-drops-satisfies.ts.txt | 6 + .../inferred-callback-drops.ts.txt | 6 + .../__snapshots__/json-shape/main.ts.txt | 4 +- .../local-class-boundaries.ts.txt | 6 +- .../local-class-type-query-brand.ts.txt | 8 +- .../__snapshots__/maybe-promise-fences.ts.txt | 9 +- .../harness/__snapshots__/mock-fences.ts.txt | 60 +- .../__snapshots__/module-out-of-scope.ts.txt | 48 +- .../object-assign-class-copy.ts.txt | 6 + .../object-literal-accessor-values.ts.txt | 6 + .../__snapshots__/options-record.ts.txt | 9 - .../pkg-import-fences/main.ts.txt | 6 +- .../__snapshots__/promise-all-fences.ts.txt | 27 +- ...gine-2083-destructuring-island-decl.ts.txt | 63 - ...ine-2104-computed-key-destructuring.ts.txt | 21 +- .../retired-engine-761-any-objects.ts.txt | 6 - .../speculative-function-refusal.ts.txt | 13 +- .../harness/__snapshots__/stdlib-fence.ts.txt | 8 +- .../stream-completion-callback-error.ts.txt | 17 + .../__snapshots__/thread-slots-fences.ts.txt | 11 +- .../variadic-wrapper-fences.ts.txt | 29 + .../__snapshots__/webassembly-fences.ts.txt | 108 +- tests/harness/bench-dynamic-tier.test.ts | 65 + tests/harness/ci-lanes.test.ts | 89 +- tests/harness/ci-test-plan.test.ts | 34 +- tests/harness/corpus-costs.json | 18 +- .../5730-thread-slots-widened-values.ts.json | 1 + .../corpus-costs/5775-md5-lengths.ts.json | 1 + .../corpus-costs/5779-sha512-lengths.ts.json | 1 + .../5816-fixed-into-unknown-rest.ts.json | 1 + ...7-constructor-function-instanceof.cjs.json | 1 + ...tructor-function-emitter-defaults.cjs.json | 1 + .../main.ts.json | 1 + .../main.ts.json | 1 + .../5916-dyn-error-stack-property.ts.json | 1 + .../5983-runtime-constructor.ts.json | 1 + .../6055-class-generator-iterables.ts.json | 1 + .../6056-class-symbol-method-calls.ts.json | 1 + .../6058-class-view-asserted-bindings.ts.json | 1 + .../6061-type-only-class-fields/main.ts.json | 1 + .../6063-union-class-field-checks.ts.json | 1 + .../6230-date-parse-legacy.ts.json | 1 + .../6231-date-local-fields.ts.json | 1 + .../corpus-costs/6232-date-fields-js.cjs.json | 1 + .../6233-object-identity-dynamic.cjs.json | 1 + .../6234-object-prototype-values.cjs.json | 1 + .../6235-object-identity-typed.ts.json | 1 + .../6236-builtin-function-values.cjs.json | 1 + .../6341-throw-array-identity.ts.json | 1 + .../6342-throw-builtin-identity.ts.json | 1 + .../6345-inspect-dyn-holes.ts.json | 1 + .../6346-inspect-dyn-dataview.ts.json | 1 + .../6347-dataview-methods-js.cjs.json | 1 + .../6348-errno-code-own-field.ts.json | 1 + .../6356-format-integer-dyn.ts.json | 1 + ...0-variadic-wrapper-arguments-apply.ts.json | 1 + .../6381-variadic-wrapper-async.ts.json | 1 + ...6382-variadic-wrapper-methods-this.ts.json | 1 + .../6383-variadic-wrapper-rest-tuple.ts.json | 1 + .../6384-variadic-wrapper-nested.ts.json | 1 + ...ariadic-wrapper-shared-local-names.ts.json | 1 + .../6390-catch-binding-predicate.ts.json | 1 + .../6391-nullish-function-default.ts.json | 1 + ...6392-optional-chain-protocol-calls.ts.json | 1 + .../6393-function-types-any-params.ts.json | 1 + .../6394-in-key-error-subclasses.ts.json | 1 + .../6395-generic-object-entries.ts.json | 1 + .../6396-generic-any-instantiation.ts.json | 1 + .../6397-event-loop-metrics.ts.json | 1 + .../6398-conditional-fixed-arity.ts.json | 1 + .../6400-dyn-error-stack-property-js.js.json | 1 + .../6400-override-any-base.ts.json | 1 + .../6401-dyn-error-own-property-names.ts.json | 1 + .../6401-override-param-variance.ts.json | 1 + ...02-dyn-error-own-property-names-js.js.json | 1 + .../6402-override-js-base/main.ts.json | 1 + .../6403-error-code-write.ts.json | 1 + .../6403-error-own-name-order.ts.json | 1 + .../6404-error-in-and-assign.ts.json | 1 + .../6404-error-own-name-order-js.js.json | 1 + .../6405-wrapped-default-parameters.ts.json | 1 + .../6406-npm-instance-receiver/main.ts.json | 1 + .../corpus-costs/6407-dyn-bytes-in.ts.json | 1 + .../corpus-costs/6408-dyn-bytes-in-js.js.json | 1 + .../6409-catch-binding-shapes.ts.json | 1 + .../6410-wrapper-untyped-results.ts.json | 1 + .../main.ts.json | 1 + .../main.ts.json | 1 + .../6413-override-js-async-base/main.ts.json | 1 + .../6414-catch-values-roundtrip.ts.json | 1 + .../6415-error-code-dynamic-writes.ts.json | 1 + .../6440-http-socket-path.ts.json | 1 + .../6441-http-request-options-record.ts.json | 1 + .../6442-https-request-options-record.ts.json | 1 + .../6443-net-lookup-http-close-text.ts.json | 1 + .../6445-rest-function-fixed-slot.ts.json | 1 + ...6-rest-function-nested-declaration.ts.json | 1 + ...6453-constructor-function-emitter.cjs.json | 1 + ...constructor-function-stream-bases.cjs.json | 1 + ...ional-members-over-index-signature.ts.json | 1 + .../6466-never-assertion-argument.ts.json | 1 + .../6467-fromentries-record-assertion.ts.json | 1 + ...468-generic-unknown-record-entries.ts.json | 1 + .../6468-node-stdio-writable-slot.ts.json | 1 + .../main.ts.json | 1 + .../6470-any-destructuring.ts.json | 1 + .../6471-any-method-chains.ts.json | 1 + .../6472-namespace-destructuring/main.ts.json | 1 + .../6473-any-parameter-values.ts.json | 1 + .../6474-async-any-callbacks/main.ts.json | 1 + .../main.ts.json | 1 + .../main.ts.json | 1 + .../6491-catch-any-bindings.ts.json | 1 + ...ructor-function-stream-late-hooks.cjs.json | 1 + .../corpus-costs/761-any-objects.ts.json | 1 + .../cjs-class-hierarchy-npm/main.ts.json | 1 + .../cjs-open-options-npm/main.ts.json | 1 + .../cjs-strict-arguments.cjs.json | 1 + .../cjs-symbol-key-collisions/main.js.json | 1 + .../cjs-symbol-key-table/main.js.json | 1 + .../cjs-unmapped-arguments.cjs.json | 1 + .../code-generation-disallowed.cjs.json | 1 + .../code-generation-disallowed.ts.json | 1 + .../comma-null-union-return.cjs.json | 1 + .../corpus-costs/dns-promises-lookup.ts.json | 1 + .../dns-promises-untyped.cjs.json | 1 + .../function-local-detached-methods.js.json | 1 + .../function-local-detached-methods.ts.json | 1 + ...function-local-private-evaluations.js.json | 1 + .../function-local-private-fields.js.json | 1 + .../graceful-fs-alias/main.cjs.json | 1 + .../local-class-detached-method.ts.json | 1 + .../object-literal-accessors-dyn.ts.json | 1 + .../private-in-checked-primitives.js.json | 1 + .../required-module-truthiness.cjs.json | 1 + .../corpus-costs/stream-web-readable.ts.json | 1 + .../corpus-costs/stream-web-require.cjs.json | 1 + .../strict-arguments-parameters.cjs.json | 1 + .../temporal-absent-probe.js.json | 1 + .../corpus-costs/temporal-absent.ts.json | 1 + .../corpus-costs/v8-heap-statistics.ts.json | 1 + .../corpus-costs/v8-require-optional.cjs.json | 1 + .../corpus-costs/webassembly-api.mjs.json | 1 + .../webassembly-dylink/main.mjs.json | 1 + .../webassembly-externref-lifetimes.mjs.json | 1 + .../corpus-costs/webassembly-jsapi.mjs.json | 1 + .../webassembly-lifetimes.mjs.json | 1 + .../webassembly-spec-control/main.mjs.json | 1 + .../webassembly-spec-float/main.mjs.json | 1 + .../webassembly-spec-imports/main.mjs.json | 1 + .../webassembly-spec-int/main.mjs.json | 1 + .../webassembly-spec-memory/main.mjs.json | 1 + .../webassembly-spec-table/main.mjs.json | 1 + .../corpus-costs/webassembly-typed.ts.json | 1 + .../corpus-costs/webassembly-worker.ts.json | 1 + .../webassembly-wpt-jsapi.mjs.json | 1 + tests/harness/coverage.test.ts | 15 +- tests/harness/diagnostics.test.ts | 12 +- tests/harness/differential.test.ts | 14 +- tests/harness/errors.test.ts | 345 +- tests/harness/esm-path-globals.test.ts | 38 + tests/harness/inheritance.test.ts | 20 +- tests/harness/library-asyncfree.test.ts | 15 + tests/harness/library-profile.test.ts | 5 +- tests/harness/linux-differential.test.ts | 3 + tests/harness/llvm-differential.test.ts | 6 +- tests/harness/lsan.supp | 4 + tests/harness/native-cli-packaging.test.ts | 9 +- tests/harness/npm-cases.ts | 6 +- tests/harness/npm-static.test.ts | 272 +- tests/harness/npm.test.ts | 34 +- tests/harness/project-config.test.ts | 238 +- tests/harness/runtime-tree-shaking.test.ts | 27 + .../self-hosting-native-toolchain.test.ts | 5 + .../self-hosting-runtime-features.test.ts | 2 + .../harness/self-hosting-runtime-pack.test.ts | 4 + .../harness/self-hosting-ts7-process.test.ts | 7 +- tests/harness/ts7-client-harness.ts | 6 +- .../harness/webassembly-memory-growth.test.ts | 96 + tests/harness/windows-differential.test.ts | 5 +- tests/harness/zero-cost-gate.test.ts | 29 + tests/wasm-spec/corpus/api.wat | 52 + tests/wasm-spec/corpus/dylink-ext.wat | 42 + tests/wasm-spec/corpus/dylink-main.wat | 33 + .../wasm-spec/corpus/externref-lifetimes.wat | 24 + tests/wasm-spec/corpus/jsapi-own.wat | 11 + tests/wasm-spec/corpus/jsapi.wat | 37 + tests/wasm-spec/corpus/lifetimes.wat | 23 + tests/wasm-spec/corpus/linked.wat | 12 + tests/wasm-spec/corpus/refs.wat | 10 + tests/wasm-spec/gen.mjs | 273 + tests/wasm-spec/runner.mjs | 148 + 1380 files changed, 69280 insertions(+), 10650 deletions(-) create mode 100644 benchmarks/runtime/workloads/channel-messages.ts create mode 100644 docs/content/limitations/builtin-apis/v8.mdx create mode 100644 docs/content/limitations/graceful-fs.mdx create mode 100644 docs/content/limitations/module-path-globals.mdx create mode 100644 docs/content/limitations/unix-domain-sockets.mdx create mode 100644 packages/compiler/src/backend/llvm/bytes-alias.ts create mode 100644 packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts create mode 100644 packages/compiler/src/backend/llvm/bytes-loop-versions.ts create mode 100644 packages/compiler/src/backend/llvm/emit-function.ts create mode 100644 packages/compiler/src/backend/llvm/emit-module.ts create mode 100644 packages/compiler/src/backend/llvm/emit-statements.ts create mode 100644 packages/compiler/src/backend/llvm/receivers.ts create mode 100644 packages/compiler/src/backend/llvm/wasm-modules.ts create mode 100644 packages/compiler/src/frontend/lowering/assertion-members.ts create mode 100644 packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts create mode 100644 packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts create mode 100644 packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts create mode 100644 packages/compiler/src/frontend/lowering/builtins/v8.ts create mode 100644 packages/compiler/src/frontend/lowering/class-attributes.ts create mode 100644 packages/compiler/src/frontend/lowering/class-iterables.ts create mode 100644 packages/compiler/src/frontend/lowering/code-generation.ts create mode 100644 packages/compiler/src/frontend/lowering/dyn-streams.ts create mode 100644 packages/compiler/src/frontend/lowering/error-name-order.ts create mode 100644 packages/compiler/src/frontend/lowering/expressions/hosted-narrowing.ts create mode 100644 packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts create mode 100644 packages/compiler/src/frontend/lowering/general-streams.ts create mode 100644 packages/compiler/src/frontend/lowering/private-brands.ts create mode 100644 packages/compiler/src/frontend/lowering/promise-executor-adapter.ts create mode 100644 packages/compiler/src/frontend/lowering/static-function-names.ts create mode 100644 packages/compiler/src/frontend/lowering/strict-mode.ts create mode 100644 packages/compiler/src/frontend/lowering/typed-arguments.ts create mode 100644 packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts create mode 100644 packages/compiler/src/frontend/npm-static-bundled-chunks.ts create mode 100644 packages/compiler/src/frontend/npm-static-bundled-esm.test.ts create mode 100644 packages/compiler/src/frontend/npm-static-bundled-esm.ts create mode 100644 packages/compiler/src/frontend/npm-static-cjs-exports-alias.test.ts create mode 100644 packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts create mode 100644 packages/compiler/src/frontend/npm-static-cjs-exports-var.ts create mode 100644 packages/compiler/src/frontend/ts7/api-conformance.ts create mode 100644 packages/compiler/src/frontend/ts7/checker-backend.test.ts create mode 100644 packages/compiler/src/frontend/ts7/checker-backend.ts create mode 100644 packages/compiler/src/frontend/ts7/diagnostic.ts create mode 100644 packages/compiler/src/frontend/ts7/node-functions.ts create mode 100644 packages/compiler/src/wasm/base64.ts create mode 100644 packages/compiler/src/wasm/module.ts create mode 100644 packages/compiler/src/wasm/translate.test.ts create mode 100644 packages/compiler/src/wasm/translate.ts create mode 100644 packages/compiler/test/bytes-field-borrow-emission.test.ts create mode 100644 packages/compiler/test/class-attributes.test.ts create mode 100644 packages/compiler/test/local-class-private-boundaries.test.ts create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5467-typed-array-loop-versions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5468-typed-array-loop-aliasing.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5607-string-char-compare.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5608-string-char-switch.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5609-string-switch-optional.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5730-thread-slots-widened-values.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5750-string-from-char-codes.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5751-string-length-cache.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5775-md5-lengths.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5779-sha512-lengths.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5816-fixed-into-unknown-rest.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5817-constructor-function-instanceof.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5818-constructor-function-emitter-defaults.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5876-readline-dispose-instanceof.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5877-readline-question-query.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5878-readline-dynamic-streams.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5891-typed-array-field-borrows.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5916-dyn-error-stack-property.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5983-runtime-constructor.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6004-class-instance-integrity.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6005-class-instance-integrity-sloppy.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6055-class-generator-iterables.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6056-class-symbol-method-calls.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6058-class-view-asserted-bindings.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6061-type-only-class-fields/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6063-union-class-field-checks.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-date-parse-legacy.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6231-date-local-fields.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6232-date-fields-js.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6233-object-identity-dynamic.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6234-object-prototype-values.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6235-object-identity-typed.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6236-builtin-function-values.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6341-throw-array-identity.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6342-throw-builtin-identity.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6345-inspect-dyn-holes.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6346-inspect-dyn-dataview.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6347-dataview-methods-js.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6348-errno-code-own-field.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6356-format-integer-dyn.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6380-variadic-wrapper-arguments-apply.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6381-variadic-wrapper-async.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6382-variadic-wrapper-methods-this.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6383-variadic-wrapper-rest-tuple.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6384-variadic-wrapper-nested.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6385-variadic-wrapper-shared-local-names.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6390-catch-binding-predicate.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6391-nullish-function-default.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6392-optional-chain-protocol-calls.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6393-function-types-any-params.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6394-in-key-error-subclasses.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6395-generic-object-entries.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6396-generic-any-instantiation.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6397-event-loop-metrics.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6398-conditional-fixed-arity.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-dyn-error-stack-property-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-override-any-base.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-dyn-error-own-property-names.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-override-param-variance.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-dyn-error-own-property-names-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-override-js-base/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-code-write.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-own-name-order.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-in-and-assign.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-own-name-order-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6405-wrapped-default-parameters.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6406-npm-instance-receiver/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-dyn-bytes-in.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-dyn-bytes-in-js.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6409-catch-binding-shapes.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-json-mixed-scalar-tables/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-wrapper-untyped-results.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-prototype-reset-constructors/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-union-array-tuple-destructuring.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6412-js-error-tostring-override/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6413-override-js-async-base/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6414-catch-values-roundtrip.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6415-error-code-dynamic-writes.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6416-tls-session-reused.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6417-object-assign-class-target.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6418-nullish-assign-index-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6420-process-send-presence.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6421-process-send-presence-fork/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6422-process-exitcode-read.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6423-process-exitcode-default.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6424-process-exit-keeps-exitcode.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6425-stream-hook-wider-chunk.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6426-promise-race-void-entry.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6427-http-request-options-type.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6428-promisify-callback-functions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6429-esm-js-dirname.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6430-npm-constructor-function-class/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6431-cjs-exports-rebinding/main.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6440-http-socket-path.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6441-http-request-options-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6442-https-request-options-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6443-net-lookup-http-close-text.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6445-rest-function-fixed-slot.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6446-rest-function-nested-declaration.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-builtin-base-class-values.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-array-length-fill-spread.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-builtin-base-class-expressions.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6453-constructor-function-emitter.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6454-constructor-function-stream-bases.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6460-in-union-optional-slots.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6461-predicate-intersection-narrowing.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6462-assertion-attached-members.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6463-node-readable-stream-producers.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6464-node-writable-stream-producers.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-node-stream-js-boundary/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-optional-members-over-index-signature.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-never-assertion-argument.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-node-stdio-stream-surface.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-fromentries-record-assertion.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-node-stream-untyped-sources/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-node-stdio-writable-slot.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-promise-reject-async-return.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-stream-transform-any-data-callback.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-stream-data-buffer-or-string.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-union-literal-results-into-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-asserted-member-read.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-child-data-unknown-chunk.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-unknown-spread-into-index-record.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-index-record-spread-literals.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-dynamic-spread-literals.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-boolean-callbacks.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-generic-callback-extended-results.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-node-stream-dyn-readable/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-enum-runtime-keys.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-node-stream-dyn-writable/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-node-stream-dyn-helpers/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-promise-all-tuples.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6483-nullable-regex-receivers.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-literal-key-field-updates.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-index-record-updates.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-default-export-class-members/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-class-hierarchy-npm/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-open-options-npm/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-strict-arguments.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-symbol-key-collisions/main.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-symbol-key-table/main.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-unmapped-arguments.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/comma-null-union-return.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-lookup.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-untyped.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-detached-methods.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-detached-methods.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-private-evaluations.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-private-fields.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/graceful-fs-alias/main.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/local-class-detached-method.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-accessors-dyn.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/private-in-checked-primitives.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/required-module-truthiness.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-readable.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-require.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/strict-arguments-parameters.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent-probe.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-heap-statistics.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-require-optional.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-api.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-dylink/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-externref-lifetimes.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-jsapi.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-lifetimes.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-control/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-float/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-imports/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-int/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-memory/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-table/main.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-typed.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-worker.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-wpt-jsapi.mjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/boot-path-node-surface-fences.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-base-class-value-arguments.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-constructor-members.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/cjs-member-alias-writes/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/class-field-accessor-descriptor.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/fromentries-aliased-entries.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-conditional.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-satisfies.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-assign-class-copy.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-literal-accessor-values.ts.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/stream-completion-callback-error.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/variadic-wrapper-fences.ts.json delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-fences.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-streams.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/event-loop-metrics.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/fetch-init-members.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/http-socket-path.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/lookup-and-header-entries.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/node-stream-interfaces.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/process-versions-absent.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/promisify-callback-functions.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-construction.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-subclass-hooks.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/timeout-type.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-session-timings.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2580-lazy-barrel/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/cjs-exports-var/main.ts.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/tsc-default-object/main.ts.json create mode 100644 packages/runtime/src/scr_dns_promises.c create mode 100644 packages/runtime/src/scr_dyn_builtin_class.c create mode 100644 packages/runtime/src/scr_dyn_builtin_stream.c create mode 100644 packages/runtime/src/scr_events_once_signal.c create mode 100644 packages/runtime/src/scr_perf_loop.c create mode 100644 packages/runtime/src/scr_stream_dyn.c create mode 100644 packages/runtime/src/scr_wasm.c create mode 100644 packages/runtime/src/scr_wasm.h create mode 100644 packages/runtime/src/scr_wasm_abi.h create mode 100644 scripts/checker-identity.mjs create mode 100644 tests/corpus/5467-typed-array-loop-versions.ts create mode 100644 tests/corpus/5468-typed-array-loop-aliasing.ts create mode 100644 tests/corpus/5469-typed-array-loop-initializer-rebinding.ts create mode 100644 tests/corpus/5607-string-char-compare.ts create mode 100644 tests/corpus/5608-string-char-switch.ts create mode 100644 tests/corpus/5609-string-switch-optional.ts create mode 100644 tests/corpus/5730-thread-slots-widened-values.ts create mode 100644 tests/corpus/5750-string-from-char-codes.ts create mode 100644 tests/corpus/5751-string-length-cache.ts create mode 100644 tests/corpus/5775-md5-lengths.ts create mode 100644 tests/corpus/5779-sha512-lengths.ts create mode 100644 tests/corpus/5816-fixed-into-unknown-rest.ts create mode 100644 tests/corpus/5817-constructor-function-instanceof.cjs create mode 100644 tests/corpus/5818-constructor-function-emitter-defaults.cjs create mode 100644 tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts create mode 100644 tests/corpus/5819-node-stream-dyn-direct-pipe/sources.js create mode 100644 tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts create mode 100644 tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js create mode 100644 tests/corpus/5876-readline-dispose-instanceof.ts create mode 100644 tests/corpus/5877-readline-question-query.cjs create mode 100644 tests/corpus/5878-readline-dynamic-streams.mjs create mode 100644 tests/corpus/5891-typed-array-field-borrows.ts create mode 100644 tests/corpus/5916-dyn-error-stack-property.ts create mode 100644 tests/corpus/5983-runtime-constructor.ts create mode 100644 tests/corpus/6004-class-instance-integrity.ts create mode 100644 tests/corpus/6005-class-instance-integrity-sloppy.cjs create mode 100644 tests/corpus/6055-class-generator-iterables.ts create mode 100644 tests/corpus/6056-class-symbol-method-calls.ts create mode 100644 tests/corpus/6058-class-view-asserted-bindings.ts create mode 100644 tests/corpus/6061-type-only-class-fields/emitted.ts create mode 100644 tests/corpus/6061-type-only-class-fields/holder.ts create mode 100644 tests/corpus/6061-type-only-class-fields/main.ts create mode 100644 tests/corpus/6063-union-class-field-checks.ts create mode 100644 tests/corpus/6230-date-parse-legacy.ts create mode 100644 tests/corpus/6231-date-local-fields.ts create mode 100644 tests/corpus/6232-date-fields-js.cjs create mode 100644 tests/corpus/6233-object-identity-dynamic.cjs create mode 100644 tests/corpus/6234-object-prototype-values.cjs create mode 100644 tests/corpus/6235-object-identity-typed.ts create mode 100644 tests/corpus/6236-builtin-function-values.cjs create mode 100644 tests/corpus/6341-throw-array-identity.ts create mode 100644 tests/corpus/6342-throw-builtin-identity.ts create mode 100644 tests/corpus/6345-inspect-dyn-holes.ts create mode 100644 tests/corpus/6346-inspect-dyn-dataview.ts create mode 100644 tests/corpus/6347-dataview-methods-js.cjs create mode 100644 tests/corpus/6348-errno-code-own-field.ts create mode 100644 tests/corpus/6356-format-integer-dyn.ts create mode 100644 tests/corpus/6380-variadic-wrapper-arguments-apply.ts create mode 100644 tests/corpus/6381-variadic-wrapper-async.ts create mode 100644 tests/corpus/6382-variadic-wrapper-methods-this.ts create mode 100644 tests/corpus/6383-variadic-wrapper-rest-tuple.ts create mode 100644 tests/corpus/6384-variadic-wrapper-nested.ts create mode 100644 tests/corpus/6385-variadic-wrapper-shared-local-names.ts create mode 100644 tests/corpus/6390-catch-binding-predicate.ts create mode 100644 tests/corpus/6391-nullish-function-default.ts create mode 100644 tests/corpus/6392-optional-chain-protocol-calls.ts create mode 100644 tests/corpus/6393-function-types-any-params.ts create mode 100644 tests/corpus/6394-in-key-error-subclasses.ts create mode 100644 tests/corpus/6395-generic-object-entries.ts create mode 100644 tests/corpus/6396-generic-any-instantiation.ts create mode 100644 tests/corpus/6397-event-loop-metrics.ts create mode 100644 tests/corpus/6398-conditional-fixed-arity.ts create mode 100644 tests/corpus/6400-dyn-error-stack-property-js.js create mode 100644 tests/corpus/6400-override-any-base.ts create mode 100644 tests/corpus/6401-dyn-error-own-property-names.ts create mode 100644 tests/corpus/6401-override-param-variance.ts create mode 100644 tests/corpus/6402-dyn-error-own-property-names-js.js create mode 100644 tests/corpus/6402-override-js-base/main.ts create mode 100644 tests/corpus/6402-override-js-base/pool.js create mode 100644 tests/corpus/6403-error-code-write.ts create mode 100644 tests/corpus/6403-error-own-name-order.ts create mode 100644 tests/corpus/6404-error-in-and-assign.ts create mode 100644 tests/corpus/6404-error-own-name-order-js.js create mode 100644 tests/corpus/6405-wrapped-default-parameters.ts create mode 100644 tests/corpus/6406-npm-instance-receiver/main.ts create mode 100644 tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.d.ts create mode 100644 tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.js create mode 100644 tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/package.json create mode 100644 tests/corpus/6407-dyn-bytes-in.ts create mode 100644 tests/corpus/6408-dyn-bytes-in-js.js create mode 100644 tests/corpus/6409-catch-binding-shapes.ts create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/extra.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/main.ts create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.d.ts create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.js create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/package.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/table.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.d.ts create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.js create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/package.json create mode 100644 tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/table.json create mode 100644 tests/corpus/6410-wrapper-untyped-results.ts create mode 100644 tests/corpus/6411-prototype-reset-constructors/issues.js create mode 100644 tests/corpus/6411-prototype-reset-constructors/main.ts create mode 100644 tests/corpus/6411-union-array-tuple-destructuring.ts create mode 100644 tests/corpus/6412-js-error-tostring-override/errors.js create mode 100644 tests/corpus/6412-js-error-tostring-override/main.ts create mode 100644 tests/corpus/6413-override-js-async-base/lock.js create mode 100644 tests/corpus/6413-override-js-async-base/main.ts create mode 100644 tests/corpus/6414-catch-values-roundtrip.ts create mode 100644 tests/corpus/6415-error-code-dynamic-writes.ts create mode 100644 tests/corpus/6416-tls-session-reused.ts create mode 100644 tests/corpus/6417-object-assign-class-target.ts create mode 100644 tests/corpus/6418-nullish-assign-index-record.ts create mode 100644 tests/corpus/6420-process-send-presence.ts create mode 100644 tests/corpus/6421-process-send-presence-fork/main.ts create mode 100644 tests/corpus/6421-process-send-presence-fork/worker.ts create mode 100644 tests/corpus/6422-process-exitcode-read.ts create mode 100644 tests/corpus/6423-process-exitcode-default.ts create mode 100644 tests/corpus/6424-process-exit-keeps-exitcode.ts create mode 100644 tests/corpus/6425-stream-hook-wider-chunk.ts create mode 100644 tests/corpus/6426-promise-race-void-entry.ts create mode 100644 tests/corpus/6427-http-request-options-type.ts create mode 100644 tests/corpus/6428-promisify-callback-functions.ts create mode 100644 tests/corpus/6429-esm-js-dirname.mjs create mode 100644 tests/corpus/6430-npm-constructor-function-class/main.ts create mode 100644 tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/index.d.ts create mode 100644 tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/index.js create mode 100644 tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/lib/client.js create mode 100644 tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/package.json create mode 100644 tests/corpus/6431-cjs-exports-rebinding/extended.cjs create mode 100644 tests/corpus/6431-cjs-exports-rebinding/lib.cjs create mode 100644 tests/corpus/6431-cjs-exports-rebinding/main.cjs create mode 100644 tests/corpus/6440-http-socket-path.ts create mode 100644 tests/corpus/6441-http-request-options-record.ts create mode 100644 tests/corpus/6442-https-request-options-record.ts create mode 100644 tests/corpus/6443-net-lookup-http-close-text.ts create mode 100644 tests/corpus/6445-rest-function-fixed-slot.ts create mode 100644 tests/corpus/6446-rest-function-nested-declaration.ts create mode 100644 tests/corpus/6450-builtin-base-class-values.ts create mode 100644 tests/corpus/6450-promise-race-never-entry.ts create mode 100644 tests/corpus/6451-array-length-fill-spread.ts create mode 100644 tests/corpus/6451-builtin-base-class-expressions.cjs create mode 100644 tests/corpus/6453-constructor-function-emitter.cjs create mode 100644 tests/corpus/6454-constructor-function-stream-bases.cjs create mode 100644 tests/corpus/6460-in-union-optional-slots.ts create mode 100644 tests/corpus/6461-predicate-intersection-narrowing.ts create mode 100644 tests/corpus/6462-assertion-attached-members.ts create mode 100644 tests/corpus/6463-node-readable-stream-producers.ts create mode 100644 tests/corpus/6464-node-writable-stream-producers.ts create mode 100644 tests/corpus/6465-node-stream-js-boundary/consume.js create mode 100644 tests/corpus/6465-node-stream-js-boundary/main.ts create mode 100644 tests/corpus/6465-optional-members-over-index-signature.ts create mode 100644 tests/corpus/6466-never-assertion-argument.ts create mode 100644 tests/corpus/6466-node-stdio-stream-surface.ts create mode 100644 tests/corpus/6467-fromentries-record-assertion.ts create mode 100644 tests/corpus/6467-node-stream-untyped-sources/main.ts create mode 100644 tests/corpus/6467-node-stream-untyped-sources/sources.js create mode 100644 tests/corpus/6468-generic-unknown-record-entries.ts create mode 100644 tests/corpus/6468-node-stdio-writable-slot.ts create mode 100644 tests/corpus/6469-node-stdio-writable-js-pipe/main.ts create mode 100644 tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js create mode 100644 tests/corpus/6470-any-destructuring.ts create mode 100644 tests/corpus/6470-promise-reject-async-return.ts create mode 100644 tests/corpus/6470-stream-transform-any-data-callback.ts create mode 100644 tests/corpus/6471-any-method-chains.ts create mode 100644 tests/corpus/6471-stream-data-buffer-or-string.ts create mode 100644 tests/corpus/6471-union-literal-results-into-record.ts create mode 100644 tests/corpus/6472-asserted-member-read.ts create mode 100644 tests/corpus/6472-child-data-unknown-chunk.ts create mode 100644 tests/corpus/6472-namespace-destructuring/lib.ts create mode 100644 tests/corpus/6472-namespace-destructuring/main.ts create mode 100644 tests/corpus/6472-namespace-destructuring/shape.ts create mode 100644 tests/corpus/6473-any-parameter-values.ts create mode 100644 tests/corpus/6473-unknown-spread-into-index-record.ts create mode 100644 tests/corpus/6474-async-any-callbacks/main.ts create mode 100644 tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts create mode 100644 tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js create mode 100644 tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json create mode 100644 tests/corpus/6474-async-any-callbacks/tsconfig.json create mode 100644 tests/corpus/6474-index-record-spread-literals.ts create mode 100644 tests/corpus/6475-dynamic-spread-literals.ts create mode 100644 tests/corpus/6475-js-promise-resolve-adoption/main.ts create mode 100644 tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts create mode 100644 tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js create mode 100644 tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json create mode 100644 tests/corpus/6480-boolean-callbacks.ts create mode 100644 tests/corpus/6480-catch-typeof-untyped-throws.ts create mode 100644 tests/corpus/6480-generic-callback-extended-results.ts create mode 100644 tests/corpus/6480-node-stream-dyn-readable/main.ts create mode 100644 tests/corpus/6480-node-stream-dyn-readable/sources.js create mode 100644 tests/corpus/6481-enum-runtime-keys.ts create mode 100644 tests/corpus/6481-node-stream-dyn-writable/main.ts create mode 100644 tests/corpus/6481-node-stream-dyn-writable/sources.js create mode 100644 tests/corpus/6482-node-stream-dyn-helpers/main.ts create mode 100644 tests/corpus/6482-node-stream-dyn-helpers/sources.js create mode 100644 tests/corpus/6482-promise-all-tuples.ts create mode 100644 tests/corpus/6483-nullable-regex-receivers.ts create mode 100644 tests/corpus/6490-events-once-signal.ts create mode 100644 tests/corpus/6490-js-named-promise-executor/main.ts create mode 100644 tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts create mode 100644 tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js create mode 100644 tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json create mode 100644 tests/corpus/6490-js-named-promise-executor/tsconfig.json create mode 100644 tests/corpus/6490-literal-key-field-updates.ts create mode 100644 tests/corpus/6491-catch-any-bindings.ts create mode 100644 tests/corpus/6491-events-once-streams.ts create mode 100644 tests/corpus/6491-index-record-updates.ts create mode 100644 tests/corpus/6492-promise-race-combined.ts create mode 100644 tests/corpus/6493-stream-function-callbacks.ts create mode 100644 tests/corpus/6494-default-export-class-members/lib/date.js create mode 100644 tests/corpus/6494-default-export-class-members/lib/error.js create mode 100644 tests/corpus/6494-default-export-class-members/lib/index.js create mode 100644 tests/corpus/6494-default-export-class-members/main.ts create mode 100644 tests/corpus/6494-default-export-class-members/package.json create mode 100644 tests/corpus/6494-events-once-dyn-stream-slot/main.ts create mode 100644 tests/corpus/6494-events-once-dyn-stream-slot/sources.js create mode 100644 tests/corpus/6560-constructor-function-readable.cjs create mode 100644 tests/corpus/6561-constructor-function-writable.cjs create mode 100644 tests/corpus/6562-constructor-function-transform.cjs create mode 100644 tests/corpus/6563-constructor-function-stream-errors.cjs create mode 100644 tests/corpus/6564-constructor-function-stream-late-hooks.cjs rename tests/{diagnostics/retired-engine-761-any-objects.ts => corpus/761-any-objects.ts} (67%) create mode 100644 tests/corpus/cjs-class-hierarchy-npm/main.ts create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/index.d.ts create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/index.js create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/agent.js create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/describe.js create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/dispatcher-base.js create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/dispatcher.js create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/pool.js create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/symbols.js create mode 100644 tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/package.json create mode 100644 tests/corpus/cjs-open-options-npm/main.ts create mode 100644 tests/corpus/cjs-open-options-npm/node_modules/optkit/index.d.ts create mode 100644 tests/corpus/cjs-open-options-npm/node_modules/optkit/index.js create mode 100644 tests/corpus/cjs-open-options-npm/node_modules/optkit/package.json create mode 100644 tests/corpus/cjs-strict-arguments.cjs create mode 100644 tests/corpus/cjs-symbol-key-collisions/client.js create mode 100644 tests/corpus/cjs-symbol-key-collisions/core-symbols.js create mode 100644 tests/corpus/cjs-symbol-key-collisions/main.js create mode 100644 tests/corpus/cjs-symbol-key-collisions/mock-client.js create mode 100644 tests/corpus/cjs-symbol-key-collisions/mock-symbols.js create mode 100644 tests/corpus/cjs-symbol-key-collisions/package.json create mode 100644 tests/corpus/cjs-symbol-key-table/main.js create mode 100644 tests/corpus/cjs-symbol-key-table/package.json create mode 100644 tests/corpus/cjs-symbol-key-table/queue.js create mode 100644 tests/corpus/cjs-symbol-key-table/symbols.js create mode 100644 tests/corpus/cjs-symbol-key-table/upper-queue.js create mode 100644 tests/corpus/cjs-unmapped-arguments.cjs create mode 100644 tests/corpus/code-generation-disallowed.cjs create mode 100644 tests/corpus/code-generation-disallowed.ts create mode 100644 tests/corpus/comma-null-union-return.cjs create mode 100644 tests/corpus/dns-promises-lookup.ts create mode 100644 tests/corpus/dns-promises-untyped.cjs create mode 100644 tests/corpus/function-local-detached-methods.js create mode 100644 tests/corpus/function-local-detached-methods.ts create mode 100644 tests/corpus/function-local-private-evaluations.js create mode 100644 tests/corpus/function-local-private-fields.js create mode 100644 tests/corpus/graceful-fs-alias/main.cjs create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/LICENSE create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/clone.js create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/graceful-fs.js create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/legacy-streams.js create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/package.json create mode 100644 tests/corpus/graceful-fs-alias/node_modules/graceful-fs/polyfills.js create mode 100644 tests/corpus/local-class-detached-method.ts create mode 100644 tests/corpus/object-literal-accessors-dyn.ts create mode 100644 tests/corpus/private-in-checked-primitives.js create mode 100644 tests/corpus/required-module-truthiness.cjs create mode 100644 tests/corpus/stream-web-readable.ts create mode 100644 tests/corpus/stream-web-require.cjs create mode 100644 tests/corpus/strict-arguments-parameters.cjs create mode 100644 tests/corpus/temporal-absent-probe.js create mode 100644 tests/corpus/temporal-absent.ts create mode 100644 tests/corpus/v8-heap-statistics.ts create mode 100644 tests/corpus/v8-require-optional.cjs create mode 100644 tests/corpus/webassembly-api.mjs create mode 100644 tests/corpus/webassembly-dylink/LICENSE.quickjs-wasi create mode 100644 tests/corpus/webassembly-dylink/extensions.js create mode 100644 tests/corpus/webassembly-dylink/main.mjs create mode 100644 tests/corpus/webassembly-externref-lifetimes.mjs create mode 100644 tests/corpus/webassembly-jsapi.mjs create mode 100644 tests/corpus/webassembly-lifetimes.mjs create mode 100644 tests/corpus/webassembly-spec-control/data.mjs create mode 100644 tests/corpus/webassembly-spec-control/main.mjs create mode 100644 tests/corpus/webassembly-spec-float/data.mjs create mode 100644 tests/corpus/webassembly-spec-float/main.mjs create mode 100644 tests/corpus/webassembly-spec-imports/data.mjs create mode 100644 tests/corpus/webassembly-spec-imports/main.mjs create mode 100644 tests/corpus/webassembly-spec-int/data.mjs create mode 100644 tests/corpus/webassembly-spec-int/main.mjs create mode 100644 tests/corpus/webassembly-spec-memory/data.mjs create mode 100644 tests/corpus/webassembly-spec-memory/main.mjs create mode 100644 tests/corpus/webassembly-spec-table/data.mjs create mode 100644 tests/corpus/webassembly-spec-table/main.mjs create mode 100644 tests/corpus/webassembly-typed.ts create mode 100644 tests/corpus/webassembly-worker.ts create mode 100644 tests/corpus/webassembly-wpt-jsapi.mjs create mode 100644 tests/coverage-fixtures/wasm/ext.so create mode 100644 tests/coverage-fixtures/wasm/native.so create mode 100644 tests/diagnostics/boot-path-node-surface-fences.ts create mode 100644 tests/diagnostics/builtin-base-class-value-arguments.ts create mode 100644 tests/diagnostics/builtin-constructor-members.ts create mode 100644 tests/diagnostics/cjs-member-alias-writes/main.ts create mode 100644 tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/index.d.ts create mode 100644 tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/index.js create mode 100644 tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/lib.js create mode 100644 tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/package.json create mode 100644 tests/diagnostics/class-field-accessor-descriptor.ts create mode 100644 tests/diagnostics/fromentries-aliased-entries.ts create mode 100644 tests/diagnostics/inferred-callback-drops-conditional.ts create mode 100644 tests/diagnostics/inferred-callback-drops-satisfies.ts create mode 100644 tests/diagnostics/inferred-callback-drops.ts create mode 100644 tests/diagnostics/object-assign-class-copy.ts create mode 100644 tests/diagnostics/object-literal-accessor-values.ts create mode 100644 tests/diagnostics/stream-completion-callback-error.ts create mode 100644 tests/diagnostics/variadic-wrapper-fences.ts create mode 100644 tests/fixtures/esm-path-globals/lib/helper.ts create mode 100644 tests/fixtures/esm-path-globals/main.ts create mode 100644 tests/fixtures/node-test/cases/mock-fn-default.ts delete mode 100644 tests/fixtures/node-types/child-stdin-fences.ts create mode 100644 tests/fixtures/node-types/child-stdin-streams.ts create mode 100644 tests/fixtures/node-types/event-loop-metrics.ts create mode 100644 tests/fixtures/node-types/events-once-signal-race.ts create mode 100644 tests/fixtures/node-types/fetch-init-members.ts create mode 100644 tests/fixtures/node-types/http-socket-path.ts create mode 100644 tests/fixtures/node-types/lookup-and-header-entries.ts create mode 100644 tests/fixtures/node-types/node-stream-interfaces.ts create mode 100644 tests/fixtures/node-types/process-versions-absent.ts create mode 100644 tests/fixtures/node-types/promisify-callback-functions.ts create mode 100644 tests/fixtures/node-types/stream-construction.ts create mode 100644 tests/fixtures/node-types/stream-subclass-hooks.ts create mode 100644 tests/fixtures/node-types/timeout-type.ts create mode 100644 tests/fixtures/node-types/tls-session-timings.ts create mode 100644 tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts create mode 100644 tests/fixtures/npm-static/node_modules/@types/twinstale/package.json create mode 100644 tests/fixtures/npm-static/node_modules/twinstale/index.js create mode 100644 tests/fixtures/npm-static/node_modules/twinstale/package.json create mode 100644 tests/fixtures/npm-static/twinstale-cli.ts create mode 100644 tests/fixtures/npm/cases/2558-bundled-chunks/main.ts create mode 100644 tests/fixtures/npm/cases/2559-cjs-cycle/main.ts create mode 100644 tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts create mode 100644 tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts create mode 100644 tests/fixtures/npm/cases/2562-subpath-leaf/main.ts create mode 100644 tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts create mode 100644 tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts create mode 100644 tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts create mode 100644 tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts create mode 100644 tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts create mode 100644 tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts create mode 100644 tests/fixtures/npm/cases/2580-lazy-barrel/main.ts create mode 100644 tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts create mode 100644 tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts create mode 120000 tests/fixtures/npm/cases/2582-npm-alias-two-releases/node_modules/gtroute create mode 100644 tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts create mode 100644 tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts create mode 100644 tests/fixtures/npm/cases/cjs-exports-var/main.ts create mode 100644 tests/fixtures/npm/cases/tsc-default-object/main.ts create mode 100644 tests/fixtures/npm/node_modules/exportsvar/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/exportsvar/lib/ip.js create mode 100644 tests/fixtures/npm/node_modules/exportsvar/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunk/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunk/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunk/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunk17/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunk17/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunk17/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunkshadow/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunkshared/package.json create mode 100644 tests/fixtures/npm/node_modules/gtchunkws/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtchunkws/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtchunkws/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcjsdist/dist/index.cjs create mode 100644 tests/fixtures/npm/node_modules/gtcjsdist/dist/index.d.cts create mode 100644 tests/fixtures/npm/node_modules/gtcjsdist/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcondexports/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcondexports/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcondexports/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/append.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/append.js create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/merge.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcycle/dist/merge.js create mode 100644 tests/fixtures/npm/node_modules/gtcycle/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclealias/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclebad/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclebad/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebad/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebad/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclebox/package.json create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js create mode 100644 tests/fixtures/npm/node_modules/gtcyclecall/package.json create mode 100644 tests/fixtures/npm/node_modules/gtesm/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtesm/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtesm/package.json create mode 100644 tests/fixtures/npm/node_modules/gtesmthis/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtesmthis/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtesmthis/package.json create mode 100644 tests/fixtures/npm/node_modules/gtesmws/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtesmws/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtesmws/package.json create mode 100644 tests/fixtures/npm/node_modules/gtfnexports/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtfnexports/index.js create mode 100644 tests/fixtures/npm/node_modules/gtfnexports/package.json create mode 100644 tests/fixtures/npm/node_modules/gtfnmembers/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtfnmembers/index.js create mode 100644 tests/fixtures/npm/node_modules/gtfnmembers/package.json create mode 100644 tests/fixtures/npm/node_modules/gtlazy/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtlazy/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtlazy/package.json create mode 100644 tests/fixtures/npm/node_modules/gtlazyuser/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtlazyuser/index.js create mode 100644 tests/fixtures/npm/node_modules/gtlazyuser/package.json create mode 100644 tests/fixtures/npm/node_modules/gtlazyws/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtlazyws/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtlazyws/package.json create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/data.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/data.js create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/detect.js create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/dist/index.js create mode 100644 tests/fixtures/npm/node_modules/gtsubpath/package.json create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/index.d.ts create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/index.js create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/registry-api.d.ts create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/registry-api.js create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/registry.d.ts create mode 100644 tests/fixtures/npm/node_modules/tscdefault/build/registry.js create mode 100644 tests/fixtures/npm/node_modules/tscdefault/package.json create mode 100644 tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.d.ts create mode 100644 tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.js create mode 100644 tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/package.json create mode 100644 tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.d.ts create mode 100644 tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.js create mode 100644 tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/package.json create mode 120000 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern create mode 120000 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern-next create mode 100644 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.d.ts create mode 100644 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.js create mode 100644 tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/package.json create mode 100644 tests/harness/__snapshots__/boot-path-node-surface-fences.ts.txt create mode 100644 tests/harness/__snapshots__/builtin-base-class-value-arguments.ts.txt create mode 100644 tests/harness/__snapshots__/builtin-constructor-members.ts.txt create mode 100644 tests/harness/__snapshots__/cjs-member-alias-writes/main.ts.txt create mode 100644 tests/harness/__snapshots__/class-field-accessor-descriptor.ts.txt create mode 100644 tests/harness/__snapshots__/fromentries-aliased-entries.ts.txt create mode 100644 tests/harness/__snapshots__/inferred-callback-drops-conditional.ts.txt create mode 100644 tests/harness/__snapshots__/inferred-callback-drops-satisfies.ts.txt create mode 100644 tests/harness/__snapshots__/inferred-callback-drops.ts.txt create mode 100644 tests/harness/__snapshots__/object-assign-class-copy.ts.txt create mode 100644 tests/harness/__snapshots__/object-literal-accessor-values.ts.txt delete mode 100644 tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt create mode 100644 tests/harness/__snapshots__/stream-completion-callback-error.ts.txt create mode 100644 tests/harness/__snapshots__/variadic-wrapper-fences.ts.txt create mode 100644 tests/harness/bench-dynamic-tier.test.ts create mode 100644 tests/harness/corpus-costs/5730-thread-slots-widened-values.ts.json create mode 100644 tests/harness/corpus-costs/5775-md5-lengths.ts.json create mode 100644 tests/harness/corpus-costs/5779-sha512-lengths.ts.json create mode 100644 tests/harness/corpus-costs/5816-fixed-into-unknown-rest.ts.json create mode 100644 tests/harness/corpus-costs/5817-constructor-function-instanceof.cjs.json create mode 100644 tests/harness/corpus-costs/5818-constructor-function-emitter-defaults.cjs.json create mode 100644 tests/harness/corpus-costs/5819-node-stream-dyn-direct-pipe/main.ts.json create mode 100644 tests/harness/corpus-costs/5820-node-stream-dyn-constructor-overrides/main.ts.json create mode 100644 tests/harness/corpus-costs/5916-dyn-error-stack-property.ts.json create mode 100644 tests/harness/corpus-costs/5983-runtime-constructor.ts.json create mode 100644 tests/harness/corpus-costs/6055-class-generator-iterables.ts.json create mode 100644 tests/harness/corpus-costs/6056-class-symbol-method-calls.ts.json create mode 100644 tests/harness/corpus-costs/6058-class-view-asserted-bindings.ts.json create mode 100644 tests/harness/corpus-costs/6061-type-only-class-fields/main.ts.json create mode 100644 tests/harness/corpus-costs/6063-union-class-field-checks.ts.json create mode 100644 tests/harness/corpus-costs/6230-date-parse-legacy.ts.json create mode 100644 tests/harness/corpus-costs/6231-date-local-fields.ts.json create mode 100644 tests/harness/corpus-costs/6232-date-fields-js.cjs.json create mode 100644 tests/harness/corpus-costs/6233-object-identity-dynamic.cjs.json create mode 100644 tests/harness/corpus-costs/6234-object-prototype-values.cjs.json create mode 100644 tests/harness/corpus-costs/6235-object-identity-typed.ts.json create mode 100644 tests/harness/corpus-costs/6236-builtin-function-values.cjs.json create mode 100644 tests/harness/corpus-costs/6341-throw-array-identity.ts.json create mode 100644 tests/harness/corpus-costs/6342-throw-builtin-identity.ts.json create mode 100644 tests/harness/corpus-costs/6345-inspect-dyn-holes.ts.json create mode 100644 tests/harness/corpus-costs/6346-inspect-dyn-dataview.ts.json create mode 100644 tests/harness/corpus-costs/6347-dataview-methods-js.cjs.json create mode 100644 tests/harness/corpus-costs/6348-errno-code-own-field.ts.json create mode 100644 tests/harness/corpus-costs/6356-format-integer-dyn.ts.json create mode 100644 tests/harness/corpus-costs/6380-variadic-wrapper-arguments-apply.ts.json create mode 100644 tests/harness/corpus-costs/6381-variadic-wrapper-async.ts.json create mode 100644 tests/harness/corpus-costs/6382-variadic-wrapper-methods-this.ts.json create mode 100644 tests/harness/corpus-costs/6383-variadic-wrapper-rest-tuple.ts.json create mode 100644 tests/harness/corpus-costs/6384-variadic-wrapper-nested.ts.json create mode 100644 tests/harness/corpus-costs/6385-variadic-wrapper-shared-local-names.ts.json create mode 100644 tests/harness/corpus-costs/6390-catch-binding-predicate.ts.json create mode 100644 tests/harness/corpus-costs/6391-nullish-function-default.ts.json create mode 100644 tests/harness/corpus-costs/6392-optional-chain-protocol-calls.ts.json create mode 100644 tests/harness/corpus-costs/6393-function-types-any-params.ts.json create mode 100644 tests/harness/corpus-costs/6394-in-key-error-subclasses.ts.json create mode 100644 tests/harness/corpus-costs/6395-generic-object-entries.ts.json create mode 100644 tests/harness/corpus-costs/6396-generic-any-instantiation.ts.json create mode 100644 tests/harness/corpus-costs/6397-event-loop-metrics.ts.json create mode 100644 tests/harness/corpus-costs/6398-conditional-fixed-arity.ts.json create mode 100644 tests/harness/corpus-costs/6400-dyn-error-stack-property-js.js.json create mode 100644 tests/harness/corpus-costs/6400-override-any-base.ts.json create mode 100644 tests/harness/corpus-costs/6401-dyn-error-own-property-names.ts.json create mode 100644 tests/harness/corpus-costs/6401-override-param-variance.ts.json create mode 100644 tests/harness/corpus-costs/6402-dyn-error-own-property-names-js.js.json create mode 100644 tests/harness/corpus-costs/6402-override-js-base/main.ts.json create mode 100644 tests/harness/corpus-costs/6403-error-code-write.ts.json create mode 100644 tests/harness/corpus-costs/6403-error-own-name-order.ts.json create mode 100644 tests/harness/corpus-costs/6404-error-in-and-assign.ts.json create mode 100644 tests/harness/corpus-costs/6404-error-own-name-order-js.js.json create mode 100644 tests/harness/corpus-costs/6405-wrapped-default-parameters.ts.json create mode 100644 tests/harness/corpus-costs/6406-npm-instance-receiver/main.ts.json create mode 100644 tests/harness/corpus-costs/6407-dyn-bytes-in.ts.json create mode 100644 tests/harness/corpus-costs/6408-dyn-bytes-in-js.js.json create mode 100644 tests/harness/corpus-costs/6409-catch-binding-shapes.ts.json create mode 100644 tests/harness/corpus-costs/6410-wrapper-untyped-results.ts.json create mode 100644 tests/harness/corpus-costs/6411-prototype-reset-constructors/main.ts.json create mode 100644 tests/harness/corpus-costs/6412-js-error-tostring-override/main.ts.json create mode 100644 tests/harness/corpus-costs/6413-override-js-async-base/main.ts.json create mode 100644 tests/harness/corpus-costs/6414-catch-values-roundtrip.ts.json create mode 100644 tests/harness/corpus-costs/6415-error-code-dynamic-writes.ts.json create mode 100644 tests/harness/corpus-costs/6440-http-socket-path.ts.json create mode 100644 tests/harness/corpus-costs/6441-http-request-options-record.ts.json create mode 100644 tests/harness/corpus-costs/6442-https-request-options-record.ts.json create mode 100644 tests/harness/corpus-costs/6443-net-lookup-http-close-text.ts.json create mode 100644 tests/harness/corpus-costs/6445-rest-function-fixed-slot.ts.json create mode 100644 tests/harness/corpus-costs/6446-rest-function-nested-declaration.ts.json create mode 100644 tests/harness/corpus-costs/6453-constructor-function-emitter.cjs.json create mode 100644 tests/harness/corpus-costs/6454-constructor-function-stream-bases.cjs.json create mode 100644 tests/harness/corpus-costs/6465-optional-members-over-index-signature.ts.json create mode 100644 tests/harness/corpus-costs/6466-never-assertion-argument.ts.json create mode 100644 tests/harness/corpus-costs/6467-fromentries-record-assertion.ts.json create mode 100644 tests/harness/corpus-costs/6468-generic-unknown-record-entries.ts.json create mode 100644 tests/harness/corpus-costs/6468-node-stdio-writable-slot.ts.json create mode 100644 tests/harness/corpus-costs/6469-node-stdio-writable-js-pipe/main.ts.json create mode 100644 tests/harness/corpus-costs/6470-any-destructuring.ts.json create mode 100644 tests/harness/corpus-costs/6471-any-method-chains.ts.json create mode 100644 tests/harness/corpus-costs/6472-namespace-destructuring/main.ts.json create mode 100644 tests/harness/corpus-costs/6473-any-parameter-values.ts.json create mode 100644 tests/harness/corpus-costs/6474-async-any-callbacks/main.ts.json create mode 100644 tests/harness/corpus-costs/6475-js-promise-resolve-adoption/main.ts.json create mode 100644 tests/harness/corpus-costs/6490-js-named-promise-executor/main.ts.json create mode 100644 tests/harness/corpus-costs/6491-catch-any-bindings.ts.json create mode 100644 tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json create mode 100644 tests/harness/corpus-costs/761-any-objects.ts.json create mode 100644 tests/harness/corpus-costs/cjs-class-hierarchy-npm/main.ts.json create mode 100644 tests/harness/corpus-costs/cjs-open-options-npm/main.ts.json create mode 100644 tests/harness/corpus-costs/cjs-strict-arguments.cjs.json create mode 100644 tests/harness/corpus-costs/cjs-symbol-key-collisions/main.js.json create mode 100644 tests/harness/corpus-costs/cjs-symbol-key-table/main.js.json create mode 100644 tests/harness/corpus-costs/cjs-unmapped-arguments.cjs.json create mode 100644 tests/harness/corpus-costs/code-generation-disallowed.cjs.json create mode 100644 tests/harness/corpus-costs/code-generation-disallowed.ts.json create mode 100644 tests/harness/corpus-costs/comma-null-union-return.cjs.json create mode 100644 tests/harness/corpus-costs/dns-promises-lookup.ts.json create mode 100644 tests/harness/corpus-costs/dns-promises-untyped.cjs.json create mode 100644 tests/harness/corpus-costs/function-local-detached-methods.js.json create mode 100644 tests/harness/corpus-costs/function-local-detached-methods.ts.json create mode 100644 tests/harness/corpus-costs/function-local-private-evaluations.js.json create mode 100644 tests/harness/corpus-costs/function-local-private-fields.js.json create mode 100644 tests/harness/corpus-costs/graceful-fs-alias/main.cjs.json create mode 100644 tests/harness/corpus-costs/local-class-detached-method.ts.json create mode 100644 tests/harness/corpus-costs/object-literal-accessors-dyn.ts.json create mode 100644 tests/harness/corpus-costs/private-in-checked-primitives.js.json create mode 100644 tests/harness/corpus-costs/required-module-truthiness.cjs.json create mode 100644 tests/harness/corpus-costs/stream-web-readable.ts.json create mode 100644 tests/harness/corpus-costs/stream-web-require.cjs.json create mode 100644 tests/harness/corpus-costs/strict-arguments-parameters.cjs.json create mode 100644 tests/harness/corpus-costs/temporal-absent-probe.js.json create mode 100644 tests/harness/corpus-costs/temporal-absent.ts.json create mode 100644 tests/harness/corpus-costs/v8-heap-statistics.ts.json create mode 100644 tests/harness/corpus-costs/v8-require-optional.cjs.json create mode 100644 tests/harness/corpus-costs/webassembly-api.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-dylink/main.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-externref-lifetimes.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-jsapi.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-lifetimes.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-spec-control/main.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-spec-float/main.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-spec-imports/main.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-spec-int/main.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-spec-memory/main.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-spec-table/main.mjs.json create mode 100644 tests/harness/corpus-costs/webassembly-typed.ts.json create mode 100644 tests/harness/corpus-costs/webassembly-worker.ts.json create mode 100644 tests/harness/corpus-costs/webassembly-wpt-jsapi.mjs.json create mode 100644 tests/harness/esm-path-globals.test.ts create mode 100644 tests/harness/lsan.supp create mode 100644 tests/harness/webassembly-memory-growth.test.ts create mode 100644 tests/wasm-spec/corpus/api.wat create mode 100644 tests/wasm-spec/corpus/dylink-ext.wat create mode 100644 tests/wasm-spec/corpus/dylink-main.wat create mode 100644 tests/wasm-spec/corpus/externref-lifetimes.wat create mode 100644 tests/wasm-spec/corpus/jsapi-own.wat create mode 100644 tests/wasm-spec/corpus/jsapi.wat create mode 100644 tests/wasm-spec/corpus/lifetimes.wat create mode 100644 tests/wasm-spec/corpus/linked.wat create mode 100644 tests/wasm-spec/corpus/refs.wat create mode 100644 tests/wasm-spec/gen.mjs create mode 100644 tests/wasm-spec/runner.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f2a6d51d..bbc9ddb10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,21 +4,23 @@ on: push: branches: [main] pull_request: - # Nightly full macOS sweep, including on days without a push to main. + # Nightly full macOS sweep. Pushes to main run no macOS jobs, and pull requests run them only when they change + # darwin code (scripts/ci-lanes.mjs). schedule: - cron: "17 7 * * *" - # A manual run; macos_full runs the complete suite on macOS as the nightly sweep does (for example on a - # release candidate commit). + # A manual run, for example on a release candidate commit. It runs every lane; macos_suite selects the complete + # suite on macOS, as the nightly sweep runs it, or the darwin suite that pull requests run. workflow_dispatch: inputs: - macos_full: - description: Run the complete test suite on macOS - type: boolean - default: false + macos_suite: + description: The test suite to run on macOS + type: choice + options: [full, darwin] + default: full concurrency: # A new push to a pull request cancels its previous run. Every push to main runs to completion in a group of - # its own commit, so each merged commit gets a full result (releases require a green run on their commit). The + # its own commit, so each merged commit gets a result (releases require a green run on their commit). The # nightly sweep and manual runs neither cancel nor are cancelled by main pushes. group: ci-${{ github.event_name == 'schedule' && 'schedule' || github.event_name == 'workflow_dispatch' && format('dispatch-{0}', github.run_id) || github.event.pull_request.number || github.sha }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} @@ -59,7 +61,8 @@ jobs: NODE # Which expensive lanes a pull request needs (scripts/ci-lanes.mjs, run from the base commit against the pull - # request's diff, so a pull request cannot change its own selection). Pushes and the nightly sweep run every lane. + # request's diff, so a pull request cannot change its own selection). Pushes to main run every lane except macos, + # whose jobs the nightly sweep runs on main. The nightly sweep and manual runs run every lane. lanes: needs: validation_scope if: ${{ needs.validation_scope.outputs.full == 'true' }} @@ -86,7 +89,9 @@ jobs: HEAD: ${{ github.event.pull_request.head.sha }} run: | every() { printf 'linux=true\nmacos=true\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT"; } - if [ "$EVENT" != pull_request ]; then + if [ "$EVENT" = push ]; then + printf 'linux=true\nmacos=false\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT" + elif [ "$EVENT" != pull_request ]; then every elif git show "$BASE:scripts/ci-lanes.mjs" > "$RUNNER_TEMP/ci-lanes.mjs" 2> /dev/null; then node "$RUNNER_TEMP/ci-lanes.mjs" --base "$BASE" --head "$HEAD" --github-output @@ -172,7 +177,9 @@ jobs: SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }} # Match macOS, where Apple ASan has no LeakSanitizer: the RC audit owns # leak detection, including intentional abandonment. Plain shards also - # build some sanitized probes, so this applies to both flavors. + # build some sanitized probes, so this applies to both flavors. The + # san shards' case-sharded corpus turns LeakSanitizer on + # (scripts/ci-test-plan.mjs, tests/harness/lsan.supp). ASAN_OPTIONS: detect_leaks=0 # Within-file partition for the large case-driven harnesses # (differential, llvm-differential, npm, server, Test262, coverage, and @@ -217,25 +224,25 @@ jobs: tests_macos: needs: [validation_scope, lanes, native_macos] if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }} - name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && !inputs.macos_full) && '3' || '10' }}) + name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }}) # arm64 macOS: same clang/libc the differential + number-formatting - # oracles were verified against. Pull requests and pushes to main run the - # darwin suite (scripts/ci-test-plan.mjs) in three slices; the nightly - # schedule and manual runs with macos_full run the complete suite in ten. - # The complete suite on every push needed more macOS runners than the - # repository has. + # oracles were verified against. Pull requests that change darwin code and + # manual runs with macos_suite set to darwin run the darwin suite + # (scripts/ci-test-plan.mjs) in three slices; the nightly schedule and + # other manual runs run the complete suite in ten. The complete suite on + # every push needed more macOS runners than the repository has. runs-on: macos-15 strategy: fail-fast: false matrix: flavor: [plain, san] - shard: ${{ fromJSON((github.event_name != 'schedule' && !inputs.macos_full) && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }} + shard: ${{ fromJSON((github.event_name != 'schedule' && inputs.macos_suite != 'full') && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }} env: # See the Linux tests job for the worker cap and case partitioning. SCRIPTC_TEST_WORKERS: "4" SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }} - SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && !inputs.macos_full) && '3' || '10' }} - SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin' || 'full' }} + SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }} + SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin' || 'full' }} steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -252,7 +259,7 @@ jobs: path: ${{ runner.temp }} - run: tar -xzf "$RUNNER_TEMP/native-darwin-arm64.tar.gz" - run: pnpm build - - name: Tests (${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin suite' || 'corpus and integration suites' }}) + - name: Tests (${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin suite' || 'corpus and integration suites' }}) run: node scripts/ci-test.mjs native_linux: @@ -496,6 +503,30 @@ jobs: - name: TypeScript 7 full parity sweep run: pnpm test:ts7 + # tsc-ts, the default checker, and tsgo (SCRIPTC_CHECKER=tsgo) serve the same TypeScript 7 API. Every corpus + # program must compile to the same LLVM IR, with the same diagnostics, with either checker. + checker_identity: + needs: [validation_scope, lanes] + if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }} + name: test (tsgo and tsc-ts give the same IR) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.1.3 + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm build + - name: Corpus LLVM IR with tsgo and with tsc-ts (release) + run: pnpm test:checker-identity --optimization release + - name: Corpus LLVM IR with tsgo and with tsc-ts (dev) + run: pnpm test:checker-identity --optimization dev + llvm_artifacts_linux_x64: needs: [validation_scope, lanes] if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }} @@ -842,14 +873,25 @@ jobs: strategy: fail-fast: false matrix: - include: - - runtime: runtime-ios-arm64 - runner: macos-15 - - runtime: runtime-ios-simulator-arm64 - runner: macos-15 - - runtime: runtime-android-arm64 - runner: ubuntu-24.04 - runs-on: ${{ matrix.runner }} + runtime: [runtime-android-arm64] + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/mobile-runtime-pack + with: + runtime: ${{ matrix.runtime }} + + # The iOS runtime packs build on macOS runners, so they run in the macos lane. + mobile_runtime_packs_ios: + needs: [validation_scope, lanes] + if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }} + name: test (${{ matrix.runtime }}) + strategy: + fail-fast: false + matrix: + runtime: [runtime-ios-arm64, runtime-ios-simulator-arm64] + runs-on: macos-15 timeout-minutes: 20 steps: - uses: actions/checkout@v4 @@ -861,7 +903,29 @@ jobs: # Require this check on the stack base. Upper layers must not pass before # full validation, so untested groups cannot merge. Merge bottom-up. test: - needs: [validation_scope, lanes, code_quality, native_macos, tests, tests_macos, native_linux, effects, bootstrap_seed, bootstrap, linux_host_clang, ts7_parity, llvm_artifacts_linux_x64, llvm_artifacts_macos_arm64, llvm_artifacts_macos_x64, windows_cli, llvm_artifacts_windows_x64, mobile_runtime_packs] + needs: + [ + validation_scope, + lanes, + code_quality, + native_macos, + tests, + tests_macos, + native_linux, + effects, + bootstrap_seed, + bootstrap, + linux_host_clang, + ts7_parity, + checker_identity, + llvm_artifacts_linux_x64, + llvm_artifacts_macos_arm64, + llvm_artifacts_macos_x64, + windows_cli, + llvm_artifacts_windows_x64, + mobile_runtime_packs, + mobile_runtime_packs_ios, + ] # Run even after failed or cancelled lanes: skipped required jobs allow merging. if: ${{ always() }} runs-on: ubuntu-latest @@ -885,12 +949,14 @@ jobs: lane bootstrap "${{ needs.bootstrap.result }}" "${{ needs.lanes.outputs.bootstrap }}" lane linux_host_clang "${{ needs.linux_host_clang.result }}" "${{ needs.lanes.outputs.linux }}" lane ts7_parity "${{ needs.ts7_parity.result }}" "${{ needs.lanes.outputs.linux }}" + lane checker_identity "${{ needs.checker_identity.result }}" "${{ needs.lanes.outputs.linux }}" lane llvm_artifacts_linux_x64 "${{ needs.llvm_artifacts_linux_x64.result }}" "${{ needs.lanes.outputs.linux }}" lane llvm_artifacts_macos_arm64 "${{ needs.llvm_artifacts_macos_arm64.result }}" "${{ needs.lanes.outputs.macos }}" lane llvm_artifacts_macos_x64 "${{ needs.llvm_artifacts_macos_x64.result }}" "${{ needs.lanes.outputs.macos }}" lane windows_cli "${{ needs.windows_cli.result }}" "${{ needs.lanes.outputs.linux }}" lane llvm_artifacts_windows_x64 "${{ needs.llvm_artifacts_windows_x64.result }}" "${{ needs.lanes.outputs.linux }}" lane mobile_runtime_packs "${{ needs.mobile_runtime_packs.result }}" "${{ needs.lanes.outputs.linux }}" + lane mobile_runtime_packs_ios "${{ needs.mobile_runtime_packs_ios.result }}" "${{ needs.lanes.outputs.macos }}" ;; false) echo "::notice::Full CI is deferred until this PR becomes the lowest unmerged layer. Merge the stack from the bottom up." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47329cc28..fff1931ae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -219,6 +219,12 @@ jobs: Move-Item "$expanded\clang+llvm-22.1.8-x86_64-pc-windows-msvc" $llvm echo "LLVM_DIR=$llvm\lib\cmake\llvm" >> $env:GITHUB_ENV echo "$llvm\bin" >> $env:GITHUB_PATH + # A musl CLI ships tsc-ts's musl executable, and these jobs run on glibc + # runners, where pnpm installs only the glibc platform packages. + - name: Let pnpm install musl platform packages + if: endsWith(matrix.cli_target, '-musl') + run: | + printf 'supportedArchitectures:\n libc: [current, musl]\n' >> pnpm-workspace.yaml - run: pnpm install --frozen-lockfile - name: Build native packages (POSIX) if: matrix.platform != 'windows-x64' diff --git a/CHANGELOG.md b/CHANGELOG.md index 3de3f334f..c319a1b53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,73 @@ All notable changes to scriptc will be documented in this file. +## 0.4.0 + +### Breaking changes + +- **tsc-ts is the default TypeScript 7 checker** on Linux and macOS. scriptc installs it with the compiler (`tsc-ts` 0.0.5 and its platform package for the host). Programs get the same diagnostics and generated code as with tsgo, and compiling takes less checker memory. `SCRIPTC_CHECKER=tsgo` selects tsgo, which stays the default on Windows. A build cache entry made with one checker isn't reused with the other. +- **`--npm-static=auto`, the default, compiles more packages:** packages typed by their DefinitelyTyped twin (`@types/`), and untyped dependencies that another package's JavaScript imports. A program whose dependencies were refused before can now compile them, and a twin whose declarations disagree with the package's JavaScript fails the build at the use. Use `--npm-static ` to name the packages to compile. +- **Program type errors are reported as type errors** instead of as refusals of the npm package that contains them. + +### Highlights + +- **WebAssembly:** builds translate every WebAssembly module whose bytes they can see into native code, and `WebAssembly.compile`, `instantiate`, `validate`, `new WebAssembly.Module` and `new WebAssembly.Instance` run them. There is no interpreter. `WebAssembly.Memory`, `Table` and `Global`, `Module.imports`, `Module.exports` and `Module.customSections`, and function references from tables are supported, and modules can be sent to workers. `scriptc coverage` lists the modules a program loads, with `--wasm ` for modules loaded from paths the sources don't spell out. +- **Async functions that suspend only at their `await`s run as stackless coroutines** instead of on fibers with their own stack, so a suspended call keeps only the values live across its awaits. +- **Discriminated unions of records are the record pointer itself,** with no box around each value. +- **The dynamic tier computes with NaN-boxed values:** numbers, booleans, `undefined` and `null` between operators no longer allocate. `scriptc coverage --explain-dynamic` lists the functions that run in the dynamic tier and why, and `--tier=static-only` keeps a program fully static. +- **npm packages:** esbuild's bundled CommonJS chunks, CommonJS cycles and lazily initialized ES modules compile statically, as do classic CommonJS packages (late and JSON `require`s, require cycles), class hierarchies across a package's modules, and the packages unblocked by type-only imports, `@types` packages, `punycode` and `constants`. chalk 5 and the jose root entry compile. + +### Features + +- **`@scriptc/experimental` adds `ThreadSlots`,** one value per key and thread on shared objects, read without a lock. +- **`node:test` adds `mock.timers`, `getTestContext`, `t.assert.snapshot` and `t.assert.fileSnapshot`,** the `TestContext` values `filePath`, `attempt`, `passed`, `workerId` and `tags`, module-level `before` and `after` hooks, the `SuiteContext` and hook contexts, and unnamed `test` and `describe` calls. A timed-out test cancels its running subtests. +- **`node:worker_threads` adds `BroadcastChannel`,** `setEnvironmentData`/`getEnvironmentData`, `receiveMessageOnPort`, `markAsUntransferable`, `markAsUncloneable`, `threadName`, `resourceLimits.stackSizeMb`, the `stdout`/`stderr`/`stdin` options and streams, and `worker[Symbol.asyncDispose]`. +- **`node:net` adds `SocketAddress` and `BlockList`,** and `net.connect` resolves host names. +- **`node:http` pools keep-alive sockets in `Agent`s,** uses `globalAgent` for default requests, reads `http.request` options at run time, and speaks HTTP over Unix domain sockets. `fetch` tunnels through `HTTP_PROXY`/`HTTPS_PROXY` with `CONNECT`. +- **`DisposableStack`, `Float16Array`, `Error.isError`, `Promise.try` and the regular expression `v` flag** are supported. +- **Iterator helpers** run on generators, `Map` and `Set` iterators, string and typed array iterators, `Iterator.from` and untyped iterators. +- **`Object.groupBy` and `Map.groupBy`** group Sets, Maps and generators. +- **`v8`, `stream/web`, `dns/promises`, `EvalError` and `graceful-fs`** are available, and `Temporal` is reported as absent, as in Node 24. +- **`util.promisify`** works over the crypto callback functions, dns lookups, `child_process.exec`'s callback forms and callback-style functions. +- **`events.once`** takes an `AbortSignal` and works over streams. +- **`AsyncLocalStorage` stores** carry into timer, immediate, `process.nextTick` and microtask callbacks. +- **JavaScript classes compile like TypeScript classes** when their layout or JSDoc types allow it. +- **Classes that extend `EventEmitter` or a stream class can be used as values,** and so can `EventEmitter`, the stream classes and the error classes themselves. Constructor functions can inherit from them. +- **Class instances can be frozen and their fields redefined.** +- **Method overrides may differ from the method they override** in parameter and result types, as long as each value converts. +- **Library profiles add the `cstring` class.** +- **An experimental young-object nursery** for compiled classes and records is available at compile time with `SCRIPTC_YOUNG_NURSERY=1`. + +### Performance + +- **Dense arrays have head room,** so `shift`, `splice(0, n)` and `unshift` runs cost amortized O(1), and `pop` of a packed array is inline. +- **Index loops over arrays and typed arrays are versioned on one bounds guard,** with stable lengths proven and typed-array memory tagged for alias analysis. +- **Retain and release pairs are removed** through private-array precision and last-use moves, borrowed string slices go into stack buffers, and records, tuples and spread updates stay in frame storage. +- **SHA-384 and SHA-512 use AVX2, AVX-512VL or the arm64 SHA-512 instructions,** and MD5's block function is unrolled. +- **String character tests, optional-string `switch`es, `String.fromCharCode` and long-string `length` are faster,** and `Buffer.write` widens ASCII to UTF-16 sixteen bytes per step. +- **The cycle collector walks dead cycles once,** frees large dead cycle sets in address order and no longer creates self-capture cycles on the release fast path. +- **Workers return their memory** while the event loop waits and when they finish, and default-region memory is committed as it is used. +- **Lifetime regions carry across `await`,** and freed region chunks are reused. +- **Large closure environments compile in linear time,** and two frontend scans no longer repeat. +- **Programs keep stack frames only when they can read `stack`.** Before, a computed-key read on a dynamic value kept frames in every function; tsc-ts's own check is back to 0.3.6's speed. +- **The child-process runtime unit links only into programs that spawn.** + +### Fixes + +- **Node 24 parity:** `util.format`, `util.types` and `util.inspect`, `URLSearchParams`, `querystring` (null-prototype results), `string_decoder`, `os`, `events` and `timers/promises` match Node 24. Class instances, iterators, iterator helper objects and holes in arrays print as Node prints them, and `%d`/`%i` format dynamic arguments as Node does. +- **Silent wrong output is fixed** for object identity, `Object.prototype` values, `Date.parse` (V8's date grammar), stored builtins, constructor returns, `arr && arr[0]`, ES5 class names, builtin function values, uninitialized `let` writes, module-export `this` and `1 ** Infinity`. +- **Strings compare in UTF-16 code-unit order.** +- **Dates format with Node's local time zone names,** convert to local time once for their local getters, and follow `TZ` changes. `Date.prototype.toJSON` gives `null` for invalid dates. +- **Errors:** thrown Maps, Sets, typed arrays, functions and arrays keep their identity. Errors list their own name where they were created, errors held as `any` have their own `stack` accessor and convert with `Error.prototype.toString`, and errors that cross into dynamic code carry Node's error properties. +- **`matchAll` rows and `exec` results have `index`, `input` and `groups`,** and named groups keep their null prototype through optional chains. +- **Unhandled rejections are reported after each timer and immediate callback,** and async generator completions settle on Node's microtask turns. +- **Missing `Record` and index-signature keys read as `undefined`,** and generic functions that read a missing element return `undefined`. +- **Sockets:** `'data'` listeners get strings after `setEncoding`, socket and stream chunks are Buffers, and an idle pooled http socket that receives data is destroyed. +- **Diagnostic code frames window long source lines.** +- **npm crashes, internal errors and false TDZ errors** found while compiling popular packages are fixed. + + + ## 0.3.6 ### Features @@ -38,8 +105,6 @@ All notable changes to scriptc will be documented in this file. - **`Object.setPrototypeOf` returns primitive receivers,** and `createHmac` without a key throws Node's error. - **Boxed records that hold an async function link the dynamic promise unit.** - - ## 0.3.5 ### Features diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index 6fe670e1e..f8552b003 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -18,6 +18,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `typed-array-scan`, `typed-array-scan-workers` | cpu | element-by-element scan loops over `Uint8Array`, `Uint16Array`, `Int32Array` and `Float64Array`; the second entry is a worker-enabled executable (it can start a worker), so each access also checks for shared storage | | `intern-table`, `intern-table-map`, `intern-table-1`, `intern-table-map-1` | cpu | worker threads interning the same type-like objects under string keys and records under number keys, then hits and misses: one shared `InternTable` per kind of key, or a module-level `Map` per thread (the `-map` entries), with 8 threads or 1 (the `-1` entries); under Node.js every thread has its own tables | | `thread-slots`, `thread-slots-cache` | cpu | worker threads reading one published type graph from an `InternTable` and keeping per-thread state for each type they visit: a `ThreadSlots` family, or a direct-mapped cache by id in front of a `Map` by id (the `-cache` entry); under Node.js both are per thread | +| `channel-messages` | cpu | 200,000 MessageChannel messages in one thread, half numbers and half small objects, all posted before the port delivers any | | `atomics-contended` | cpu | four worker threads updating `SharedArrayBuffer` counters with `Atomics`: one shared counter, one counter per worker, a `compareExchange` loop, and `Atomics.wait`/`notify` handoffs | | `value-objects` | cpu | small immutable records and class instances: vector helpers, method chains, particle updates | | `value-records` | cpu | records that hold strings, spread state updates and destructured tuples: token helpers, an immutable reducer, tuple returns | diff --git a/benchmarks/runtime/workloads.json b/benchmarks/runtime/workloads.json index 76d27fbc2..9a3f1bc2f 100644 --- a/benchmarks/runtime/workloads.json +++ b/benchmarks/runtime/workloads.json @@ -147,6 +147,13 @@ "20" ] }, + { + "name": "channel-messages", + "entry": "workloads/channel-messages.ts", + "args": [ + "200000" + ] + }, { "name": "atomics-contended", "entry": "workloads/atomics-contended.ts", diff --git a/benchmarks/runtime/workloads/channel-messages.ts b/benchmarks/runtime/workloads/channel-messages.ts new file mode 100644 index 000000000..5d11adee4 --- /dev/null +++ b/benchmarks/runtime/workloads/channel-messages.ts @@ -0,0 +1,21 @@ +// MessageChannel messages within one thread: the program posts the given +// number of messages, half of them numbers and half small objects, before +// the port delivers any, so all of them are queued at once. Argument: the +// number of messages. +import { MessageChannel } from "node:worker_threads"; + +const count = Number(process.argv[2] ?? "200000"); +const { port1, port2 } = new MessageChannel(); +let received = 0; +let sum = 0; +port1.on("message", (value: number | { id: number; name: string }) => { + sum += typeof value === "number" ? value : value.id + value.name.length; + if (++received === count) { + console.log(`${count} messages, sum ${sum}`); + port1.close(); + } +}); +for (let i = 0; i < count; i++) { + if (i % 2 === 0) port2.postMessage(i); + else port2.postMessage({ id: i, name: "item" }); +} diff --git a/docs/content/docs/cli.mdx b/docs/content/docs/cli.mdx index c4fdf7876..786cf2c63 100644 --- a/docs/content/docs/cli.mdx +++ b/docs/content/docs/cli.mdx @@ -134,7 +134,7 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
Coverage only. After the report, list the functions that already operate on values typed only at runtime and the bindings, fields, functions, classes and modules that unsupported constructs would move to the dynamic tier, each with the diagnostic behind it. With a name such as x, Point, or Point.x, show only the matching units. See Dynamic tier.
--wasm <file.wasm>
-
Coverage only. Report a WebAssembly module the program loads from a path its sources do not spell out. The report already lists modules whose paths the compiler resolves, modules inlined as base64 literals, and .wasm files shipped by packages that use WebAssembly; a scriptc.wasm array in package.json adds files the same way as this flag. Each module is listed with its size, whether it validates, and the WebAssembly proposals it uses. scriptc does not compile WebAssembly modules to native code yet. Repeatable.
+
Coverage only. Report a WebAssembly module the program loads from a path its sources do not spell out. The report already lists modules whose paths the compiler resolves, modules inlined as base64 literals, and .wasm files shipped by packages that use WebAssembly; a scriptc.wasm array in package.json adds files the same way as this flag. Each module is listed with its size, whether it validates, and the WebAssembly proposals it uses. Builds translate the modules they find to native code; a build reads the scriptc.wasm array but not this flag. Repeatable.
--sanitize
Build an executable with AddressSanitizer and a reference-count audit at exit, the same checks scriptc's own test suite uses. Requires a C compiler and is available only for host builds. Not supported with --emit=asm or --emit=obj.
@@ -178,6 +178,9 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
SCRIPTC_RUNTIME_PACK
A runtime pack directory to use instead of an installed @scriptc/runtime-<target> package.
+ +
SCRIPTC_CHECKER
+
The TypeScript 7 API server that checks the program: tsc-ts (the default), which scriptc includes, or tsgo, from the typescript package scriptc also includes. Both give the same diagnostics and generated code. On Windows the default is tsgo, because tsc-ts's API server does not yet accept the named pipe scriptc connects over there.
```console diff --git a/docs/content/docs/coverage.mdx b/docs/content/docs/coverage.mdx index b27327b91..1bb645bd9 100644 --- a/docs/content/docs/coverage.mdx +++ b/docs/content/docs/coverage.mdx @@ -79,7 +79,6 @@ Some code compiles but can behave differently than under Node.js. Coverage repor - `SC6002`: values of one record type are built in different key orders, so the type keeps its declaration order, an object literal's key order differs from it, and the program observes the order through `JSON.stringify`, `Object.keys`, `Object.values`, `Object.entries`, `for...in`, or console output. - `SC6003`: `localeCompare` compares a string literal outside the built-in Latin-script collation. - `SC6004`: a call passes a class instance to a function that casts it to an unrelated class. -- `SC6005`: a `Date` string literal uses a form the native parser does not accept. ## Gating builds and CI diff --git a/docs/content/docs/dependencies.mdx b/docs/content/docs/dependencies.mdx index 3c6958841..b16c14e7d 100644 --- a/docs/content/docs/dependencies.mdx +++ b/docs/content/docs/dependencies.mdx @@ -52,10 +52,14 @@ Type-only imports follow Node.js's type stripping. `import type` and `export typ scriptc compiles a package automatically when it: -- ships its own type declarations, and -- ships readable, unminified JavaScript. +- ships its own type declarations, or is typed by its DefinitelyTyped `@types/` package, or is a dependency of another package that has no declarations at all, and +- ships readable JavaScript: unminified, or minified by a bundler whose helpers keep their names. -A package that doesn't meet these requirements, or that uses something scriptc can't compile, gets a package-specific diagnostic that explains why. The usual fixes are to switch to a version or alternative package that compiles, or to replace the call with code you write yourself. A package whose declarations come from a separate `@types/` package doesn't ship its own; its diagnostic names the package and the `--npm-static` option that compiles it anyway. +The declarations check your program; the package's JavaScript compiles with the types scriptc infers from it. A program that doesn't typecheck against the inferred types, for example because an `@types/` package describes another release, fails the build at that use. + +A package that doesn't meet these requirements, or that uses something scriptc can't compile, gets a package-specific diagnostic that explains why. The usual fixes are to switch to a version or alternative package that compiles, or to replace the call with code you write yourself. + +`graceful-fs` is the exception: it compiles as `node:fs` instead of from its JavaScript, with one [documented difference](/docs/limitations#graceful-fs). @@ -63,17 +67,17 @@ A package that doesn't meet these requirements, or that uses something scriptc c ## Explicit package selection -Some packages rely on separately installed declarations, such as an `@types/` package, or on types inferred from their JavaScript. To try compiling one of them, name it with `--npm-static`: +Some packages your program imports directly ship no declarations, so their types can only be inferred from their JavaScript. To try compiling one of them, name it with `--npm-static`: ```console $ scriptc build app.ts --npm-static=three -o app >/dev/null ``` -Naming a package skips the declaration requirement above, but the package still has to compile. The option selects only the packages it names: a dependency of a named package that doesn't ship its own declarations is still refused. Add a trailing `...` to select a package together with every package it depends on, for example `--npm-static=fs-extra...`. `--npm-static=auto` uses the default automatic selection. The option accepts a comma-separated list and can be repeated. +Naming a package skips the declaration requirement above, but the package still has to compile. Add a trailing `...` to select a package together with every package it depends on, for example `--npm-static=fs-extra...`. `--npm-static=auto` uses the default automatic selection. The option accepts a comma-separated list and can be repeated. ## CommonJS packages -CommonJS packages compile with Node.js's module semantics. Each `require()` evaluates its module at the call, once, and later calls return the cached exports. A package can assign `module.exports` and run code before it requires its dependencies. If that code reads a `const` or `let` require binding before its `require` runs, the read throws the same `ReferenceError` as Node.js. A `var` binding that earlier code can reach is reported at build time. A `require()` of a JSON file, such as a package's own `package.json`, returns one object parsed at build time, shared by every module that requires it. A require cycle compiles when each of its modules assigns its exports before requiring anything, as semver's classes do, and uses the other modules only inside functions that its top-level code can't call. Other require cycles are reported at build time. The [limitations](/docs/limitations) page lists the supported `require` forms. +CommonJS packages compile with Node.js's module semantics. Each `require()` evaluates its module at the call, once, and later calls return the cached exports. A package can assign `module.exports` and run code before it requires its dependencies. If that code reads a `const` or `let` require binding before its `require` runs, the read throws the same `ReferenceError` as Node.js. A `var` binding that earlier code can reach is reported at build time. A `require()` of a JSON file, such as a package's own `package.json`, returns one object parsed at build time, shared by every module that requires it. A require cycle compiles when each of its modules assigns its exports before requiring anything, as semver's classes and esbuild's per-file output do, and uses the other modules only inside functions that its top-level code can't call. Bundles built by esbuild compile with their bundled CommonJS modules and lazily initialized ES modules, each evaluated once, at its first use, as under Node.js. Other require cycles are reported at build time. The [limitations](/docs/limitations) page lists the supported `require` forms. ## Unsupported code inside a package diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index d261b6385..823b7f028 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -102,6 +102,8 @@ title: "Limitations" ../limitations/builtin-apis/process-memory.mdx +../limitations/builtin-apis/v8.mdx + ../limitations/builtin-apis/performance-entries.mdx ../limitations/builtin-apis/builtin-module-lookup.mdx @@ -212,6 +214,10 @@ title: "Limitations" ../limitations/console-and-process-streams.mdx +### graceful-fs + +../limitations/graceful-fs.mdx + ### DNS lookup errors ../limitations/dns-lookup-errors.mdx @@ -224,10 +230,18 @@ title: "Limitations" ../limitations/udp-socket-options.mdx +### Unix domain sockets and request options + +../limitations/unix-domain-sockets.mdx + ### Process arguments and errors ../limitations/process-arguments-and-errors.mdx +### Module path globals + +../limitations/module-path-globals.mdx + ## Runtime code evaluation diff --git a/docs/content/limitations/builtin-apis/date.mdx b/docs/content/limitations/builtin-apis/date.mdx index 4c1c7e034..ac8bf9115 100644 --- a/docs/content/limitations/builtin-apis/date.mdx +++ b/docs/content/limitations/builtin-apis/date.mdx @@ -1,5 +1,5 @@ -- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale formatters are unsupported. - Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. +- **Date:** Date unions, spread constructor arguments, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale formatters are unsupported. + Zero-argument, single number/string, and local-time year/month field constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`. - See [Dates and timezones](#dates-and-timezones) for parsing restrictions. + See [Dates and timezones](#dates-and-timezones) for timezone data. diff --git a/docs/content/limitations/builtin-apis/eventemitter.mdx b/docs/content/limitations/builtin-apis/eventemitter.mdx index 4f7142094..a9f03b857 100644 --- a/docs/content/limitations/builtin-apis/eventemitter.mdx +++ b/docs/content/limitations/builtin-apis/eventemitter.mdx @@ -1,4 +1,5 @@ - **EventEmitter:** Literal event names use fixed argument tuples. Computed string names require a constant prefix or suffix that excludes `error`, meta events, and stream-internal events; their arguments must match the exact arity and support checked conversion. Unrestricted names, symbols, names that may reach internal events, and typed `listeners()`/`rawListeners()` results for names with possible computed registrations are unsupported. + `events.once` accepts EventEmitter and stream instances with a literal event name and an optional object-literal `{ signal }`; a stream's `data` event, other options values, and EventTarget emitters are unsupported. Reading `defaultMaxListeners` through a CommonJS destructured or member binding (`const { defaultMaxListeners } = require("events")`) is unsupported. diff --git a/docs/content/limitations/builtin-apis/exit-status.mdx b/docs/content/limitations/builtin-apis/exit-status.mdx index d9bb779db..3cdf10496 100644 --- a/docs/content/limitations/builtin-apis/exit-status.mdx +++ b/docs/content/limitations/builtin-apis/exit-status.mdx @@ -1,3 +1,4 @@ - **Exit status:** Numeric `process.exitCode = value` writes in statement position set the implicit exit status, which `process.exit()` uses when called without an argument. `process.exit(code)` and `process.exitCode` throw a `RangeError` (`ERR_OUT_OF_RANGE`) for a code that is not a safe integer, and convert other codes to int32, as Node does. - Reads, resets, and assignments used as values are unsupported. + Reading `process.exitCode` answers the assigned code, or `undefined` before any assignment; `process.exit(code)` assigns it before the `'exit'` listeners run. + Resets (`process.exitCode = undefined`) and assignments used as values are unsupported. diff --git a/docs/content/limitations/builtin-apis/globals.mdx b/docs/content/limitations/builtin-apis/globals.mdx index 5d8f70969..f875bd761 100644 --- a/docs/content/limitations/builtin-apis/globals.mdx +++ b/docs/content/limitations/builtin-apis/globals.mdx @@ -1,2 +1,4 @@ - **Globals:** Stored `globalThis` references support identity and probes for absent capabilities. Most builtin members require direct global access; access through a stored global object can raise `SC2020`. + `Temporal` is not defined, as in Node.js 24: `typeof Temporal` is `"undefined"`, `globalThis.Temporal` is `undefined`, and any other use throws `ReferenceError: Temporal is not defined`. + Programs that name it type-check against TypeScript's `Temporal` declarations. diff --git a/docs/content/limitations/builtin-apis/object-freezing.mdx b/docs/content/limitations/builtin-apis/object-freezing.mdx index 9784cd715..38f0155c0 100644 --- a/docs/content/limitations/builtin-apis/object-freezing.mdx +++ b/docs/content/limitations/builtin-apis/object-freezing.mdx @@ -1,3 +1,8 @@ - **Object freezing:** `Object.freeze` and `Object.isFrozen` support plain checked dictionaries and primitives. Freezing is shallow and preserves accessors. Aliased typed records, arrays, proxies, and other native objects cannot be frozen. + Class instances support `Object.freeze`, `Object.seal`, `Object.preventExtensions`, `Object.isFrozen`, `Object.isSealed`, and `Object.isExtensible` when the static type of the argument names the class. + A write to a read-only field throws Node's `TypeError` in strict mode code and leaves the field unchanged in sloppy mode code. + `Object.defineProperty` with a string-literal key changes the value, `writable`, `enumerable`, and `configurable` attributes of a declared field; an object-literal accessor descriptor (`get` or `set`) for a declared field is refused at compile time with `SC2020`, and another accessor descriptor for a declared field throws `SC2020` when the call runs. + Freezing, sealing, or preventing extensions of a class instance held in an untyped value throws `SC2020`. + A JavaScript class field that is first created after its instance became non-extensible is added instead of throwing. diff --git a/docs/content/limitations/builtin-apis/performance-entries.mdx b/docs/content/limitations/builtin-apis/performance-entries.mdx index 92af98cb7..a42940b68 100644 --- a/docs/content/limitations/builtin-apis/performance-entries.mdx +++ b/docs/content/limitations/builtin-apis/performance-entries.mdx @@ -1,2 +1,4 @@ - **Performance entries:** `performance.mark()`, `measure()`, and the `getEntries` methods return entries with Node's fields, `toJSON()`, inspection, and identity. `Object.keys`, `for...in`, object spread, and `hasOwnProperty` on an entry are unsupported, and an entry stored in an `unknown` slot prints as a plain object. +- **Event-loop metrics:** `performance.eventLoopUtilization()` counts the main thread's CPU time since the event loop started as active time, so blocking system calls count as idle where Node counts them as active. + `monitorEventLoopDelay()` supports `enable()`, `disable()`, `reset()`, `percentile()`, and the `min`, `max`, `mean`, `stddev`, `count`, and `exceeds` readings; the BigInt readings, `percentiles`, and non-literal option objects are unsupported. diff --git a/docs/content/limitations/builtin-apis/prototypes.mdx b/docs/content/limitations/builtin-apis/prototypes.mdx index 4ac2ee438..3e0b5789d 100644 --- a/docs/content/limitations/builtin-apis/prototypes.mdx +++ b/docs/content/limitations/builtin-apis/prototypes.mdx @@ -1,2 +1,6 @@ - **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported. + Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly. + Named reads such as `value.valueOf` answer these inherited members; computed-key reads such as `value[key]` do not. + `Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported. + The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called, and `instanceof` and `new` against them remain unsupported. diff --git a/docs/content/limitations/builtin-apis/streams-and-readline.mdx b/docs/content/limitations/builtin-apis/streams-and-readline.mdx index cdd5df47e..a21f7a617 100644 --- a/docs/content/limitations/builtin-apis/streams-and-readline.mdx +++ b/docs/content/limitations/builtin-apis/streams-and-readline.mdx @@ -1,4 +1,10 @@ - **Streams and readline:** Stream chunks are Buffers or strings. + `NodeJS.ReadableStream` and `NodeJS.WritableStream`, and `stream.Readable` and `stream.Writable` under `@types/node`, accept every stream: when a program passes file, zlib, or `node:stream` streams through them, child-process pipes and `process.stdout` and `process.stderr` join those streams as `Readable` and `Writable` instances. + `readable.pipe(process.stdout)` and `readable.pipe(process.stderr)` compile and, as in Node, do not end the process stream. + When an untyped value reaches `NodeJS.ReadableStream` or `NodeJS.WritableStream`, those two interfaces hold any value, so JavaScript stream objects (constructor-function streams, plain objects with `on` and `pipe`) pass through TypeScript functions typed with them and come back to JavaScript as the same objects. + Member calls on such a value use the native stream when it is one and call the object's own members otherwise; `readable.pipe()` into a JavaScript destination follows Node's pipe protocol. + A `pipeline` stage and a `for await` source that arrive as untyped values must be native streams at run time; other values throw a not-supported error. + Native streams expose no `emit` to JavaScript, so a JavaScript stream whose own `pipe()` emits on its destination (the legacy `Stream` pipe) cannot pipe into one. `Readable.from` accepts arrays, Sets, Map and Set iterators, generators, async generators, and class iterables and async iterables whose values are strings or Buffers, and in JavaScript any iterable or async iterable of them; a stream that mixes strings and Buffers is unsupported. In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported. Object-mode streams of other values (including `Readable.from` over a `Map`, whose values are entry arrays) are unsupported. @@ -11,6 +17,7 @@ `stream.compose` accepts two or more streams, or one `Duplex`; functions, iterables, and web streams as stages are unsupported, and the `ERR_INVALID_ARG_VALUE` message for a stream that is no longer readable or writable names the stream's class instead of printing the stream. `pipeline` from `node:stream/promises` accepts a trailing `{ signal, end }` object, and `finished` accepts `readable`, `writable`, `error`, `cleanup`, and `signal` options, with boolean literals for the flags. Stream constructors accept a `signal` option in an options object literal, and `stream.addAbortSignal` accepts any stream. + A `transform` or `flush` option callback whose completion callback types `data` as `any`, as @types/node's `TransformCallback` does, may pass a Buffer, a string, or nothing; any other value throws a TypeError where the callback is called, instead of Node's `ERR_INVALID_ARG_TYPE` stream error. `node:readline` and `node:readline/promises` (also reached as `readline.promises`) interfaces, from `createInterface(options)` or `createInterface(input, output, completer, terminal)`, read `process.stdin` or a readable stream: `question`, `prompt`, `write` (with a key object literal of `name`, `ctrl`, `meta`, and `shift`), `pause`, `resume`, `'line'`/`'close'`/`'pause'`/`'resume'`/`'error'`/`'history'`/`'SIGINT'`/`'SIGTSTP'`/`'SIGCONT'` listeners, and `for await` compile. The `history`, `historySize`, `removeHistoryDuplicates`, `completer`, `tabSize`, and `escapeCodeTimeout` options are validated as in Node. With `terminal: true`, or with no `terminal` option and `process.stdout` as the output when it is a TTY, the interface runs Node's terminal mode: the editing keys, the kill ring, undo and redo, history recording and recall, Tab completion through the completer, Ctrl+C, Ctrl+D, and Ctrl+Z, `TERM=dumb`, and the same escape sequences written to the output. @@ -18,7 +25,12 @@ The cursor never stops between the two UTF-16 units of a supplementary character: where Node's Up or Down movement through a multiline line would, the cursor moves to the character's start or end. In JavaScript, a `'history'` listener receives a copy of the history; its changes are written back when the listener returns, but the copy is not `rl.history` itself. `rl.history`, `historySize`, `historyIndex`, `crlfDelay`, `tabSize`, `escapeCodeTimeout`, `line`, `cursor`, and `terminal` read as in Node, except that `rl.cursor` of an interface that is not a terminal throws instead of answering `undefined`. + `rl[Symbol.dispose]()` closes the interface, and `instanceof` with `readline.Interface` or `readlinePromises.Interface` answers whether the interface came from that module, as in Node. + In JavaScript, the input and output can be any expression that evaluates to a stream, such as a function's result or an object's member; a value that is not a stream is unsupported. + In JavaScript, `question()` and `setPrompt()` accept a value that is not a string: when such a prompt is written, an interface that is not a terminal throws `ERR_INVALID_ARG_TYPE` as Node does (the `readline/promises` question rejects with it) and the question stays pending, and a terminal interface writes `String(value)`. + A terminal interface's prompt that is an object or a symbol, `getPrompt()` while the prompt is not a string, and `getCursorPos()` with an object or symbol prompt are unsupported. `readline.cursorTo`, `moveCursor`, `clearLine`, and `clearScreenDown` write to `process.stdout`, `process.stderr`, a writable stream, or nothing for `null` and `undefined`; their callbacks take no parameters. The `readline/promises` `Readline` class writes to the same outputs. `readline.emitKeypressEvents` emits `'keypress'` events on `process.stdin` or a readable stream, shared with terminal interfaces on that stream as in Node; a listener's first parameter must admit `undefined`, which it receives for escape sequences. Chained listener registration is unsupported. + `node:stream/web` exports the same `ReadableStream` class as the global; its other classes are unsupported. diff --git a/docs/content/limitations/builtin-apis/test-mocks.mdx b/docs/content/limitations/builtin-apis/test-mocks.mdx index abfdb3657..903a873b9 100644 --- a/docs/content/limitations/builtin-apis/test-mocks.mdx +++ b/docs/content/limitations/builtin-apis/test-mocks.mdx @@ -3,7 +3,8 @@ Class instances, builtin objects, and object literals whose methods use `this` throw `SC2020`. Calls through the object record it as `this`, including calls of a method read off the object and called on its own, where Node records `undefined`. `object.method === mock` is `false`, because the field holds a wrapper of the mock. - `mock.fn()` without an original, `mock.getter`, `mock.setter`, `mock.property`, `mock.module` and mock options throw `SC2020`. + `mock.fn()` without an original returns `undefined` and records the arguments of each call, as in Node; with a function type whose return type does not include `undefined`, such as `mock.fn<() => number>()`, it throws `SC2020`. + `mock.getter`, `mock.setter`, `mock.property`, `mock.module` and mock options throw `SC2020`. - **Mock timers:** `mock.timers.enable`, `tick`, `runAll`, `reset`, `setTime` and `[Symbol.dispose]()` compile, on the module's `mock` and on `t.mock`, for the `setTimeout`, `setInterval`, `setImmediate`, `Date`, `scheduler.wait` and `AbortSignal.timeout` APIs. `enable()` takes no options or an object literal whose `apis`, when given, is an array literal. Options and `apis` lists that are not literals, and the `MockTimers` object used as a value, throw `SC2020`. diff --git a/docs/content/limitations/builtin-apis/test-runner.mdx b/docs/content/limitations/builtin-apis/test-runner.mdx index b25eb0eaa..4afd4f11b 100644 --- a/docs/content/limitations/builtin-apis/test-runner.mdx +++ b/docs/content/limitations/builtin-apis/test-runner.mdx @@ -21,7 +21,8 @@ - **Timeouts:** when a test's `{ timeout }` elapses, its running subtests are cancelled, as in Node. Subtests do not take their parent's `timeout` as their own, where Node's do, and the subtests of a timed-out test with `{ concurrency }` above 1 run to completion before the test reports. A body that keeps running after its timeout and calls `t.test` again runs that subtest, where Node reports it as not started. -- **Thrown values:** a test that throws an object or array that is not an `Error`, such as `throw { code: 42 }`, reports `[object Object]` in the failing section, where Node prints the value as `util.inspect` renders it. +- **Thrown values:** a test that throws a value that is not an `Error` reports it in the failing section as `util.inspect` renders it, as Node does, for strings, numbers, booleans, objects, arrays, `null` and `undefined`. + A thrown class instance that is not an `Error`, such as `throw new Point(1, 2)`, reports `[object Object]`, where Node prints `Point { x: 1, y: 2 }`. - **Test names:** a test without a name, or with an empty one, takes its function's name or ``, as in Node, when the function's name is known at compile time: a function expression, a function declaration, or a `const` bound to an arrow or anonymous function. A name computed at run time, with a function the program received as a parameter, is used as given, so an empty one stays empty where Node uses the function's name. `test(fn)` and `test("", fn)` with such a function throw `SC2020`. diff --git a/docs/content/limitations/builtin-apis/v8.mdx b/docs/content/limitations/builtin-apis/v8.mdx new file mode 100644 index 000000000..066fb000d --- /dev/null +++ b/docs/content/limitations/builtin-apis/v8.mdx @@ -0,0 +1,5 @@ +- **V8 heap statistics:** `node:v8` can be imported. + `v8.getHeapStatistics()` returns Node's fields in Node's order, computed from the runtime allocator like `process.memoryUsage()`: `total_heap_size` and `total_physical_size` are the bytes the heap holds, `used_heap_size` the bytes allocated, `heap_size_limit` the machine's memory (the heap has no fixed ceiling), and `total_available_size` the limit minus the used bytes. + Fields that describe V8 itself (`total_heap_size_executable`, `malloced_memory`, `peak_malloced_memory`, the global handle sizes, `external_memory`, and `does_zap_garbage`) are `0`, and `number_of_native_contexts` is `1`. + The values are approximate and are not V8 heap figures. + The other `node:v8` members observe the V8 engine, such as heap snapshots, profiles, flags, and serialization, and throw `SC2020` where they are used. diff --git a/docs/content/limitations/builtin-function-values.mdx b/docs/content/limitations/builtin-function-values.mdx index c08540c1a..2c08ecc6d 100644 --- a/docs/content/limitations/builtin-function-values.mdx +++ b/docs/content/limitations/builtin-function-values.mdx @@ -5,6 +5,10 @@ Some builtins can be stored and passed as functions: - `util.isDeepStrictEqual`, `styleText`, `stripVTControlCharacters`, `toUSVString`, `inherits`, `deprecate`, `callbackify`, `debuglog`, and `debug`. - `fs.existsSync`, `unlinkSync`, `chmodSync`, `chownSync`, `renameSync`, and `closeSync`, and `fs/promises.unlink`, `chmod`, and `rename`. +In JavaScript, `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`, `clearImmediate`, `queueMicrotask`, and `process.nextTick` are function values wherever they are stored, including conditional and logical expressions and assignments: `typeof` answers `"function"`, identity holds across spellings, and calls schedule as the direct calls do, returning the same numeric timer handles. +Calls through these values bypass `node:test` mock timers. +Other builtin globals stored in JavaScript remain opaque identity values. + Other immutable aliases of table-backed Node builtins support direct calls, but cannot be passed elsewhere. `util.promisify` supports compile-time conversions of `child_process.execFile`, `child_process.exec`, `setTimeout` and `setImmediate` (to the `timers/promises` functions, as in Node), `stream.finished` and `stream.pipeline` (to the `stream/promises` functions, as in Node), the `zlib` callback one-shots `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `gzip`, `gunzip` and `unzip` (with or without options), the `crypto` callback functions `randomBytes`, `pbkdf2`, `scrypt`, `hkdf`, `randomFill`, `randomInt` and `generateKey`, `dns.lookup` (settling with `{ address, family }`, or the address array with `all: true`) and `dns.lookupService` (settling with `{ hostname, service }`), and the `fs` callback functions that have an `fs/promises` counterpart (`access`, `appendFile`, `chmod`, `chown`, `copyFile`, `lchown`, `link`, `lstat`, `lutimes`, `mkdir`, `mkdtemp`, `readFile`, `readdir`, `readlink`, `realpath`, `rename`, `rm`, `rmdir`, `stat`, `statfs`, `symlink`, `truncate`, `unlink`, `utimes`, and `writeFile`); calls through the converted function lower as the `fs/promises` call. @@ -12,7 +16,15 @@ Other immutable aliases of table-backed Node builtins support direct calls, but Native calls accept standard process streams; custom native streams and mutations to `util.inspect.colors` remain unsupported. `util.inherits` links the prototypes of constructor functions, and `util.deprecate`, `util.callbackify`, and `util.debuglog` return native function values that forward receivers and arguments. -Class constructors passed to `util.inherits` or `util.deprecate` are refused. +In JavaScript, a constructor function can inherit from `EventEmitter`, the legacy `Stream` constructor, or a stream class through `util.inherits` or `Object.setPrototypeOf`. +`EventEmitter.call(this)` and `Stream.call(this)` initialize the object as Node does, and its listener methods keep their state in the object's own `_events`, `_eventsCount`, and `_maxListeners` properties. +The `captureRejections` option and `events.errorMonitor` listeners are not supported on such objects. +`Readable.call(this, options)` and the same call on `Writable`, `Duplex`, `Transform`, or `PassThrough` give the object a native stream. +The stream calls the object's `_read`, `_write`, `_final`, `_transform`, `_flush`, and `_destroy` with the object as `this`, and the stream prototypes' methods and accessors work on it. +Its listeners live in the native stream rather than in `_events`, and `_readableState` and `_writableState` are read-only views that answer the commonly read fields. +Typed code that receives such an object as a stream class uses its native stream, and piping a native stream into it works. +The options the native streams refuse (`objectMode`, `decodeStrings: false`, `signal`, and others) are refused here too, as are `_writev`, `_construct`, symbol event names, `listeners()`, `rawListeners()`, `wrap()`, strings written in encodings other than UTF-8, and pipes into a destination that overrides `write()` or `end()`. +Other class constructors passed to `util.inherits` or `util.deprecate` are refused. Deprecation warnings are emitted on the next tick and follow assignments to `process.noDeprecation` and `process.throwDeprecation`; compiled binaries take no Node command-line flags such as `--no-deprecation` or `--trace-deprecation`, and `process.traceDeprecation` is unsupported. A stored `util.deprecate` reports a `length` of 4, where Node reports 3. In TypeScript, a `util.callbackify` result must reach a callback whose error parameter accepts `null`; the @types/node overloads that type it as non-null are refused. diff --git a/docs/content/limitations/child-process-workers.mdx b/docs/content/limitations/child-process-workers.mdx index 5b41c5e53..9d6602509 100644 --- a/docs/content/limitations/child-process-workers.mdx +++ b/docs/content/limitations/child-process-workers.mdx @@ -12,3 +12,7 @@ Message listeners may return `void` or `Promise`. Messages use newline-delimited JSON, so their static types must be JSON-stringifiable or `unknown` backed by a checked JSON tree. `send()` returns the channel's backpressure state and invokes its callback later with `null` or an `Error`. `disconnect()` changes `connected` synchronously, before the later disconnect events. + +A `'data'` listener on a child's `stdout` or `stderr` whose chunk is `unknown` or `any` receives strings when a `setEncoding()` statement on the same stream precedes it in its block or an enclosing block, and Buffers when the function never calls `setEncoding()` on that stream. +Any other placement of `setEncoding()` is refused at compile time, and a `setEncoding()` call that runs elsewhere afterwards makes the listener throw a TypeError instead of receiving strings. +An inline `'exit'` or `'close'` listener may declare `(code: number, signal: string)`, which TypeScript accepts through the catch-all `on` overload; it receives the `null` values Node passes, and a typed use of a `null` value throws a TypeError. diff --git a/docs/content/limitations/console-and-process-streams.mdx b/docs/content/limitations/console-and-process-streams.mdx index 0d7cd8f30..c62b82f0c 100644 --- a/docs/content/limitations/console-and-process-streams.mdx +++ b/docs/content/limitations/console-and-process-streams.mdx @@ -9,4 +9,5 @@ JavaScript can store `process.stdin`, `stdout`, and `stderr`, replace output `wr Input supports data/end/error listeners, pause/resume, buffered reads, and TTY raw mode. Output writes are synchronous, with successful callbacks on the next-tick queue; queued backpressure is not modeled. Stored streams do not support piping, encoding changes, arbitrary properties, or other events. -TypeScript output parameters support `write(string)`. +A TypeScript `NodeJS.WritableStream` parameter that only receives `process.stdout` or `process.stderr` supports `write(string)`. +When other streams flow into the same parameter, the process streams join them as `Writable` instances with the full writable surface. diff --git a/docs/content/limitations/dates-and-timezones.mdx b/docs/content/limitations/dates-and-timezones.mdx index e5655eb85..cba1d9004 100644 --- a/docs/content/limitations/dates-and-timezones.mdx +++ b/docs/content/limitations/dates-and-timezones.mdx @@ -1,7 +1,7 @@ -String constructors and `Date.parse` accept ECMAScript date-only forms, date-times with an explicit `Z` or numeric offset, and GMT certificate-validity strings from the supported X509 APIs. -Other V8-specific forms and offset-less local date-times produce an Invalid Date or `NaN`; `SC6005` reports string literals in those forms. +String constructors and `Date.parse` use V8's date-string grammar: ECMAScript date-time strings and the legacy forms Node.js accepts, such as RFC 1123 HTTP dates (`Mon, 01 Jan 2024 00:00:00 GMT`), `Date#toString` output, and `Jan 2, 2024`. +Strings without a timezone, and `new Date(year, monthIndex, ...)` fields, are local time. -Local calendar getters use the operating system's timezone database. +Local calendar getters and local-time conversions use the operating system's timezone database. Historical results can differ from Node.js when timezone data differs. For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule. diff --git a/docs/content/limitations/dns-lookup-errors.mdx b/docs/content/limitations/dns-lookup-errors.mdx index 9ed2c854c..e407922e1 100644 --- a/docs/content/limitations/dns-lookup-errors.mdx +++ b/docs/content/limitations/dns-lookup-errors.mdx @@ -1,3 +1,4 @@ After a failed `dns.lookup` or `dns.lookupService`, Node calls the callback with the error alone, so the address and family parameters are `undefined`. A callback whose address parameter is typed `string` (as Node's own type declarations type it) receives `""` and `NaN` there, or an empty array for `all: true`, because a typed string or number cannot hold `undefined`. An untyped callback receives the error alone, as in Node. +The promise forms in `node:dns/promises` (also reached as `dns.promises`) reject with the error and are unaffected. diff --git a/docs/content/limitations/graceful-fs.mdx b/docs/content/limitations/graceful-fs.mdx new file mode 100644 index 000000000..aabf175b7 --- /dev/null +++ b/docs/content/limitations/graceful-fs.mdx @@ -0,0 +1,4 @@ +The `graceful-fs` package compiles as `node:fs`. +Its imports and `require("graceful-fs")` calls resolve to the builtin module, so the package's JavaScript is not compiled, and `gracefulify(fs)` as a statement does nothing. +Programs behave as with the package except under file-descriptor exhaustion: graceful-fs queues operations that fail with `EMFILE` or `ENFILE` and retries them when a descriptor closes, while a compiled program reports the error to its caller, as `fs` does. +In a compiled program, `require("graceful-fs") === require("fs")` is `true`. diff --git a/docs/content/limitations/language-features/function-values.mdx b/docs/content/limitations/language-features/function-values.mdx index 88422c406..75229ff95 100644 --- a/docs/content/limitations/language-features/function-values.mdx +++ b/docs/content/limitations/language-features/function-values.mdx @@ -1,4 +1,4 @@ - **Function values:** Immutable aliases of overloaded program functions retain per-call overload resolution and identity. A function stored in a slot with a different signature remains the same function for equality, array searches, `length`, and own properties. - Typed rest-parameter functions can be stored and called indirectly. + Typed rest-parameter functions can be stored and called indirectly, including through a slot with fixed parameters, where the slot's arguments arrive in the rest array. Builtin function values have API-specific restrictions; see [Builtin function values](#builtin-function-values). diff --git a/docs/content/limitations/language-features/generics.mdx b/docs/content/limitations/language-features/generics.mdx index 6e203506f..a234c8f57 100644 --- a/docs/content/limitations/language-features/generics.mdx +++ b/docs/content/limitations/language-features/generics.mdx @@ -3,3 +3,9 @@ Independent concrete uses have no specialization-count limit; recursively expanding demands and type layouts produce diagnostics. Other generic calls compile when the target resolves statically. Generic function values require a concrete signature at use and a binding that is never reassigned. +- **Variadic wrappers:** A generic higher-order wrapper is specialized to each wrapped function's exact signature. + `Parameters`, `ReturnType`, and rest parameters typed as fixed tuples become that signature's parameter list. + A function expression with no parameters that reads `arguments` compiles only when its value is asserted to, or contextually typed as, one fixed-arity signature; `arguments` is then that signature's tuple. + Forwarding it with `fn.apply(this, arguments)` passes `this` through. + Reading `fn.name` on a wrapped function requires its name to be known where the wrapper is called, such as a function declaration or a `const`-bound function. + `Parameters` over a signature with optional or array-typed rest parameters is unsupported, because that tuple has no fixed length. diff --git a/docs/content/limitations/module-path-globals.mdx b/docs/content/limitations/module-path-globals.mdx new file mode 100644 index 000000000..358d96f95 --- /dev/null +++ b/docs/content/limitations/module-path-globals.mdx @@ -0,0 +1,4 @@ +`__dirname` and `__filename` are compile-time constants: the absolute path of the module's source directory and file at build time, the same strings as `import.meta.dirname` and `import.meta.filename`. +A `.ts` or `.tsx` file keeps them even when it uses `import` syntax, which Node.js would load as an ES module without these globals, because TypeScript projects are commonly shipped as CommonJS bundles where they exist. +In such a file `typeof __dirname` is `"string"`, where Node.js running the source directly reports `"undefined"`. +JavaScript ES modules (`.mjs`, or `.js` in a `"type": "module"` package) do not have them, as in Node.js. diff --git a/docs/content/limitations/runtime-code-evaluation.mdx b/docs/content/limitations/runtime-code-evaluation.mdx index c85e4dcd4..7b8117e70 100644 --- a/docs/content/limitations/runtime-code-evaluation.mdx +++ b/docs/content/limitations/runtime-code-evaluation.mdx @@ -1,4 +1,7 @@ -`eval`, `new Function`, and other ways of running JavaScript source at runtime are unsupported, in application code and in packages. -Rewrite code that depends on them, or replace the package. -To compute a value from code at build time instead, use `comptime`. +A compiled program embeds no JavaScript engine, so it cannot run JavaScript source at runtime. +`eval`, indirect `eval`, `new Function`, and `Function` behave as they do under `node --disallow-code-generation-from-strings`, a deliberate difference from default Node.js: every call throws a catchable `EvalError` with the message `Code generation from strings disallowed for this context`. +The arguments are evaluated first, and the Function constructor converts them to strings before it throws, as Node.js does. +Packages that test for code generation before using it, such as zod 4, take their interpreted path. +Code that needs to evaluate source at runtime must be rewritten; to compute a value from code at build time, use `comptime`. +The `vm` module is refused for the same reason. See [npm Dependencies](/docs/dependencies#which-packages-compile) for which packages compile. diff --git a/docs/content/limitations/types-and-class-definitions/null-and-optional-values.mdx b/docs/content/limitations/types-and-class-definitions/null-and-optional-values.mdx index e8b351679..be8b07571 100644 --- a/docs/content/limitations/types-and-class-definitions/null-and-optional-values.mdx +++ b/docs/content/limitations/types-and-class-definitions/null-and-optional-values.mdx @@ -1,3 +1,4 @@ - **Null and optional values:** `undefined` and `null` compile as standalone values and union members. Optional record and class fields and optional, default, and rest parameters also compile, including rest parameters typed `unknown[]` or `any[]`, whose functions take any number of arguments. - A function value with fixed parameters cannot be stored where such a variadic function type is expected. + A function value whose fixed parameters are all `unknown` or `any`, or that has none, can be stored where such a variadic function type without fixed parameters is expected: each call passes the arguments in order, `undefined` for the ones the caller left out. + Other function values with fixed parameters cannot be stored there. diff --git a/docs/content/limitations/unix-domain-sockets.mdx b/docs/content/limitations/unix-domain-sockets.mdx new file mode 100644 index 000000000..e98bc72e3 --- /dev/null +++ b/docs/content/limitations/unix-domain-sockets.mdx @@ -0,0 +1,2 @@ +`server.listen(path)` and the `socketPath` option of `http.request()` and `https.request()` use Unix domain sockets on Linux and macOS. On Windows, where Node maps these paths to named pipes, they fail with `ENOTSUP`. `server.address()` on a server listening on a Unix socket returns the socket path, as in Node, when the result is typed `string | AddressInfo | null` (the @types/node declaration). Where it is typed as the `{ address, family, port }` record alone, which cannot hold the path, the call throws an error for such a server. `listen(path, backlog, callback)` is refused at compile time; pass `listen(path, callback)`. +When the options of `http.request()` come from a variable, or from an object literal that spreads one, they are read at run time with Node's validation errors. The `auth`, `createConnection`, `family`, `hints`, `insecureHTTPParser`, `joinDuplicateHeaders`, `localAddress`, `localPort`, `lookup` and `uniqueHeaders` options then throw a runtime error when they hold a value, and so do `setHost: false` and `setDefaultHeaders: false`. diff --git a/docs/content/limitations/webassembly-modules.mdx b/docs/content/limitations/webassembly-modules.mdx index e3bf6e500..43fa55e48 100644 --- a/docs/content/limitations/webassembly-modules.mdx +++ b/docs/content/limitations/webassembly-modules.mdx @@ -1,7 +1,31 @@ -Compiled programs can't load WebAssembly modules yet. -Code that only mentions the `WebAssembly` types type-checks, including packages whose declarations name them, and `typeof WebAssembly` answers as it does in Node.js. -Every use of the API itself is rejected: `WebAssembly.compile`, `instantiate` and `validate`, `new WebAssembly.Module` and the other constructors, `instanceof` checks, and reads of `globalThis.WebAssembly`. -TypeScript sources fail to build with SC2020 at each use, and JavaScript sources throw that diagnostic when the statement runs. +scriptc translates WebAssembly modules to native code when it builds the program; there is no interpreter or JIT in the executable. +A build translates every module whose bytes it can see: files at paths the compiler resolves (`require.resolve`, `createRequire(...).resolve`, `import.meta.resolve`, `new URL("./x.wasm", import.meta.url)`, `path.join(__dirname, ...)`), including `.so` WebAssembly shared libraries such as quickjs-wasi's extensions, modules inlined as base64 or byte-array literals, `.wasm` files shipped by packages whose code uses WebAssembly, and files listed in a `scriptc.wasm` array in `package.json`. +At run time `WebAssembly.compile`, `instantiate`, `validate` and `new WebAssembly.Module` find the translated module by the SHA-256 of the bytes they are given. +Bytes the build did not see, including bytes produced at run time, reject with a `CompileError` that says the module was not translated, and `WebAssembly.validate` answers `false` for them. +`scriptc coverage` lists the modules a build finds, with their size, whether they validate, and the WebAssembly proposals they use. -`scriptc coverage` lists the modules a program would load, with their size, whether they validate, and the WebAssembly proposals they use. -It finds modules at paths the compiler resolves, modules inlined as base64 literals, `.wasm` files shipped by packages that use WebAssembly, and files named with [`--wasm`](/docs/cli) or a `scriptc.wasm` array in `package.json`. +Translated modules can use the WebAssembly 2.0 feature set: the MVP plus sign-extension operators, non-trapping float-to-int conversions, bulk memory, multi-value, mutable globals, reference types, and extended constant expressions. +Modules that use SIMD, threads, exception handling, tail calls, typed function references or GC, memory64, or multiple memories are not translated, so loading them rejects with the `CompileError` above; packages that fall back to a scalar build when that happens, such as undici and es-module-lexer, take their fallback. + +Supported: `WebAssembly.compile`, `instantiate` (with bytes or a `Module`), `validate`, `new WebAssembly.Module`, `new WebAssembly.Instance`, `new WebAssembly.Memory`, `new WebAssembly.Table`, `new WebAssembly.Global`, `WebAssembly.Module.imports`, `exports` and `customSections`, and `instanceof` for every class; the error classes `CompileError`, `LinkError` and `RuntimeError`, constructed with or without `new`; `globalThis.WebAssembly` and its `compile`, `instantiate` and `validate` functions as values. +Instances expose exported functions, memories (`buffer`, `grow`), tables (`get`, `set`, `grow`, `length`) and globals (`value`, `valueOf`). +Imports can be functions, numbers or BigInts for immutable globals, and memories, tables and globals that the program constructs or other instances export; their limits, types and mutability are checked with Node.js's `LinkError` messages. +Function references from a table or global reach JavaScript as callable functions, and exported functions can be stored in tables and globals, which is what dynamic linkers such as quickjs-wasi's extension loader do. +Traps throw `WebAssembly.RuntimeError` with Node.js's messages, unbounded recursion throws `RangeError`, and an exception thrown by an imported function propagates through the WebAssembly frames unchanged. +A `WebAssembly.Module` can be sent to a worker through `workerData` or `postMessage`. + +Not supported yet: the `Tag` and `Exception` constructors, shared memories (`new WebAssembly.Memory({ shared: true, ... })` throws a `TypeError`), `compileStreaming` and `instantiateStreaming`, and the classes used as values other than with `new`, `instanceof` and `WebAssembly.Module`'s static functions. +TypeScript sources fail to build with SC2020 at each such use, and JavaScript sources throw that diagnostic when the statement runs. +Programs built for `wasm32-wasi` can't use the WebAssembly API. + +Differences from Node.js: + +- Growing a memory, from JavaScript or inside the module, does not detach the previous `ArrayBuffer`. + The memory never moves, so views created before the growth stay valid but keep their old length; code that reads `memory.buffer` again after growing, as WebAssembly glue code does, behaves as in Node.js. +- `valueOf()` answers a `WebAssembly.Global`'s value only when the receiver's static type is `WebAssembly.Global`; through an untyped value it answers the object, and converting a `Global` to a primitive uses the object rather than its value. +- Instances are never freed, together with their memories, imports and exports. +- A load whose result the module never uses may be removed instead of trapping when its address is out of bounds. + Loads whose result is dropped explicitly still trap. +- NaN payloads may differ, for example when a signaling NaN passes through arithmetic or a JavaScript number. +- `WebAssembly.compile` and `instantiate` with bytes settle after the current microtasks, as in Node.js, but can settle after `setImmediate` callbacks that Node.js would run later. +- The executable reads `.wasm` files at run time from the paths they had when it was built. diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index be1a931cc..e5fd082f6 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -239,9 +239,9 @@ "apiEntries": 51, "static": { "supported": 0, - "partial": 0, + "partial": 10, "refused": 0, - "not-implemented": 51, + "not-implemented": 41, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -329,8 +329,8 @@ "apiEntries": 92, "static": { "supported": 1, - "partial": 15, - "refused": 1, + "partial": 16, + "refused": 0, "not-implemented": 75, "by-design": 0, "unreviewed": 0, @@ -539,9 +539,9 @@ "apiEntries": 131, "static": { "supported": 0, - "partial": 19, + "partial": 25, "refused": 0, - "not-implemented": 112, + "not-implemented": 106, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -809,10 +809,10 @@ "apiEntries": 68, "static": { "supported": 0, - "partial": 3, + "partial": 4, "refused": 0, "not-implemented": 0, - "by-design": 65, + "by-design": 64, "unreviewed": 0, "not-applicable": 0 } @@ -869,9 +869,9 @@ "apiEntries": 89, "static": { "supported": 0, - "partial": 7, + "partial": 8, "refused": 1, - "not-implemented": 81, + "not-implemented": 80, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -16626,7 +16626,7 @@ "inherited": false }, "static": { - "status": "not-implemented", + "status": "partial", "detail": "Derived from the exact API rows in this chapter.", "verification": "derived" } @@ -16718,9 +16718,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -16741,9 +16741,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17132,9 +17132,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17201,9 +17201,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17316,9 +17316,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17339,9 +17339,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17362,9 +17362,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17730,9 +17730,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -17753,9 +17753,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -36590,9 +36590,9 @@ "inherited": true }, "static": { - "status": "refused", - "detail": "Explicitly rejected by a named compiler diagnostic.", - "verification": "explicit-refusal" + "status": "partial", + "detail": "Implemented for the call shapes accepted by the compiler lowering.", + "verification": "test-backed" } }, { @@ -54688,9 +54688,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -54803,9 +54803,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -55286,9 +55286,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -55309,9 +55309,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -55677,9 +55677,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the call shapes accepted by the compiler lowering.", + "verification": "test-backed" } }, { @@ -55815,9 +55815,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" } }, { @@ -75973,9 +75973,9 @@ "anchor": "v8getheapstatistics", "nodeStability": null, "static": { - "status": "by-design", - "detail": "Intentionally outside the static native execution model.", - "verification": "architectural-policy" + "status": "partial", + "detail": "Implemented for the call shapes accepted by the compiler lowering.", + "verification": "test-backed" } }, { @@ -81011,9 +81011,9 @@ "inherited": true }, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented static async-iteration subset.", + "verification": "test-backed" } }, { diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index a02446908..51459d3b3 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -3,20 +3,20 @@ "nodeVersion": "24.22.0", "nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93", "summary": { - "taskCount": 3453, - "tierItemCount": 3453, + "taskCount": 3454, + "tierItemCount": 3454, "tiers": { "static": { - "audit-partial": 1179, - "verify-gap": 2146, - "replace-refusal": 41, + "audit-partial": 1197, + "verify-gap": 2130, + "replace-refusal": 40, "classify": 2, "implement": 85 } }, "priorities": { "high": 2287, - "normal": 694, + "normal": 695, "low": 472 } }, @@ -12300,12 +12300,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dnsPromises.lookup", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] } } }, @@ -12325,12 +12328,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dnsPromises.lookupService", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] } } }, @@ -12750,12 +12756,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dnsPromises.getDefaultResultOrder", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] } } }, @@ -12825,12 +12834,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.lookup", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -12950,12 +12963,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.getServers", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -12975,12 +12992,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.lookup", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -13000,12 +13021,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.lookupService", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -13400,12 +13425,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.setDefaultResultOrder", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -13425,12 +13454,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:dns", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:dns.dns.getDefaultResultOrder", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] } } }, @@ -34202,12 +34235,25 @@ "priority": "high", "tiers": { "static": { - "status": "refused", - "action": "replace-refusal", - "verification": "explicit-refusal", + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", "confidence": "high", - "source": "surface-manifest:unsupported:stdlib.webassembly", - "tests": [] + "source": "surface-manifest:stdlib.webassembly", + "tests": [ + "tests/corpus/webassembly-api.mjs", + "tests/corpus/webassembly-typed.ts", + "tests/corpus/webassembly-worker.ts", + "tests/corpus/webassembly-spec-int/main.mjs", + "tests/corpus/webassembly-spec-float/main.mjs", + "tests/corpus/webassembly-spec-control/main.mjs", + "tests/corpus/webassembly-spec-memory/main.mjs", + "tests/corpus/webassembly-spec-table/main.mjs", + "tests/corpus/webassembly-spec-imports/main.mjs", + "tests/corpus/webassembly-jsapi.mjs", + "tests/corpus/webassembly-wpt-jsapi.mjs", + "tests/corpus/webassembly-dylink/main.mjs" + ] } } }, @@ -54231,12 +54277,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.histogram.percentile", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -54356,12 +54405,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.histogram.reset", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -54881,12 +54933,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.histogram.disable", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -54906,12 +54961,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.histogram.enable", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -55306,12 +55364,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.perf_hooks.monitorEventLoopDelay", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -55466,12 +55527,15 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:perf_hooks", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:perf_hooks.performance.eventLoopUtilization", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] } } }, @@ -55940,7 +56004,9 @@ "confidence": "high", "source": "compiler-feature:process.'disconnect'", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -55982,7 +56048,9 @@ "confidence": "high", "source": "compiler-feature:process.'message'", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -56365,7 +56433,9 @@ "confidence": "high", "source": "compiler-feature:process.process.disconnect", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -57013,7 +57083,9 @@ "confidence": "high", "source": "compiler-feature:process.process.send", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -57467,7 +57539,9 @@ "confidence": "high", "source": "compiler-feature:process.connected", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] } } @@ -57582,7 +57656,10 @@ "tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", - "tests/corpus/2944-process-exit-code-wrap.ts" + "tests/corpus/2944-process-exit-code-wrap.ts", + "tests/corpus/6422-process-exitcode-read.ts", + "tests/corpus/6423-process-exitcode-default.ts", + "tests/corpus/6424-process-exit-keeps-exitcode.ts" ] } } @@ -58969,7 +59046,9 @@ "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59001,7 +59080,9 @@ "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59229,7 +59310,9 @@ "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59290,7 +59373,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59322,7 +59407,9 @@ "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59405,7 +59492,8 @@ "confidence": "high", "source": "compiler-dedicated:readline.createInterface", "tests": [ - "tests/corpus/1475-readline-closed-stdin.ts" + "tests/corpus/1475-readline-closed-stdin.ts", + "tests/corpus/5878-readline-dynamic-streams.mjs" ] } } @@ -59544,7 +59632,8 @@ "confidence": "high", "source": "compiler-dedicated:rl.close", "tests": [ - "tests/corpus/1475-readline-closed-stdin.ts" + "tests/corpus/1475-readline-closed-stdin.ts", + "tests/corpus/5878-readline-dynamic-streams.mjs" ] } } @@ -59574,7 +59663,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59604,7 +59695,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59634,7 +59727,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59664,7 +59759,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59694,7 +59791,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59724,7 +59823,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59754,7 +59855,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59784,7 +59887,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59904,7 +60009,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59934,7 +60041,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -59964,7 +60073,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -60022,7 +60133,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -60052,7 +60165,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] } } @@ -66493,6 +66608,7 @@ "source": "compiler-feature:test.mock.fn", "tests": [ "tests/fixtures/node-test/cases/mock-fn.ts", + "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/harness/node-test.test.ts" ] } @@ -76165,6 +76281,8 @@ "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", + "tests/corpus/6428-promisify-callback-functions.ts", + "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts" ] } @@ -77759,6 +77877,29 @@ } } }, + { + "id": "v8:cefde4a8297c", + "chapter": "v8", + "label": "v8.getHeapStatistics()", + "signature": "`v8.getHeapStatistics()`", + "apiSymbol": "v8.getHeapStatistics", + "docsUrl": "https://nodejs.org/docs/v24.22.0/api/v8.html#v8getheapstatistics", + "nodeStability": null, + "priority": "normal", + "tiers": { + "static": { + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "surface-manifest:node-builtin.v8.getHeapStatistics", + "tests": [ + "tests/corpus/v8-heap-statistics.ts", + "tests/corpus/v8-require-optional.cjs" + ] + } + } + }, { "id": "webcrypto:22408f834c48", "chapter": "webcrypto", @@ -81911,12 +82052,16 @@ "priority": "high", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:webstreams", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:webstreams.ReadableStream", + "tests": [ + "tests/corpus/stream-web-readable.ts", + "tests/corpus/stream-web-require.cjs", + "tests/corpus/2862-web-readable-async-iteration.ts" + ] } } }, diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 3690ce077..0e1e73a26 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -251,9 +251,9 @@ "apiEntries": 51, "static": { "supported": 0, - "partial": 0, + "partial": 10, "refused": 0, - "not-implemented": 51, + "not-implemented": 41, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -341,8 +341,8 @@ "apiEntries": 92, "static": { "supported": 1, - "partial": 15, - "refused": 1, + "partial": 16, + "refused": 0, "not-implemented": 75, "by-design": 0, "unreviewed": 0, @@ -566,9 +566,9 @@ "apiEntries": 131, "static": { "supported": 0, - "partial": 19, + "partial": 25, "refused": 0, - "not-implemented": 112, + "not-implemented": 106, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -866,10 +866,10 @@ "apiEntries": 68, "static": { "supported": 0, - "partial": 3, + "partial": 4, "refused": 0, "not-implemented": 0, - "by-design": 65, + "by-design": 64, "unreviewed": 0, "not-applicable": 0 } @@ -926,9 +926,9 @@ "apiEntries": 89, "static": { "supported": 0, - "partial": 7, + "partial": 8, "refused": 1, - "not-implemented": 81, + "not-implemented": 80, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -30720,8 +30720,15 @@ "inherited": false }, "static": { - "status": "not-implemented", - "evidence": "derived:descendants" + "status": "partial", + "evidence": "derived:descendants", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs", + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] }, "anchorSource": "chapter" }, @@ -30835,8 +30842,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dnsPromises.lookup", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -30858,8 +30869,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dnsPromises.lookupService", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31249,8 +31264,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dnsPromises.getDefaultResultOrder", + "tests": [ + "tests/corpus/dns-promises-lookup.ts", + "tests/corpus/dns-promises-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31364,8 +31383,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.lookup", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31479,8 +31503,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.getServers", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31502,8 +31531,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.lookup", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31548,8 +31582,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.lookupService", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31916,8 +31955,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.setDefaultResultOrder", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -31939,8 +31983,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:dns" + "status": "partial", + "evidence": "compiler-feature:dns.dns.getDefaultResultOrder", + "tests": [ + "tests/corpus/5648-dns-lookup-forms.ts", + "tests/corpus/5649-dns-service-servers.ts", + "tests/corpus/5650-dns-lookup-errors-untyped.cjs" + ] }, "anchorSource": "exact" }, @@ -52249,6 +52298,18 @@ "tests/corpus/6295-jose-hs256/main.mjs", "tests/corpus/6331-jose-root-entry/main.ts", "tests/corpus/text-codec-values/main.ts", + "tests/corpus/webassembly-api.mjs", + "tests/corpus/webassembly-dylink/main.mjs", + "tests/corpus/webassembly-jsapi.mjs", + "tests/corpus/webassembly-spec-control/main.mjs", + "tests/corpus/webassembly-spec-float/main.mjs", + "tests/corpus/webassembly-spec-imports/main.mjs", + "tests/corpus/webassembly-spec-int/main.mjs", + "tests/corpus/webassembly-spec-memory/main.mjs", + "tests/corpus/webassembly-spec-table/main.mjs", + "tests/corpus/webassembly-typed.ts", + "tests/corpus/webassembly-worker.ts", + "tests/corpus/webassembly-wpt-jsapi.mjs", "tests/fixtures/node-types/navigator.ts", "tests/harness/fetch-conformance.test.ts", "tests/harness/project-config.test.ts" @@ -53600,8 +53661,22 @@ "inherited": true }, "static": { - "status": "refused", - "evidence": "surface-manifest:unsupported:stdlib.webassembly" + "status": "partial", + "evidence": "surface-manifest:stdlib.webassembly", + "tests": [ + "tests/corpus/webassembly-api.mjs", + "tests/corpus/webassembly-typed.ts", + "tests/corpus/webassembly-worker.ts", + "tests/corpus/webassembly-spec-int/main.mjs", + "tests/corpus/webassembly-spec-float/main.mjs", + "tests/corpus/webassembly-spec-control/main.mjs", + "tests/corpus/webassembly-spec-memory/main.mjs", + "tests/corpus/webassembly-spec-table/main.mjs", + "tests/corpus/webassembly-spec-imports/main.mjs", + "tests/corpus/webassembly-jsapi.mjs", + "tests/corpus/webassembly-wpt-jsapi.mjs", + "tests/corpus/webassembly-dylink/main.mjs" + ] }, "anchorSource": "exact" }, @@ -75571,7 +75646,9 @@ "tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5213-perf-timing-errors.ts", "tests/corpus/5214-perf-entry-inspect.ts", - "tests/corpus/5215-perf-timing-validation.js" + "tests/corpus/5215-perf-timing-validation.js", + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" ] }, "anchorSource": "chapter" @@ -77612,8 +77689,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.histogram.percentile", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -77727,8 +77808,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.histogram.reset", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -78210,8 +78295,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.histogram.disable", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -78233,8 +78322,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.histogram.enable", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -78601,8 +78694,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "surface-manifest:node-builtin.perf_hooks.monitorEventLoopDelay", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -78751,8 +78848,12 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:perf_hooks" + "status": "partial", + "evidence": "compiler-feature:perf_hooks.performance.eventLoopUtilization", + "tests": [ + "tests/corpus/6397-event-loop-metrics.ts", + "tests/fixtures/node-types/event-loop-metrics.ts" + ] }, "anchorSource": "exact" }, @@ -79409,6 +79510,11 @@ "tests/corpus/5210-process-memory-usage.ts", "tests/corpus/5211-process-identity.ts", "tests/corpus/5809-process-exit-and-rejections.cjs", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts", + "tests/corpus/6422-process-exitcode-read.ts", + "tests/corpus/6423-process-exitcode-default.ts", + "tests/corpus/6424-process-exit-keeps-exitcode.ts", "tests/corpus/990-process-basics.ts", "tests/corpus/node24-env-files-errors.ts", "tests/corpus/node24-env-files.ts", @@ -79486,7 +79592,9 @@ "status": "partial", "evidence": "compiler-feature:process.'disconnect'", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "exact" @@ -79525,7 +79633,9 @@ "status": "partial", "evidence": "compiler-feature:process.'message'", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "exact" @@ -79938,7 +80048,9 @@ "status": "partial", "evidence": "compiler-feature:process.process.disconnect", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "exact" @@ -80583,7 +80695,9 @@ "status": "partial", "evidence": "compiler-feature:process.process.send", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "exact" @@ -81002,7 +81116,9 @@ "status": "partial", "evidence": "compiler-feature:process.connected", "tests": [ - "tests/corpus/2946-child-fork-ipc/main.ts" + "tests/corpus/2946-child-fork-ipc/main.ts", + "tests/corpus/6420-process-send-presence.ts", + "tests/corpus/6421-process-send-presence-fork/main.ts" ] }, "anchorSource": "chapter" @@ -81111,7 +81227,10 @@ "tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", - "tests/corpus/2944-process-exit-code-wrap.ts" + "tests/corpus/2944-process-exit-code-wrap.ts", + "tests/corpus/6422-process-exitcode-read.ts", + "tests/corpus/6423-process-exitcode-default.ts", + "tests/corpus/6424-process-exit-keeps-exitcode.ts" ] }, "anchorSource": "chapter" @@ -82492,7 +82611,10 @@ "tests/corpus/5791-readline-keypress-interfaces.cjs", "tests/corpus/5792-readline-keypress-listeners.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs", + "tests/corpus/5878-readline-dynamic-streams.mjs" ] }, "anchorSource": "chapter" @@ -82546,7 +82668,9 @@ "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -82577,7 +82701,9 @@ "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -82797,7 +82923,9 @@ "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -82879,7 +83007,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -82910,7 +83040,9 @@ "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", - "tests/corpus/5873-readline-promises-namespace.cjs" + "tests/corpus/5873-readline-promises-namespace.cjs", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -82990,7 +83122,8 @@ "status": "partial", "evidence": "compiler-dedicated:readline.createInterface", "tests": [ - "tests/corpus/1475-readline-closed-stdin.ts" + "tests/corpus/1475-readline-closed-stdin.ts", + "tests/corpus/5878-readline-dynamic-streams.mjs" ] }, "anchorSource": "exact" @@ -83146,7 +83279,8 @@ "status": "partial", "evidence": "compiler-dedicated:rl.close", "tests": [ - "tests/corpus/1475-readline-closed-stdin.ts" + "tests/corpus/1475-readline-closed-stdin.ts", + "tests/corpus/5878-readline-dynamic-streams.mjs" ] }, "anchorSource": "exact" @@ -83175,7 +83309,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83204,7 +83340,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83233,7 +83371,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83262,7 +83402,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83291,7 +83433,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83320,7 +83464,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83349,7 +83495,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83378,7 +83526,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83494,7 +83644,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83523,7 +83675,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83552,7 +83706,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83608,7 +83764,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -83637,7 +83795,9 @@ "tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", - "tests/corpus/5299-readline-stdin.ts" + "tests/corpus/5299-readline-stdin.ts", + "tests/corpus/5876-readline-dispose-instanceof.ts", + "tests/corpus/5877-readline-question-query.cjs" ] }, "anchorSource": "exact" @@ -91104,6 +91264,7 @@ "tests/fixtures/node-test/cases/ctx-plan.ts", "tests/fixtures/node-test/cases/get-test-context-cancelled.ts", "tests/fixtures/node-test/cases/get-test-context.ts", + "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/fixtures/node-test/cases/mock-fn.ts", "tests/fixtures/node-test/cases/mock-method.ts", "tests/fixtures/node-test/cases/mock-timers-date.ts", @@ -92515,6 +92676,7 @@ "evidence": "compiler-feature:test.mock.fn", "tests": [ "tests/fixtures/node-test/cases/mock-fn.ts", + "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/harness/node-test.test.ts" ] }, @@ -101196,6 +101358,7 @@ "tests/corpus/5832-util-types-brands-dynamic.js", "tests/corpus/5835-util-format-integer-specifiers.ts", "tests/corpus/5884-inspect-layout-options.ts", + "tests/corpus/6428-promisify-callback-functions.ts", "tests/corpus/node24-deep-equality-properties.js", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality.ts", @@ -101222,6 +101385,7 @@ "tests/corpus/text-codec-values/main.ts", "tests/fixtures/node-types/deep-equality.ts", "tests/fixtures/node-types/env-files.ts", + "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/fixtures/node-types/promisify-fs.ts", @@ -102880,6 +103044,8 @@ "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", + "tests/corpus/6428-promisify-callback-functions.ts", + "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts" ] }, @@ -104412,6 +104578,8 @@ "status": "partial", "evidence": "derived:descendants", "tests": [ + "tests/corpus/v8-heap-statistics.ts", + "tests/corpus/v8-require-optional.cjs", "tests/harness/node-test.test.ts" ] }, @@ -105534,8 +105702,12 @@ "anchor": "v8getheapstatistics", "nodeStability": null, "static": { - "status": "by-design", - "evidence": "compiler-chapter-policy:v8" + "status": "partial", + "evidence": "surface-manifest:node-builtin.v8.getHeapStatistics", + "tests": [ + "tests/corpus/v8-heap-statistics.ts", + "tests/corpus/v8-require-optional.cjs" + ] }, "anchorSource": "exact" }, @@ -111147,6 +111319,8 @@ "tests/corpus/2630-stream-consumers-errors.ts", "tests/corpus/2862-web-readable-async-iteration.ts", "tests/corpus/4875-stream-consumers-array-buffer.ts", + "tests/corpus/stream-web-readable.ts", + "tests/corpus/stream-web-require.cjs", "tests/harness/fetch-conformance.test.ts" ] }, @@ -111415,8 +111589,13 @@ "inherited": true }, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:webstreams" + "status": "partial", + "evidence": "compiler-feature:webstreams.ReadableStream", + "tests": [ + "tests/corpus/stream-web-readable.ts", + "tests/corpus/stream-web-require.cjs", + "tests/corpus/2862-web-readable-async-iteration.ts" + ] }, "anchorSource": "exact" }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 855d065f7..734e038ef 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -28,6 +28,7 @@ "webstreams": "unreviewed" }, "evidenceByManifestPrefix": { + "stdlib.webassembly": ["tests/corpus/webassembly-api.mjs", "tests/corpus/webassembly-typed.ts", "tests/corpus/webassembly-worker.ts", "tests/corpus/webassembly-spec-int/main.mjs", "tests/corpus/webassembly-spec-float/main.mjs", "tests/corpus/webassembly-spec-control/main.mjs", "tests/corpus/webassembly-spec-memory/main.mjs", "tests/corpus/webassembly-spec-table/main.mjs", "tests/corpus/webassembly-spec-imports/main.mjs", "tests/corpus/webassembly-jsapi.mjs", "tests/corpus/webassembly-wpt-jsapi.mjs", "tests/corpus/webassembly-dylink/main.mjs"], "node-builtin.fs.readFileSync": ["tests/corpus/1403-buffer-fs.ts", "tests/corpus/2595-fs-arg-ladders.cjs", "tests/corpus/2919-fs-function-values.ts", "tests/corpus/2920-fs-promises-function-values.ts", "tests/corpus/fs-read-encodings.ts"], "syntax.using-declarations": ["tests/corpus/2841-using-sync.ts"], "syntax.await-using-declarations": ["tests/corpus/2842-await-using.ts"], @@ -47,6 +48,10 @@ "node-builtin.dgram": ["tests/corpus/2597-dgram-send-ladders.cjs"], "node-builtin.diagnostics_channel": ["tests/corpus/2214-dc-bind-store.cjs"], "node-builtin.dns": ["tests/corpus/2597-dgram-send-ladders.cjs"], + "node-builtin.dns.promises": ["tests/corpus/dns-promises-lookup.ts", "tests/corpus/dns-promises-untyped.cjs"], + "node-builtin.stream.web": ["tests/corpus/stream-web-readable.ts", "tests/corpus/stream-web-require.cjs"], + "node-builtin.v8": ["tests/corpus/v8-require-optional.cjs"], + "node-builtin.v8.getHeapStatistics": ["tests/corpus/v8-heap-statistics.ts", "tests/corpus/v8-require-optional.cjs"], "node-builtin.events": ["tests/corpus/1677-emitter-listeners.ts", "tests/corpus/2620-ee-override-once-order.ts"], "node-builtin.fs": ["tests/corpus/1403-buffer-fs.ts", "tests/corpus/2595-fs-arg-ladders.cjs", "tests/corpus/2919-fs-function-values.ts", "tests/corpus/2920-fs-promises-function-values.ts"], "node-builtin.fs.realpathSync.native": ["tests/corpus/fs-native-realpath.ts"], @@ -134,6 +139,8 @@ "node-builtin.perf_hooks.performance.getEntries": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts", "tests/corpus/5215-perf-timing-validation.js"], "node-builtin.perf_hooks.performance.clearMarks": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5213-perf-timing-errors.ts", "tests/corpus/5215-perf-timing-validation.js"], "node-builtin.perf_hooks.performance.clearMeasures": ["tests/corpus/5212-perf-marks-measures.ts"], + "node-builtin.perf_hooks.performance.eventLoopUtilization": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"], + "node-builtin.perf_hooks.monitorEventLoopDelay": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"], "node-builtin.process": ["tests/corpus/990-process-basics.ts", "tests/corpus/2314-process-introspection.ts"], "node-builtin.process.hrtime": ["tests/corpus/process-hrtime.js"], "node-builtin.process.memoryUsage": ["tests/corpus/5210-process-memory-usage.ts"], @@ -148,7 +155,7 @@ "node-builtin.process.versions": ["tests/corpus/process-versions-storage.ts"], "node-builtin.process.rows": ["tests/corpus/2992-stream-rows.ts", "tests/harness/terminal-geometry.test.ts"], "node-builtin.process.columns": ["tests/corpus/1448-stream-columns.ts", "tests/harness/terminal-geometry.test.ts"], - "node-builtin.process.exitCode": ["tests/corpus/5809-process-exit-and-rejections.cjs", "tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", "tests/corpus/2944-process-exit-code-wrap.ts"], + "node-builtin.process.exitCode": ["tests/corpus/5809-process-exit-and-rejections.cjs", "tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", "tests/corpus/2944-process-exit-code-wrap.ts", "tests/corpus/6422-process-exitcode-read.ts", "tests/corpus/6423-process-exitcode-default.ts", "tests/corpus/6424-process-exit-keeps-exitcode.ts"], "node-builtin.punycode": ["tests/corpus/6122-punycode-conversions.ts", "tests/corpus/6124-punycode-require-warnings.cjs", "tests/corpus/6126-punycode-dependency/main.cjs"], "node-builtin.querystring": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/2917-builtin-callable-widths.ts"], "node-builtin.readline": ["tests/corpus/1475-readline-closed-stdin.ts"], @@ -199,13 +206,13 @@ {"chapter": "events", "symbols": ["CustomEvent", "detail"], "status": "partial", "evidence": ["tests/corpus/5130-event-target-dispatch.js", "tests/corpus/5132-event-target-typed.ts"]}, {"chapter": "events", "symbols": ["events.errorMonitor"], "status": "partial", "evidence": ["tests/corpus/5134-events-error-monitor.ts", "tests/corpus/5136-events-error-monitor-string-name.ts", "tests/corpus/5137-events-error-monitor-computed-name.js", "tests/corpus/5138-events-error-monitor-shared-listener.ts"]}, {"chapter": "events", "symbols": ["events.addAbortListener"], "status": "partial", "evidence": ["tests/corpus/5133-abort-signal-event-target.ts"]}, - {"chapter": "readline", "symbols": ["readlinePromises.Interface", "readlinePromises.createInterface", "rl.question"], "status": "partial", "evidence": ["tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", "tests/corpus/5873-readline-promises-namespace.cjs"]}, + {"chapter": "readline", "symbols": ["readlinePromises.Interface", "readlinePromises.createInterface", "rl.question"], "status": "partial", "evidence": ["tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5298-readline-abort.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5872-readline-positional-create-interface.mjs", "tests/corpus/5873-readline-promises-namespace.cjs", "tests/corpus/5876-readline-dispose-instanceof.ts", "tests/corpus/5877-readline-question-query.cjs"]}, {"chapter": "readline", "symbols": ["'history'", "rl.line"], "status": "partial", "evidence": ["tests/corpus/5780-readline-history-options.ts", "tests/corpus/5781-readline-history-options.cjs"]}, {"chapter": "readline", "symbols": ["'SIGINT'", "'SIGTSTP'", "'SIGCONT'", "line", "cursor", "completer"], "status": "partial", "evidence": ["tests/corpus/5782-readline-terminal-editing.ts", "tests/corpus/5783-readline-terminal-completion.cjs", "tests/corpus/5784-readline-terminal-promises.ts", "tests/corpus/5785-readline-terminal-keys.ts", "tests/corpus/5786-readline-terminal-history.cjs"]}, {"chapter": "readline", "symbols": ["readline.cursorTo", "readline.moveCursor", "readline.clearLine", "readline.clearScreenDown", "rl.getCursorPos"], "status": "partial", "evidence": ["tests/corpus/5787-readline-cursor-helpers.cjs", "tests/corpus/5789-readline-cursor-pos.cjs"]}, {"chapter": "readline", "symbols": ["readlinePromises.Readline", "rl.clearLine", "rl.clearScreenDown", "rl.commit", "rl.cursorTo", "rl.moveCursor", "rl.rollback"], "status": "partial", "evidence": ["tests/corpus/5788-readline-promises-readline.ts", "tests/corpus/5789-readline-cursor-pos.cjs"]}, {"chapter": "readline", "symbols": ["readline.emitKeypressEvents"], "status": "partial", "evidence": ["tests/corpus/5790-readline-keypress-events.ts", "tests/corpus/5791-readline-keypress-interfaces.cjs", "tests/corpus/5792-readline-keypress-listeners.ts"]}, - {"chapter": "readline", "symbols": ["readline.Interface", "rl.pause", "rl.resume", "rl.prompt", "rl.setPrompt", "rl.getPrompt", "rl.write", "rl", "'close'", "'error'", "'line'", "'pause'", "'resume'"], "status": "partial", "evidence": ["tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", "tests/corpus/5299-readline-stdin.ts"]}, + {"chapter": "readline", "symbols": ["readline.Interface", "rl.pause", "rl.resume", "rl.prompt", "rl.setPrompt", "rl.getPrompt", "rl.write", "rl", "'close'", "'error'", "'line'", "'pause'", "'resume'"], "status": "partial", "evidence": ["tests/corpus/5295-readline-promises-question.ts", "tests/corpus/5296-readline-lines.ts", "tests/corpus/5297-readline-callback-events.cjs", "tests/corpus/5299-readline-stdin.ts", "tests/corpus/5876-readline-dispose-instanceof.ts", "tests/corpus/5877-readline-question-query.cjs"]}, {"chapter": "stream", "symbols": ["stream.addAbortSignal"], "status": "partial", "evidence": ["tests/corpus/5300-stream-add-abort-signal.ts", "tests/corpus/5301-stream-add-abort-signal-errors.cjs"]}, {"chapter": "stream", "symbols": ["readable.map", "readable.filter", "readable.forEach", "readable.toArray", "readable.some", "readable.find", "readable.every", "readable.flatMap", "readable.drop", "readable.take", "readable.reduce"], "status": "partial", "evidence": ["tests/corpus/5335-stream-operators-values.ts", "tests/corpus/5336-stream-operators-promise-timing.ts", "tests/corpus/5337-stream-operators-concurrency.ts", "tests/corpus/5338-stream-operators-checks.cjs"]}, {"chapter": "stream", "symbols": ["stream.Readable.from"], "status": "partial", "evidence": ["tests/corpus/5340-readable-from-options.ts", "tests/corpus/5341-readable-from-dynamic.cjs", "tests/corpus/5420-readable-from-generators.ts", "tests/corpus/5421-readable-from-generators.cjs", "tests/corpus/5610-readable-from-sets-and-views.ts", "tests/corpus/5611-readable-from-class-iterables.ts", "tests/corpus/5612-readable-from-iterables.cjs"]}, @@ -242,6 +249,8 @@ { "chapter": "util", "symbols": ["util.parseEnv"], "status": "partial", "evidence": ["tests/corpus/node24-env-parse.ts", "tests/corpus/node24-env-parse-errors.ts", "tests/corpus/node24-env-imports.cjs", "tests/fixtures/node-types/env-files.ts", "tests/harness/project-config.test.ts"] }, { "chapter": "process", "symbols": ["process.memoryUsage.rss"], "status": "partial", "evidence": ["tests/corpus/5210-process-memory-usage.ts"] }, { "chapter": "perf_hooks", "symbols": ["performance.now"], "status": "partial", "evidence": ["tests/corpus/2427-perf-hooks-now.ts", "tests/corpus/2446-performance-global.ts"] }, + { "chapter": "perf_hooks", "symbols": ["performance.eventLoopUtilization"], "status": "partial", "evidence": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"] }, + { "chapter": "perf_hooks", "symbols": ["histogram.enable", "histogram.disable", "histogram.percentile", "histogram.reset"], "status": "partial", "evidence": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"] }, { "chapter": "perf_hooks", "symbols": ["performance.mark", "performance.measure", "performance.getEntries", "performance.getEntriesByName", "performance.getEntriesByType", "performance.clearMarks", "performance.clearMeasures"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5213-perf-timing-errors.ts", "tests/corpus/5214-perf-entry-inspect.ts", "tests/corpus/5215-perf-timing-validation.js"] }, { "chapter": "perf_hooks", "symbols": ["PerformanceEntry", "PerformanceMark", "PerformanceMeasure"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts"] }, { "chapter": "perf_hooks", "parent": "Class: `PerformanceEntry`", "symbols": ["duration", "entryType", "name", "startTime"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts"] }, @@ -341,14 +350,14 @@ { "chapter": "fs", "signatures": ["`fsPromises.lstat(path[, options])`"], "status": "partial", "evidence": ["tests/corpus/2941-fsp-lstat-identity.ts"] }, { "chapter": "fs", "signatures": ["`fs.globSync(pattern[, options])`", "`fs.glob(pattern[, options], callback)`", "`fsPromises.glob(pattern[, options])`"], "status": "partial", "evidence": ["tests/corpus/5448-fs-glob-patterns.ts", "tests/corpus/5449-fs-glob-options.ts", "tests/corpus/5450-fs-glob-validation.js", "tests/corpus/5451-fs-promises-glob.ts", "tests/corpus/5452-fs-glob-callback.ts", "tests/fixtures/node-types/fs-glob.ts"] }, { "chapter": "fs", "signatures": ["`fs.cpSync(src, dest[, options])`", "`fs.cp(src, dest[, options], callback)`", "`fsPromises.cp(src, dest[, options])`"], "status": "partial", "evidence": ["tests/corpus/5567-fs-cpsync.ts", "tests/corpus/5568-fs-cp-errors.js", "tests/corpus/5569-fs-cp-symlinks.ts", "tests/corpus/5570-fs-cp-async.ts"] }, - { "chapter": "process", "signatures": ["`process.send(message[, sendHandle[, options]][, callback])`", "`process.disconnect()`", "`connected` Type: {boolean}", "Event: `'message'`", "Event: `'disconnect'`"], "status": "partial", "evidence": ["tests/corpus/2946-child-fork-ipc/main.ts"] }, + { "chapter": "process", "signatures": ["`process.send(message[, sendHandle[, options]][, callback])`", "`process.disconnect()`", "`connected` Type: {boolean}", "Event: `'message'`", "Event: `'disconnect'`"], "status": "partial", "evidence": ["tests/corpus/2946-child-fork-ipc/main.ts", "tests/corpus/6420-process-send-presence.ts", "tests/corpus/6421-process-send-presence-fork/main.ts"] }, { "chapter": "fs", "symbols": ["fs.Stats", "stats.isBlockDevice", "stats.isCharacterDevice", "stats.isDirectory", "stats.isFIFO", "stats.isFile", "stats.isSocket", "stats.isSymbolicLink"], "status": "partial", "evidence": ["tests/corpus/5541-fs-stats-fields.cjs"] }, { "chapter": "fs", "parent": "Class: `fs.Stats`", "signatures": ["`mode` Type: {number|bigint}", "`nlink` Type: {number|bigint}", "`uid` Type: {number|bigint}", "`gid` Type: {number|bigint}", "`rdev` Type: {number|bigint}", "`size` Type: {number|bigint}", "`blksize` Type: {number|bigint}", "`blocks` Type: {number|bigint}", "`atimeMs` Type: {number|bigint}", "`mtimeMs` Type: {number|bigint}", "`birthtimeMs` Type: {number|bigint}", "`atimeNs` Type: {bigint}", "`mtimeNs` Type: {bigint}", "`ctimeNs` Type: {bigint}", "`birthtimeNs` Type: {bigint}", "`atime` Type: {Date}", "`mtime` Type: {Date}", "`ctime` Type: {Date}", "`birthtime` Type: {Date}"], "status": "partial", "evidence": ["tests/corpus/5541-fs-stats-fields.cjs"] }, { "chapter": "fs", "signatures": ["`dev` Type: {number|bigint}", "`ino` Type: {number|bigint}", "`ctimeMs` Type: {number|bigint}"], "status": "partial", "evidence": ["tests/corpus/2941-fsp-lstat-identity.ts"] }, { "chapter": "fs", "signatures": ["`filehandle[Symbol.asyncDispose]()`"], "status": "partial", "evidence": ["tests/corpus/2844-node-disposable-resources.ts"] }, { "chapter": "fs", "signatures": ["`fsPromises.realpath(path[, options])`"], "status": "partial", "evidence": ["tests/corpus/2940-fsp-realpath.ts"] }, { "chapter": "fs", "signatures": ["`fsPromises.readdir(path[, options])`"], "status": "partial", "evidence": ["tests/corpus/2942-fsp-readdir-dirent.ts"] }, - { "chapter": "readline", "signatures": ["`rl[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/corpus/2844-node-disposable-resources.ts"] }, + { "chapter": "readline", "signatures": ["`rl[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/corpus/2844-node-disposable-resources.ts", "tests/corpus/5876-readline-dispose-instanceof.ts"] }, { "chapter": "timers", "signatures": ["`immediate[Symbol.dispose]()`", "`timeout[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/corpus/2844-node-disposable-resources.ts"] }, { "chapter": "stream", "signatures": ["`readable[Symbol.asyncIterator]()`", "`readable.iterator([options])`"], "status": "partial", "evidence": ["tests/corpus/1746-stream-for-await.ts", "tests/corpus/2861-readable-iterator-close.ts"] }, { "chapter": "stream", "signatures": ["`writable.destroy([error])`"], "status": "partial", "evidence": ["tests/corpus/2897-child-stdin-destroy.ts"] }, @@ -370,7 +379,7 @@ { "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.filePath", "passed", "attempt", "tags", "workerId"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-members.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `SuiteContext`", "symbols": ["context.name", "context.fullName", "context.filePath", "context.diagnostic", "passed", "attempt"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-context.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.diagnostic", "context.name"], "status": "partial", "evidence": ["tests/harness/node-test.test.ts"] }, - { "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.fn"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/harness/node-test.test.ts"] }, + { "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.fn"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/fixtures/node-test/cases/mock-fn-default.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.method", "mock.reset", "mock.restoreAll"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-method.ts", "tests/corpus/5810-mock-method-records.ts", "tests/corpus/5811-mock-tracker-restore.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `MockTimers`", "symbols": ["timers.enable", "timers.reset", "timers.tick", "timers.runAll", "timers.setTime"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-timers.ts", "tests/fixtures/node-test/cases/mock-timers-date.ts", "tests/corpus/5813-mock-timers-module.ts", "tests/corpus/5814-mock-timers-date.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "parent": "Class: `MockTimers`", "signatures": ["`timers[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-timers-date.ts", "tests/harness/node-test.test.ts"] }, @@ -378,10 +387,13 @@ { "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.before", "context.after", "context.beforeEach", "context.afterEach", "context.plan", "context.fullName"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-hooks.ts", "tests/fixtures/node-test/cases/ctx-plan.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "symbols": ["getTestContext"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/get-test-context.ts", "tests/fixtures/node-test/cases/get-test-context-cancelled.ts", "tests/harness/node-test.test.ts"] }, { "chapter": "test", "symbols": ["describe.skip", "describe.todo", "describe.only", "suite.skip", "suite.todo", "suite.only", "it.skip", "it.todo", "it.only"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-variants.ts", "tests/fixtures/node-test/cases/only-nested.ts", "tests/fixtures/node-test/cases/todo-suites.ts", "tests/harness/node-test.test.ts"] }, - { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/harness/project-config.test.ts"] }, + { "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts"] }, { "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2702-type-import-link-static/main.ts", "tests/corpus/2703-type-import-link-static-reexport/main.ts", "tests/corpus/2704-type-import-link-static-success/main.ts", "tests/corpus/2707-type-import-link-static-default-reexport/main.ts", "tests/harness/errors.test.ts"] }, { "chapter": "zlib", "signatures": ["Class: `zlib.Deflate`", "Class: `zlib.Inflate`", "Class: `zlib.Gzip`", "Class: `zlib.Gunzip`", "Class: `zlib.DeflateRaw`", "Class: `zlib.InflateRaw`", "Class: `zlib.Unzip`", "Class: `zlib.ZlibBase`", "`zlib.close([callback])`", "`zlib.flush([kind, ]callback)`", "`zlib.params(level, strategy, callback)`", "`zlib.reset()`", "`bytesWritten` Type: {number}", "Class: `Options`"], "status": "partial", "evidence": ["tests/corpus/5370-zlib-stream-roundtrip.ts", "tests/corpus/5371-zlib-stream-chunking.cjs", "tests/corpus/5372-zlib-stream-errors.cjs", "tests/corpus/5373-zlib-stream-options.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts", "tests/corpus/5375-zlib-stream-promises.ts", "tests/corpus/5377-zlib-stream-backpressure.cjs"] }, - { "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] } + { "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] }, + { "chapter": "dns", "symbols": ["dns.lookup", "dns.lookupService", "dns.getServers", "dns.getDefaultResultOrder", "dns.setDefaultResultOrder"], "status": "partial", "evidence": ["tests/corpus/5648-dns-lookup-forms.ts", "tests/corpus/5649-dns-service-servers.ts", "tests/corpus/5650-dns-lookup-errors-untyped.cjs"] }, + { "chapter": "dns", "symbols": ["dnsPromises.lookup", "dnsPromises.lookupService", "dnsPromises.getDefaultResultOrder"], "status": "partial", "evidence": ["tests/corpus/dns-promises-lookup.ts", "tests/corpus/dns-promises-untyped.cjs"] }, + { "chapter": "webstreams", "symbols": ["ReadableStream"], "status": "partial", "evidence": ["tests/corpus/stream-web-readable.ts", "tests/corpus/stream-web-require.cjs", "tests/corpus/2862-web-readable-async-iteration.ts"] } ], "dedicated": [ { "symbols": ["assert.doesNotThrow", "assert.doesNotReject"], "status": "partial", "evidence": ["tests/corpus/node24-no-error-assertions.ts", "tests/corpus/node24-no-error-assertions.cjs", "tests/corpus/node24-no-error-assertions-coercion.js", "tests/fixtures/node-types/no-error-assertions.ts", "tests/harness/project-config.test.ts"] }, @@ -416,7 +428,7 @@ { "symbols": ["dgram.createSocket", "socket.address", "socket.bind", "socket.close", "socket.connect", "socket.ref", "socket.remoteAddress", "socket.send", "socket.unref"], "status": "partial", "evidence": ["tests/corpus/2597-dgram-send-ladders.cjs"] }, { "symbols": ["net.SocketAddress", "net.BlockList"], "status": "partial", "evidence": ["tests/corpus/5659-net-blocklist-socketaddress.ts", "tests/fixtures/node-types/net-blocklist.ts"] }, { "symbols": ["SocketAddress.parse", "BlockList.isBlockList", "blockList.addAddress", "blockList.addAddresses", "blockList.addCIDR", "blockList.addCIDRs", "blockList.addRange", "blockList.addSubnet", "blockList.check", "blockList.clear", "blockList.fromJSON", "blockList.removeAddress", "blockList.removeCIDR", "blockList.removeRange", "blockList.removeSubnet", "blockList.toJSON"], "status": "supported", "evidence": ["tests/corpus/5659-net-blocklist-socketaddress.ts", "tests/fixtures/node-types/net-blocklist.ts"] }, - { "symbols": ["readline.createInterface", "rl.close"], "status": "partial", "evidence": ["tests/corpus/1475-readline-closed-stdin.ts"] }, + { "symbols": ["readline.createInterface", "rl.close"], "status": "partial", "evidence": ["tests/corpus/1475-readline-closed-stdin.ts", "tests/corpus/5878-readline-dynamic-streams.mjs"] }, { "symbols": ["test", "it", "test.skip", "test.todo", "test.only", "describe", "suite", "before", "after", "beforeEach", "afterEach", "context.test", "context.skip", "context.todo", "context.assert"], "status": "partial", "evidence": ["tests/harness/node-test.test.ts"] }, { "symbols": ["http.request", "http.get", "http.createServer", "https.request", "https.get", "https.createServer", "request.end", "request.destroy", "request.setTimeout", "request.write", "response.end", "response.getHeader", "response.hasHeader", "response.removeHeader", "response.setHeader", "response.write", "response.writeHead", "headersTimeout", "requestTimeout", "timeout", "keepAliveTimeout", "keepAliveTimeoutBuffer"], "status": "partial", "evidence": ["tests/corpus/1780-http-res-surface.ts", "tests/corpus/2672-http-request-response-callback.ts", "tests/corpus/2689-http-server-timeout-properties.ts"] } ] diff --git a/package.json b/package.json index 7318e04f5..03a763372 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "bench:backend-analysis": "node scripts/bench-backend-analysis.mjs", "test": "vitest run", "test:ts7": "node scripts/test-ts7.mjs", + "test:checker-identity": "node scripts/checker-identity.mjs", "generate:ts7-ast": "node scripts/generate-ts7-ast-schema.mjs", "test:fetch-conformance": "vitest run tests/harness/fetch-conformance.test.ts", "test:test262": "tsx tests/test262/run.ts", diff --git a/packages/cli-darwin-arm64/package.json b/packages/cli-darwin-arm64/package.json index ff55c940e..3c6e73e91 100644 --- a/packages/cli-darwin-arm64/package.json +++ b/packages/cli-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-darwin-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for darwin-arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-darwin-x64/package.json b/packages/cli-darwin-x64/package.json index b3b0a58dc..51121824b 100644 --- a/packages/cli-darwin-x64/package.json +++ b/packages/cli-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-darwin-x64", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for darwin-x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-arm64-gnu/package.json b/packages/cli-linux-arm64-gnu/package.json index ef9fe8bcc..1273cdfb8 100644 --- a/packages/cli-linux-arm64-gnu/package.json +++ b/packages/cli-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-arm64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for linux-arm64-gnu", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-arm64-musl/package.json b/packages/cli-linux-arm64-musl/package.json index a33eb60ab..bdba7d919 100644 --- a/packages/cli-linux-arm64-musl/package.json +++ b/packages/cli-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-arm64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for linux-arm64-musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-x64-gnu/package.json b/packages/cli-linux-x64-gnu/package.json index 25cd08204..3de4dcb72 100644 --- a/packages/cli-linux-x64-gnu/package.json +++ b/packages/cli-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-x64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for linux-x64-gnu", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-linux-x64-musl/package.json b/packages/cli-linux-x64-musl/package.json index b07577400..97a1fae89 100644 --- a/packages/cli-linux-x64-musl/package.json +++ b/packages/cli-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-linux-x64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for linux-x64-musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli-win32-x64-msvc/package.json b/packages/cli-win32-x64-msvc/package.json index 23dc94994..9dcd31fa2 100644 --- a/packages/cli-win32-x64-msvc/package.json +++ b/packages/cli-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/cli-win32-x64-msvc", - "version": "0.3.6", + "version": "0.4.0", "description": "Native scriptc compiler for win32-x64-msvc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli/package.json b/packages/cli/package.json index 434b76fe6..cfe908775 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "scriptc", - "version": "0.3.6", + "version": "0.4.0", "description": "Compile ordinary TypeScript and JavaScript to small, fast native executables and WebAssembly modules that run without Node.js", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/cli/scripts/install-native.mjs b/packages/cli/scripts/install-native.mjs index 0d125970d..84eb846bc 100644 --- a/packages/cli/scripts/install-native.mjs +++ b/packages/cli/scripts/install-native.mjs @@ -42,6 +42,7 @@ export function relocateToolchain(manifest, sourceDirectory, destinationDirector const pathFrom = (value) => relative(destinationDirectory, resolve(sourceDirectory, value)); for (const name of [ "ts7", + "tsc_ts", "llvm_package", "runtime_pack", "runtime_sources", @@ -96,6 +97,7 @@ export function installNativeCli( if (process.platform !== "win32") { for (const path of [ manifest.ts7, + ...(manifest.tsc_ts === undefined ? [] : [manifest.tsc_ts]), manifest.comptime, join(manifest.llvm_package, "bin/scriptc-llvm-codegen"), ]) { diff --git a/packages/cli/src/bootstrap.ts b/packages/cli/src/bootstrap.ts index 2d12883fa..1466707d6 100644 --- a/packages/cli/src/bootstrap.ts +++ b/packages/cli/src/bootstrap.ts @@ -116,6 +116,13 @@ async function tryFastPath(): Promise { // the whole compiler graph to rediscover it. const helperRuntimePackTarget = !values.sanitize ? startup.precompiledRuntimePackTarget() : null; const helperObjectRoute = helperRuntimePackTarget !== null; + // The full compiler reports a checker it cannot find. + let checker: string | undefined; + try { + checker = startup.checkerCacheIdentity(); + } catch { + return null; + } const hit = await startup.readRoutedExecutableCache(root, { entryPath: input, outDir, @@ -126,6 +133,7 @@ async function tryFastPath(): Promise { ...(optimization === "dev" || optimization === "speed" ? { optimization } : {}), ...(values.strip ? { strip: true as const } : {}), ...(values["detached-exit"] ? { detachedExit: true as const } : {}), + ...(checker === undefined ? {} : { checker }), npmStatic, ffiProfile: ffiPath === null ? null : { path: ffiPath, bytes: ffiBytes! }, target: `${process.env["SCRIPTC_TARGET"] ?? "native"}:${buildPlatform}:${process.arch}:${ diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index a8f1f8a8b..6dfcc90ef 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -80,21 +80,54 @@ declare namespace NodeJS { path?: string | undefined; syscall?: string | undefined; } - /* The piped child-output stream (child.stdout / child.stderr under - * stdio ["ignore", "pipe", "pipe"]): 'data' fires one Buffer chunk per - * read (≤64KB, consumer-driven — no listener, no read), 'end' fires - * once at EOF and always BEFORE the child's 'exit'. A flowing stream - * keeps the loop alive. */ + /* The readable-stream interface every Node stream implements. The + * piped child-output stream (child.stdout / child.stderr under stdio + * ["ignore", "pipe", "pipe"]) is declared with it: 'data' fires one + * Buffer chunk per read (≤64KB, consumer-driven — no listener, no + * read), 'end' fires once at EOF and always BEFORE the child's 'exit'. A + * flowing stream keeps the loop alive. A program that also passes + * other streams (fs, zlib, node:stream) where this interface is + * expected compiles it as the node:stream Readable, with child output + * bridged into one. */ interface ReadableStream { on(event: "data", listener: (chunk: Buffer) => void): void; on(event: "data", listener: (chunk: string) => void): void; on(event: "end", listener: () => void): void; + on(event: string, listener: (...args: any[]) => void): void; once(event: "data", listener: (chunk: Buffer) => void): void; once(event: "data", listener: (chunk: string) => void): void; once(event: "end", listener: () => void): void; + once(event: string, listener: (...args: any[]) => void): void; /* setEncoding('utf8') makes subsequent data chunks strings, including * multibyte sequences split across native pipe reads. */ setEncoding(encoding: string): this; + readonly readable: boolean; + pause(): this; + resume(): this; + isPaused(): boolean; + pipe(destination: T, options?: { end?: boolean }): T; + unpipe(destination?: WritableStream): this; + [Symbol.asyncIterator](): AsyncIterableIterator; + } + /* The writable-stream interface every Node stream implements: + * process.stdout/process.stderr, fs.WriteStream and the node:stream + * Writable family. Written only to the process streams, it compiles as + * their descriptor; with other streams flowing into it, as the + * node:stream Writable (the process streams convert into one). */ + interface WritableStream { + readonly writable: boolean; + write(data: string | Uint8Array, callback?: (error?: Error | null) => void): boolean; + write( + data: string | Uint8Array, + encoding: BufferEncoding, + callback?: (error?: Error | null) => void, + ): boolean; + end(callback?: () => void): void; + end(data: string | Uint8Array, callback?: () => void): void; + end(data: string | Uint8Array, encoding: BufferEncoding, callback?: () => void): void; + /* Node's EventEmitter signature: listeners take untyped arguments. */ + on(event: string, listener: (...args: any[]) => void): void; + once(event: string, listener: (...args: any[]) => void): void; } } @@ -1271,8 +1304,37 @@ declare module "node:perf_hooks" { getEntriesByType(type: EntryType): PerformanceEntry[]; clearMarks(name?: string): void; clearMeasures(name?: string): void; + eventLoopUtilization( + utilization1?: EventLoopUtilization, + utilization2?: EventLoopUtilization, + ): EventLoopUtilization; } export const performance: Performance; + /* Event-loop metrics: utilization (idle/active ms since the loop started) + * and monitorEventLoopDelay's sampling histogram (nanoseconds). */ + export interface EventLoopUtilization { + idle: number; + active: number; + utilization: number; + } + export interface EventLoopMonitorOptions { + resolution?: number | undefined; + } + export interface Histogram { + readonly count: number; + readonly exceeds: number; + readonly max: number; + readonly mean: number; + readonly min: number; + readonly stddev: number; + percentile(percentile: number): number; + reset(): void; + } + export interface IntervalHistogram extends Histogram { + enable(): boolean; + disable(): boolean; + } + export function monitorEventLoopDelay(options?: EventLoopMonitorOptions): IntervalHistogram; } declare module "perf_hooks" { export * from "node:perf_hooks"; @@ -1878,6 +1940,13 @@ declare module "node:fs" { declare module "fs" { export * from "node:fs"; } +/* graceful-fs — served as node:fs (it monkeypatches fs and retries on + * EMFILE/ENFILE; compiled programs call fs directly, without the retry + * queue). gracefulify is a no-op statement. The @types/graceful-fs shape. */ +declare module "graceful-fs" { + export * from "node:fs"; + export function gracefulify(fsModule: T): T; +} /* fs/promises — the SAME synchronous operations behind already-settled * promises: success fulfills, failure REJECTS with the would-be thrown @@ -3059,8 +3128,10 @@ declare module "child_process" { destroy(): void; on(event: "drain" | "finish", listener: () => void): void; on(event: "error", listener: (err: Error) => void): void; + on(event: string, listener: (...args: any[]) => void): void; once(event: "drain" | "finish", listener: () => void): void; once(event: "error", listener: (err: Error) => void): void; + once(event: string, listener: (...args: any[]) => void): void; } export interface ChildProcess extends Disposable { @@ -3372,6 +3443,13 @@ declare module "util" { info: import("node:crypto").BinaryLike, keylen: number, ) => Promise; + /* A callback-only function (`obj.close.bind(obj)`): before the targets + * whose leading parameters would otherwise capture it. Builtins that take + * arguments before their callback never match these. */ + export function promisify( + fn: (callback: (err: any, result: TResult) => void) => void, + ): () => Promise; + export function promisify(fn: (callback: (err?: any) => void) => void): () => Promise; export function promisify( fn: typeof import("node:crypto").randomFill, ): ( @@ -4006,7 +4084,10 @@ declare module "readline" { meta?: boolean; shift?: boolean; } - export interface Interface extends Disposable { + export class Interface implements Disposable { + /* createInterface creates interfaces; instanceof tells the two + * modules' classes apart. */ + protected constructor(); /* terminal says whether the interface edits lines as a terminal; * line and cursor are the terminal's (line is '' otherwise). history * is the history option itself (or the interface's own empty array); @@ -4121,7 +4202,10 @@ declare module "readline/promises" { /* The readline Interface with the promise question: it resolves with * the next line, rejects with an AbortError when options.signal aborts, * and with ERR_USE_AFTER_CLOSE after close(). */ - export interface Interface extends Disposable { + export class Interface implements Disposable { + /* createInterface creates interfaces; instanceof tells the two + * modules' classes apart. */ + protected constructor(); /* terminal says whether the interface edits lines as a terminal; * line and cursor are the terminal's (line is '' otherwise). history * is the history option itself (or the interface's own empty array); @@ -4674,6 +4758,11 @@ declare module "net" { on(event: "close", listener: (hadError: boolean) => void): void; on(event: "error", listener: (err: Error) => void): void; on(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + on( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; /* addListener IS on (Node aliases them) — the suite spells both. */ addListener(event: "data", listener: (chunk: Buffer) => void): void; /* After setEncoding the chunk is a string. */ @@ -4685,6 +4774,11 @@ declare module "net" { addListener(event: "close", listener: (hadError: boolean) => void): void; addListener(event: "error", listener: (err: Error) => void): void; addListener(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + addListener( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; once(event: "data", listener: (chunk: Buffer) => void): void; /* After setEncoding the chunk is a string. */ once(event: "data", listener: (chunk: string) => void): void; @@ -4695,6 +4789,11 @@ declare module "net" { once(event: "close", listener: (hadError: boolean) => void): void; once(event: "error", listener: (err: Error) => void): void; once(event: "session", listener: (session: Buffer) => void): void; + /* The host name's resolution, before the connect. */ + once( + event: "lookup", + listener: (err: Error | null, address: string, family: number, host: string) => void, + ): void; } export interface Server { /* Whether the server keeps the event loop alive (ref'd by default). */ @@ -4713,6 +4812,8 @@ declare module "net" { /* The positional bind address (the options form's host in argument * spelling). */ listen(port: number, host: string, callback?: () => void): Server; + /* A Unix-socket server; the socket file is removed when it closes. */ + listen(path: string, callback?: () => void): Server; /* The explicit-interface bind: host is an IP literal (absent = the * host-less dual-stack any); ipv6Only sets IPV6_V6ONLY and reusePort * requests kernel connection distribution where Node supports it. */ @@ -5036,10 +5137,11 @@ declare module "http" { * callback receives the response as an IncomingMessage (statusCode, * headers, body via on("data"/"end")). */ export interface RequestOptions { - hostname?: string; - host?: string; - port?: number; - path?: string; + hostname?: string | null; + host?: string | null; + /* A string port (a URL's .port) is accepted, as by Node. */ + port?: number | string | null; + path?: string | null; method?: string; timeout?: number; headers?: OutgoingHttpHeaders; @@ -5053,6 +5155,8 @@ declare module "http" { agent?: Agent | boolean | null; /* Aborting destroys the request with an AbortError. */ signal?: AbortSignal; + /* HTTP over the Unix socket at this path instead of host:port. */ + socketPath?: string; /* The options-record stance: Node ignores an options record's * unknown keys, so every key typechecks here — the option WALK in * the lowering decides per key (lower, fence by name, or drop @@ -5238,6 +5342,8 @@ declare module "tls" { getProtocol(): string | null; /* OpenSSL's suite name, the IANA name and the suite's version */ getCipher(): CipherNameAndProtocol; + /* whether the handshake resumed a cached session */ + isSessionReused(): boolean; } export interface CipherNameAndProtocol { name: string; @@ -5326,6 +5432,7 @@ declare module "https" { agent?: Agent | boolean | null; /* Aborting destroys the request with an AbortError. */ signal?: AbortSignal; + socketPath?: string; /* The options-record stance (see http.RequestOptions). */ [option: string]: unknown; } @@ -6143,10 +6250,79 @@ declare module "dns" { export function getServers(): string[]; export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; + /* The promise forms (node:dns/promises). */ + export const promises: typeof import("dns/promises"); } declare module "node:dns" { export * from "dns"; } +/* node:dns/promises — lookup and lookupService as promises over the same + * resolution as the callback forms: argument errors throw synchronously, + * failures reject with the callbacks' errors. */ +declare module "dns/promises" { + import type { LookupAddress, LookupAllOptions, LookupOneOptions, LookupOptions } from "dns"; + export function lookup(hostname: string, family: number): Promise; + export function lookup(hostname: string, options: LookupOneOptions): Promise; + export function lookup(hostname: string, options: LookupAllOptions): Promise; + export function lookup( + hostname: string, + options: LookupOptions, + ): Promise; + export function lookup(hostname: string): Promise; + export function lookupService( + address: string, + port: number, + ): Promise<{ hostname: string; service: string }>; + export function getServers(): string[]; + export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim"; + export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void; +} +declare module "node:dns/promises" { + export * from "dns/promises"; +} + +/* node:v8 — importable for the packages that require it at top level. + * getHeapStatistics answers allocator-derived figures in Node's shape + * (approximate: the runtime heap's total and used bytes, the machine's + * memory as the limit; V8-only figures are 0). The other members observe + * V8 itself and fence at their use with the by-design reason; they are + * declared so programs that type-check under their own tsc reach that + * fence. */ +declare module "v8" { + type DoesZapCodeSpaceFlag = 0 | 1; + export interface HeapInfo { + total_heap_size: number; + total_heap_size_executable: number; + total_physical_size: number; + total_available_size: number; + used_heap_size: number; + heap_size_limit: number; + malloced_memory: number; + peak_malloced_memory: number; + does_zap_garbage: DoesZapCodeSpaceFlag; + number_of_native_contexts: number; + number_of_detached_contexts: number; + total_global_handles_size: number; + used_global_handles_size: number; + external_memory: number; + } + export function getHeapStatistics(): HeapInfo; + export function cachedDataVersionTag(): number; + export function getHeapSpaceStatistics(): unknown[]; + export function getHeapCodeStatistics(): unknown; + export function getHeapSnapshot(options?: unknown): unknown; + export function writeHeapSnapshot(fileName?: string, options?: unknown): string; + export function setFlagsFromString(flags: string): void; + export function setHeapSnapshotNearHeapLimit(limit: number): void; + export function serialize(value: unknown): Buffer; + export function deserialize(buffer: Uint8Array): unknown; + export function takeCoverage(): void; + export function stopCoverage(): void; + export function isStringOneByteRepresentation(content: string): boolean; +} +declare module "node:v8" { + export * from "v8"; +} /* node:worker_threads — native workers use statically compiled entry points. * Lowering and runtime diagnostics define the supported options and methods. */ @@ -6302,7 +6478,7 @@ declare module "node:cluster" { /* node:tty — the fd probe (the same isatty(3) behind process.*.isTTY) * and the supported structural view of a process output stream. */ declare module "tty" { - export interface WriteStream { + export interface WriteStream extends NodeJS.WritableStream { write(data: string | Uint8Array, callback?: (error?: Error | null) => void): boolean; write( data: string | Uint8Array, @@ -6393,7 +6569,7 @@ declare module "events" { export { internal as EventEmitter }; // The event's arguments, as checked-dynamic values. export function once( - emitter: EventEmitter, + emitter: EventEmitter | NodeJS.ReadableStream | NodeJS.WritableStream, eventName: string, options?: { signal?: AbortSignal }, ): Promise; @@ -6552,8 +6728,8 @@ declare module "stream" { resume(): this; isPaused(): boolean; setEncoding(encoding: string): this; - pipe(destination: T, options?: { end?: boolean }): T; - unpipe(destination?: Writable): this; + pipe(destination: T, options?: { end?: boolean }): T; + unpipe(destination?: NodeJS.WritableStream): this; destroy(error?: Error): this; readonly readable: boolean; readonly readableEnded: boolean; @@ -6936,6 +7112,22 @@ declare module "node:stream/consumers" { import streamConsumers = require("stream/consumers"); export = streamConsumers; } +/* node:stream/web — the module exports the web stream globals themselves + * (require("stream/web").ReadableStream === globalThis.ReadableStream). + * ReadableStream is the implemented class; its reader and controller are + * exported as types. */ +declare module "stream/web" { + export const ReadableStream: typeof globalThis.ReadableStream; + export type ReadableStream = globalThis.ReadableStream; + export type ReadableStreamDefaultReader = globalThis.ReadableStreamDefaultReader; + export type ReadableStreamDefaultController = + globalThis.ReadableStreamDefaultController; + export type UnderlyingSource = globalThis.UnderlyingSource; + export type QueuingStrategy = globalThis.QueuingStrategy; +} +declare module "node:stream/web" { + export * from "stream/web"; +} interface ErrorConstructor { stackTraceLimit: number; } diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 6e5442dc6..0c058c467 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/compiler", - "version": "0.3.6", + "version": "0.4.0", "description": "The scriptc compiler — TypeScript/JavaScript frontend, typed IR, LLVM backend", "license": "Apache-2.0", "homepage": "https://scriptc.dev", @@ -39,6 +39,7 @@ }, "dependencies": { "@scriptc/runtime": "workspace:*", + "tsc-ts": "0.0.5", "typescript": "7.0.2", "typescript5": "npm:typescript@5.9.3" }, diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index eeb6c6900..0fcf86ada 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -16,6 +16,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { fileHandle: features.fileHandle, fetch: features.fetch, ...(features.eventTarget ? { eventTarget: true } : {}), + ...(features.wasm ? { wasm: true } : {}), ...(features.warningReplace ? { warningReplace: true } : {}), ...(features.stdioExtended ? { stdioExtended: true } : {}), zlib: features.zlib, @@ -25,18 +26,23 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { dc: features.dc, dynAsync: features.dynAsync, ...(features.dynTier ? { dynTier: true } : {}), + ...(features.streamDyn ? { streamDyn: true } : {}), + ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), events: features.processEvents, emitter: features.emitter, symbol: features.symbol, bigint: features.bigint, searchParams: features.searchParams, qs: features.qs, + dynBuiltinClass: features.dynBuiltinClass, + dynBuiltinStream: features.dynBuiltinStream, parseArgs: features.parseArgs, stream: features.stream, net: features.net, http: features.http, http2: features.http2, dgram: features.dgram, + dnsPromises: features.dnsPromises, watch: features.fsWatch, foreignFfi: hasForeignFfiCallback(mod.ffiImports ?? []), nodeTest: features.nodeTest, @@ -44,5 +50,6 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures { tlsCa: features.tlsCa, cipher: features.cipher, webcrypto: features.webcrypto, + perfLoop: features.perfLoop, }; } diff --git a/packages/compiler/src/backend/library-pack.ts b/packages/compiler/src/backend/library-pack.ts index 5070a47a8..cb929e4b0 100644 --- a/packages/compiler/src/backend/library-pack.ts +++ b/packages/compiler/src/backend/library-pack.ts @@ -42,18 +42,22 @@ export async function compilePackedLibrary( bigint: options.bigint ?? false, searchParams: options.searchParams ?? false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, tls: false, cipher: false, webcrypto: false, + perfLoop: false, tlsCa: false, }; const selection = await loadRuntimePack({ diff --git a/packages/compiler/src/backend/llvm/byte-numbers.ts b/packages/compiler/src/backend/llvm/byte-numbers.ts index c51d03744..9001e015d 100644 --- a/packages/compiler/src/backend/llvm/byte-numbers.ts +++ b/packages/compiler/src/backend/llvm/byte-numbers.ts @@ -4,6 +4,7 @@ import type { LlValue, LlvmEmitterContext } from "./expr-context.js"; import { f64Lit } from "./common.js"; import { exactInteger, widenInteger } from "./integer-values.js"; import { emitTruncF64 } from "./trunc.js"; +import { bytesElementAlias, bytesHeaderAlias } from "./bytes-alias.js"; /** Supported LLVM targets are little-endian. All memory accesses use align * 1: Buffer fields and DataView windows can start at arbitrary byte offsets. */ @@ -79,7 +80,7 @@ export function emitByteNumber( capacity = B.tmp(); room = B.tmp(); B.line(`${lengthPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver.name}, i64 0, i32 1`); - B.line(`${length} = load ${size}, ptr ${lengthPtr}`); + B.line(`${length} = load ${size}, ptr ${lengthPtr}${bytesHeaderAlias(host)}`); B.line(`${room} = icmp uge ${size} ${length}, ${spec.width}`); B.line(`${capacity} = sub ${size} ${length}, ${spec.width}`); if (integer) { @@ -221,14 +222,14 @@ export function emitByteNumber( } } const stored = endianBits(host, raw, spec.width, little); - B.line(`store i${bits} ${stored}, ptr ${pointer}, align 1`); + B.line(`store i${bits} ${stored}, ptr ${pointer}, align 1${bytesElementAlias(host)}`); if (!spec.dataView) { fastResult = B.tmp(); B.line(`${fastResult} = fadd double ${offset.name}, ${f64Lit(spec.width)}`); } } else { const loaded = B.tmp(); - B.line(`${loaded} = load i${bits}, ptr ${pointer}, align 1`); + B.line(`${loaded} = load i${bits}, ptr ${pointer}, align 1${bytesElementAlias(host)}`); const raw = endianBits(host, loaded, spec.width, little); fastResult = B.tmp(); if (spec.floating) { diff --git a/packages/compiler/src/backend/llvm/bytes-alias.ts b/packages/compiler/src/backend/llvm/bytes-alias.ts new file mode 100644 index 000000000..9aaeaa6c7 --- /dev/null +++ b/packages/compiler/src/backend/llvm/bytes-alias.ts @@ -0,0 +1,23 @@ +import type { LlvmEmitterContext } from "./expr-context.js"; + +/** Alias categories of typed-array storage. A ScrBytes header (length, + * data pointer, shared backing) is fixed at construction and only ever + * written by runtime code, never by emitted code. Element storage is one + * category for every element type: views of different types may share a + * buffer. Neither overlaps the other, a native field, array storage or a + * module global's slot, so element stores leave header loads and global + * receiver slots loop-invariant. Untagged accesses and calls still alias + * every category. */ +export function bytesHeaderAlias(host: LlvmEmitterContext): string { + return host.memoryAliasAttachment("bytes:header"); +} + +export function bytesElementAlias(host: LlvmEmitterContext): string { + return host.memoryAliasAttachment("bytes:elements"); +} + +/** A module global's slot is its own category for whole-slot loads of a + * scalar or typed-array binding. Stores stay untagged (and so alias it). */ +export function globalSlotAlias(host: LlvmEmitterContext, id: string, type: string): string { + return type === "bytes" || type === "f64" ? host.memoryAliasAttachment(`global:${id}`) : ""; +} diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts new file mode 100644 index 000000000..ddcfd667d --- /dev/null +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.test.ts @@ -0,0 +1,278 @@ +import { expect, test } from "vitest"; +import { + BOOL, + F64, + VOID, + type IrExpr, + type IrFunction, + type IrLocal, + type IrModule, + type IrStmt, + type IrType, +} from "../../ir/ir.js"; +import { analyzeIntegerRanges } from "../../ir/integer-ranges.js"; +import { matchIntegerCountedForLoop } from "../../ir/integer-loops.js"; +import { findInitializedByteLoopBindings } from "./byte-windows.js"; +import { matchBytesLoopVersion, type BytesLoopVersion } from "./bytes-loop-versions.js"; +import { emitLlvmModule } from "./emitter.js"; + +const loc = { file: "versions.ts", start: 0, end: 0 }; +const f64s: IrType = { kind: "bytes", elem: "f64" }; +const i32s: IrType = { kind: "bytes", elem: "i32" }; +const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc }); +const ref = (localId: string, type: IrType = F64): IrExpr => ({ + kind: "varRef", + localId, + type, + loc, +}); +const bin = (op: string, left: IrExpr, right: IrExpr): IrExpr => + ({ kind: "bin", op, left, right, type: F64, loc }) as IrExpr; +const get = (receiver: IrExpr, index: IrExpr): IrExpr => ({ + kind: "bytesIntrinsic", + method: "get", + receiver, + args: [index], + invalidNaN: true, + type: F64, + loc, +}); +const set = (arr: IrExpr, index: IrExpr, value: IrExpr): IrStmt => ({ + kind: "bytesSet", + arr, + index, + value, + loc, +}); +const counted = ( + body: IrStmt[], + limit: IrExpr = ref("n"), + counter = "i", +): IrStmt & { kind: "for" } => ({ + kind: "for", + init: { kind: "varDecl", localId: counter, init: num(0), loc }, + cond: { kind: "bin", op: "<", left: ref(counter), right: limit, type: BOOL, loc }, + update: { kind: "assign", localId: counter, value: bin("+", ref(counter), num(1)), loc }, + body, + loc, +}); +const local = ( + id: string, + type: IrType, + mutable = false, + extra: Partial = {}, +): IrLocal => ({ + id, + name: id, + type, + mutable, + ...extra, +}); + +function fn(body: IrStmt[], extra: IrLocal[] = []): IrFunction { + return { + name: "kernel", + params: [ + { localId: "out", name: "out", type: f64s }, + { localId: "src", name: "src", type: f64s }, + { localId: "table", name: "table", type: i32s }, + { localId: "n", name: "n", type: F64 }, + { localId: "k", name: "k", type: F64 }, + ], + returnType: VOID, + locals: [ + local("out", f64s), + local("src", f64s), + local("table", i32s), + local("n", F64), + local("k", F64), + local("i", F64, true), + local("j", F64, true), + ...extra, + ], + body, + loc, + }; +} + +function match( + f: IrFunction, + loop: IrStmt & { kind: "for" }, + captured: string[] = [], +): BytesLoopVersion | null { + const ranges = analyzeIntegerRanges(f); + const locals = new Map(f.locals.map((l) => [l.id, l])); + return matchBytesLoopVersion(loop, matchIntegerCountedForLoop(loop, locals, ranges), { + locals, + invariantGlobals: new Set(), + captured: new Set(captured), + ranges, + initialized: findInitializedByteLoopBindings(f).get(loop) ?? new Set(), + proven: new Set(), + }); +} + +test("a counted loop's affine accesses get one guard and exact operand ranges", () => { + const shifted = bin("+", ref("i"), num(1)); + const loop = counted([set(ref("out", f64s), ref("i"), get(ref("src", f64s), shifted))]); + const version = match(fn([loop]), loop)!; + expect(version).not.toBeNull(); + expect(version.bounds.size).toBe(2); + expect(version.receivers.map((r) => r.ref.kind === "varRef" && r.ref.localId)).toEqual([ + "out", + "src", + ]); + expect(version.affine.map((c) => [c.scale, c.minOffset, c.maxEnd])).toEqual([ + [1, 0, 0], + [1, 1, 1], + ]); + // `n` is unproven: the guard bounds the counter so `i + 1` stays exact. + expect(version.counted?.limitGuarded).toBe(true); + expect(version.counterBound).toBe(Number.MAX_SAFE_INTEGER - 1); + expect(version.ranges.get(shifted)).toEqual({ min: 1, max: Number.MAX_SAFE_INTEGER }); +}); + +test("statically ranged indices compare one constant with the length", () => { + const masked = bin("&", bin("^", ref("i"), ref("k")), num(255)); + const loop = counted([set(ref("out", f64s), num(0), get(ref("table", i32s), masked))]); + const version = match(fn([loop]), loop)!; + expect(version.fixed.map((c) => [version.receivers[c.receiver]!.ref, c.end])).toEqual([ + [ref("out", f64s), 0], + [ref("table", i32s), 255], + ]); + expect(version.affine).toEqual([]); + expect(version.counted).toBeNull(); +}); + +test("scaled counters and body temps resolve to their receiver and index", () => { + const temps = [local("view", f64s), local("at", F64)]; + const at = bin("+", bin("*", ref("i"), num(4)), num(3)); + const loop = counted([ + { kind: "varDecl", localId: "view", init: ref("src", f64s), loc }, + { kind: "varDecl", localId: "at", init: at, loc }, + set(ref("view", f64s), ref("at"), num(1)), + // (The integer counted loop needs one direct counter index.) + set(ref("out", f64s), ref("i"), num(0)), + ]); + const version = match(fn([loop], temps), loop)!; + expect(version.receivers.map((r) => r.ref)).toEqual([ref("src", f64s), ref("out", f64s)]); + expect(version.affine.map((c) => [c.scale, c.minOffset, c.maxEnd])).toEqual([ + [4, 3, 3], + [1, 0, 0], + ]); + expect(version.ranges.has(at)).toBe(true); +}); + +test("negative factors, rebinding, captures and outer loops are not versioned", () => { + // `i * -1` is -0 at zero, so it is not treated as an affine operand. + const negative = counted([set(ref("out", f64s), bin("*", ref("i"), num(-1)), num(1))]); + expect(match(fn([negative]), negative)).toBeNull(); + const rebound = counted([ + set(ref("out", f64s), ref("i"), num(1)), + { kind: "assign", localId: "out", value: ref("src", f64s), loc }, + ]); + expect(match(fn([rebound]), rebound)).toBeNull(); + const plain = counted([set(ref("out", f64s), ref("i"), num(1))]); + expect(match(fn([plain]), plain, ["out"])).toBeNull(); + const inner = counted([set(ref("out", f64s), ref("j"), num(1))], ref("n"), "j"); + const outer = counted([inner]); + expect(match(fn([outer]), outer)).toBeNull(); + expect(match(fn([outer]), inner)).not.toBeNull(); +}); + +test("emitted loops version the body and tag typed-array memory", () => { + const loop = counted([ + set(ref("out", f64s), ref("i"), bin("*", get(ref("src", f64s), ref("i")), num(2))), + ]); + const kernel = fn([loop]); + const mod: IrModule = { + irVersion: 17, + sourceFile: loc.file, + entry: "__main", + functions: [ + { name: "__main", params: [], returnType: VOID, locals: [], body: [], loc }, + kernel, + ], + }; + const llvm = emitLlvmModule(mod); + const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(llvm)![0]; + expect(body).toContain("bytes.version.fast"); + expect(body).toContain("bytes.version.slow"); + // The checked copy keeps its per-element tests; the guard reads lengths. + expect(body).toContain("bytes.index.invalid"); + expect(body).toMatch(/store double [^\n]*, align 1, !tbaa !\d+/); + expect(body).toMatch(/load double, ptr [^\n]*, align 1, !tbaa !\d+/); + expect(body).toMatch(/load i64, ptr [^\n]*, !tbaa !\d+/); + expect(body).toMatch(/load ptr, ptr [^\n]*, !tbaa !\d+/); + const elements = /store double [^\n]*!tbaa !(\d+)/.exec(body)![1]; + const header = /load ptr, ptr [^\n]*!tbaa !(\d+)/.exec(body)![1]; + expect(elements).not.toBe(header); +}); + +test("Float16Array accesses version in elements and share the element tag", () => { + const f16s: IrType = { kind: "bytes", elem: "f16" }; + const withHalves = (body: IrStmt[]): IrFunction => { + const f = fn(body, [local("half", f16s)]); + return { ...f, params: [...f.params, { localId: "half", name: "half", type: f16s }] }; + }; + // half[i] = src[i + 1]; out[i] = half[i] * 2 + const loop = counted([ + set(ref("half", f16s), ref("i"), get(ref("src", f64s), bin("+", ref("i"), num(1)))), + set(ref("out", f64s), ref("i"), bin("*", get(ref("half", f16s), ref("i")), num(2))), + ]); + const kernel = withHalves([loop]); + const version = match(kernel, loop)!; + expect(version.bounds.size).toBe(4); + expect(version.receivers.map((r) => r.ref)).toEqual([ + ref("half", f16s), + ref("src", f64s), + ref("out", f64s), + ]); + // Element offsets against the element count, whatever the element size. + expect(version.affine.map((c) => [c.scale, c.minOffset, c.maxEnd])).toEqual([ + [1, 0, 0], + [1, 1, 1], + [1, 0, 0], + ]); + + const mod: IrModule = { + irVersion: 17, + sourceFile: loc.file, + entry: "__main", + functions: [ + { name: "__main", params: [], returnType: VOID, locals: [], body: [], loc }, + kernel, + ], + }; + const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; + const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0]; + expect(fast).toContain("call double @scr_f16_to_f64"); + expect(fast).toContain("call zeroext i16 @scr_f16_from_f64"); + expect(fast).not.toContain("bytes.index"); + const halfStore = /store i16 [^\n]*, align 1, !tbaa !(\d+)/.exec(fast)![1]; + const halfLoad = /load i16, ptr [^\n]*, align 1, !tbaa !(\d+)/.exec(fast)![1]; + const doubleStore = /store double [^\n]*, align 1, !tbaa !(\d+)/.exec(fast)![1]; + // Views of any element type may share a buffer: one element category. + expect(halfStore).toBe(doubleStore); + expect(halfLoad).toBe(doubleStore); + // The checked copy keeps its per-element tests. + expect(body.slice(0, body.indexOf(fast))).toContain("bytes.index.invalid"); +}); + +test("bodies compiled without optimization keep one checked loop", () => { + const loop = counted([set(ref("out", f64s), ref("i"), get(ref("src", f64s), ref("i")))]); + const boxes = Array.from({ length: 512 }, (_, k) => local(`box${k}`, F64, true, { boxed: true })); + const mod: IrModule = { + irVersion: 17, + sourceFile: loc.file, + entry: "__main", + functions: [ + { name: "__main", params: [], returnType: VOID, locals: [], body: [], loc }, + fn([loop], boxes), + ], + }; + const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0]; + expect(body.split("\n", 1)[0]).toContain("optnone"); + expect(body).not.toContain("bytes.version"); + expect(body).toContain("bytes.index.invalid"); +}); diff --git a/packages/compiler/src/backend/llvm/bytes-loop-versions.ts b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts new file mode 100644 index 000000000..df8977426 --- /dev/null +++ b/packages/compiler/src/backend/llvm/bytes-loop-versions.ts @@ -0,0 +1,578 @@ +import type { IrExpr, IrLocal, IrStmt } from "../../ir/ir.js"; +import type { IntegerRange, IntegerRanges } from "../../ir/integer-ranges.js"; +import type { IntegerCountedForLoop } from "../../ir/integer-loops.js"; +import type { LlvmEmitterContext } from "./expr-context.js"; +import { byteNumberAccess } from "../../ir/byte-numbers.js"; +import { everyExprChild, everyStmtChild } from "../../ir/traverse.js"; +import { exactInteger, widenInteger } from "./integer-values.js"; +import { emitCountedLoopLimit, emitCountedLoopSafety } from "./counted-loops.js"; +import { emitBytesHeaderLength } from "./expr-bytes.js"; + +type Node = IrExpr | IrStmt; +type VarRef = IrExpr & { kind: "varRef" }; + +/** One loop-invariant integer operand of an affine index. */ +interface Term { + expr: IrExpr; + /** 1 or -1. */ + sign: number; +} + +interface KeyedTerm extends Term { + key: string; +} + +/** Every access of `receiver` at a static index range ends below `end + 1`. */ +interface FixedCheck { + receiver: number; + end: number; +} + +/** Every access of `receiver` at `scale * counter + Σ terms + offset` has an + * offset in [minOffset, maxEnd - width + 1]; `maxEnd` includes the width. */ +interface AffineCheck { + receiver: number; + scale: number; + terms: KeyedTerm[]; + minOffset: number; + maxEnd: number; +} + +export interface BytesLoopVersion { + /** Typed-array accesses that are in bounds whenever the guard holds. */ + bounds: ReadonlySet; + /** Root receiver bindings, read once before the loop. */ + receivers: { ref: VarRef; global: boolean }[]; + fixed: FixedCheck[]; + affine: AffineCheck[]; + /** The counted loop whose iteration space the affine checks cover. */ + counted: IntegerCountedForLoop | null; + /** The guard also requires every counter value within [-bound, bound]. */ + counterBound: number | null; + /** Integer ranges of affine index operands that hold in the unchecked + * loop (where the guard bounds the counter). */ + ranges: ReadonlyMap; +} + +export interface BytesLoopFacts { + /** The function's own locals (not module globals). */ + locals: ReadonlyMap; + /** Module globals whose slot no call can replace: stable constants and + * the private bindings of a guarded initializer. */ + invariantGlobals: ReadonlySet; + captured: ReadonlySet; + ranges: IntegerRanges; + /** Locals definitely initialized when the loop is entered. */ + initialized: ReadonlySet; + /** Accesses already proved in bounds without a guard. */ + proven: ReadonlySet; +} + +const MAX_COST = 400; +const MAX_FIXED_END = 2 ** 32; +const MAX_CHECKS = 8; +const MAX_OFFSET = 2 ** 31; +const SAFE = Number.MAX_SAFE_INTEGER; +const INVARIANT_OPS = new Set(["+", "-", "*", "&", "|", "^", "<<", ">>", ">>>"]); + +/** Loop versioning for typed-array bounds. A typed array's length and data + * pointer are fixed at construction (ScrBytes has no resize, transfer or + * detach), so an access is in bounds for the whole loop when its receiver + * binding and index operands cannot change while it runs. One test before + * the loop then covers every iteration: + * + * - a statically ranged integer index `[lo, hi]` (lo >= 0) needs + * `hi < length`, such as `table[x & 0xff]` or a constant-trip counter; + * - an index `k * counter + invariant + c` of a counted loop needs its first + * and last values in bounds, such as `out[base + i]` for `i < n` or + * `view.getUint32(i * 4)`. + * + * Only small synchronous loops are versioned (nested loops included), and + * the emitter bounds how deeply versions nest. The checked loop runs + * whenever the guard fails, and keeps every out-of-bounds read, ignored + * write and NaN-index behavior. */ +export function matchBytesLoopVersion( + loop: IrStmt & { kind: "for" }, + counted: IntegerCountedForLoop | null, + facts: BytesLoopFacts, +): BytesLoopVersion | null { + const written = new Set(); + const temps = new Map(); + let cost = 0; + const scanExpr = (e: IrExpr): boolean => { + if (++cost > MAX_COST) return false; + if (e.kind === "assignExpr" || e.kind === "incDec") written.add(e.localId); + return everyExprChild(e, scanExpr, scanStmt); + }; + const scanStmt = (s: IrStmt): boolean => { + if (++cost > MAX_COST) return false; + switch (s.kind) { + case "forOf": + written.add(s.localId); + break; + case "varDecl": { + if (temps.has(s.localId) || written.has(s.localId)) temps.delete(s.localId); + else if (s.init) temps.set(s.localId, s.init); + written.add(s.localId); + break; + } + case "assign": + written.add(s.localId); + break; + case "tryCatch": + if (s.catchLocalId) written.add(s.catchLocalId); + break; + default: + break; + } + return everyStmtChild(s, scanExpr, scanStmt); + }; + // The guard runs before the initializer, so a binding the initializer + // writes is not invariant either: `for (let i = (a = b, 0); ...)` must + // not keep the bounds the guard proved for the old `a`. + if ( + (loop.init !== null && !scanStmt(loop.init)) || + !loop.body.every(scanStmt) || + (loop.cond !== null && !scanExpr(loop.cond)) || + (loop.update !== null && !scanStmt(loop.update)) + ) + return null; + // A body temp stands for its initializer only when it is a constant + // binding assigned exactly once, by that declaration. + const declared = new Map(); + const countDecls = (s: IrStmt): boolean => { + if (s.kind === "varDecl") declared.set(s.localId, (declared.get(s.localId) ?? 0) + 1); + return everyStmtChild(s, countExprDecls, countDecls); + }; + const countExprDecls = (e: IrExpr): boolean => everyExprChild(e, countExprDecls, countDecls); + loop.body.every(countDecls); + const temp = (id: string): IrExpr | null => { + const local = facts.locals.get(id); + const init = temps.get(id); + if ( + !init || + !local || + local.mutable || + local.boxed || + local.tdz || + facts.captured.has(id) || + declared.get(id) !== 1 + ) + return null; + return init; + }; + const counter = counted?.localId ?? null; + // A binding keeps its loop-entry value for the whole loop. + const invariant = (id: string): boolean => { + if (written.has(id) || id === counter) return false; + const local = facts.locals.get(id); + if (local) + return !local.boxed && !local.tdz && !facts.captured.has(id) && facts.initialized.has(id); + return facts.invariantGlobals.has(id); + }; + const resolving = new Set(); + const root = (e: IrExpr): VarRef | null => { + if (e.kind !== "varRef" || e.type.kind !== "bytes") return null; + const init = temp(e.localId); + if (init) { + if (resolving.has(e.localId)) return null; + resolving.add(e.localId); + const result = root(init); + resolving.delete(e.localId); + return result; + } + return invariant(e.localId) ? e : null; + }; + // A pure integer operand evaluated again before the loop. + const invariantKey = (e: IrExpr): string | null => { + if (!facts.ranges.get(e)) return null; + if (e.kind === "numLit") return Number.isSafeInteger(e.value) ? `n${e.value}` : null; + if (e.kind === "varRef") + return invariant(e.localId) ? `v${e.localId.length}:${e.localId}` : null; + if ( + e.kind === "bin" && + INVARIANT_OPS.has(e.op) && + e.left.type.kind === "f64" && + e.right.type.kind === "f64" + ) { + const left = invariantKey(e.left), + right = invariantKey(e.right); + return left !== null && right !== null ? `(${e.op} ${left} ${right})` : null; + } + return null; + }; + interface Affine { + /** The counter's coefficient; 0 when the value is loop-invariant. */ + counter: number; + terms: KeyedTerm[]; + offset: number; + } + // Every operand of an affine index is `counter * k + Σ terms + offset`. + // Invariant terms need proven ranges. Operands that involve the counter + // need none: the guard bounds the counter so that each of them is a safe + // integer, which makes the index's double arithmetic exact. + const shapes = new Map(); + const affine = (e: IrExpr): Affine | null => { + if (shapes.has(e)) return shapes.get(e)!; + const result = affineShape(e); + shapes.set(e, result); + return result; + }; + const affineShape = (e: IrExpr): Affine | null => { + if (e.kind === "numLit") + return Number.isSafeInteger(e.value) && Math.abs(e.value) <= MAX_OFFSET + ? { counter: 0, terms: [], offset: e.value } + : null; + if (e.kind === "varRef") { + if (e.localId === counter) return { counter: 1, terms: [], offset: 0 }; + const init = temp(e.localId); + if (init) { + if (resolving.has(e.localId)) return null; + resolving.add(e.localId); + const result = affine(init); + resolving.delete(e.localId); + return result; + } + } + if ( + e.kind === "bin" && + (e.op === "+" || e.op === "-") && + e.left.type.kind === "f64" && + e.right.type.kind === "f64" + ) { + const left = affine(e.left), + right = affine(e.right); + if (left && right && (left.counter === 0 || right.counter === 0)) { + const sign = e.op === "+" ? 1 : -1; + const offset = left.offset + sign * right.offset; + if (Math.abs(offset) > MAX_OFFSET) return null; + const terms = [ + ...left.terms, + ...right.terms.map((t) => ({ expr: t.expr, sign: sign * t.sign, key: t.key })), + ]; + const result = { counter: left.counter + sign * right.counter, terms, offset }; + if (result.counter !== 0 || facts.ranges.get(e)) return result; + } + } + // A positive constant multiple of the counter: `i * 4`, `4 * i`, + // `(i + 1) * 8`. (A negative factor turns a zero into -0.) + if (e.kind === "bin" && e.op === "*") { + const rightLiteral = e.right.kind === "numLit"; + const scaled = rightLiteral ? e.left : e.right; + const literal = rightLiteral ? e.right : e.left; + const factor = literal.kind === "numLit" ? literal.value : 0; + if ( + literal.kind === "numLit" && + scaled.type.kind === "f64" && + Number.isSafeInteger(factor) && + factor > 0 && + factor <= MAX_OFFSET + ) { + const inner = affine(scaled); + if (inner && inner.counter !== 0 && inner.terms.length === 0) { + const counterScale = inner.counter * factor, + offset = inner.offset * factor; + if (Math.abs(counterScale) <= MAX_OFFSET && Math.abs(offset) <= MAX_OFFSET) + return { counter: counterScale, terms: [], offset }; + } + } + } + const key = invariantKey(e); + return key !== null ? { counter: 0, terms: [{ expr: e, sign: 1, key }], offset: 0 } : null; + }; + // The operands of an index that involve the counter. + const chain = (e: IrExpr, out: Set): void => { + if (!shapes.get(e)?.counter) return; + out.add(e); + if (e.kind === "varRef") { + const init = temp(e.localId); + if (init) chain(init, out); + } else if (e.kind === "bin") { + chain(e.left, out); + chain(e.right, out); + } + }; + const termMagnitude = (terms: Term[]): number => + terms.reduce((sum, t) => { + const r = facts.ranges.get(t.expr)!; + return sum + Math.max(Math.abs(r.min), Math.abs(r.max)); + }, 0); + + const receivers: { ref: VarRef; global: boolean }[] = []; + const receiverIndex = new Map(); + const fixed = new Map(); + const affineChecks = new Map< + string, + { check: AffineCheck; nodes: Node[]; operands: Set } + >(); + const receiverOf = (ref: VarRef): number => { + let index = receiverIndex.get(ref.localId); + if (index === undefined) { + index = receivers.length; + receiverIndex.set(ref.localId, index); + receivers.push({ ref, global: !facts.locals.has(ref.localId) }); + } + return index; + }; + const access = (node: Node, array: IrExpr, index: IrExpr, width: number): void => { + if (facts.proven.has(node)) return; + const ref = root(array); + if (!ref) return; + const shape = counter !== null ? affine(index) : null; + if (shape && shape.counter !== 0) { + const operands = new Set(); + chain(index, operands); + const terms = [...shape.terms].sort((a, b) => + a.key < b.key ? -1 : a.key > b.key ? 1 : a.sign - b.sign, + ); + const receiver = receiverOf(ref); + const key = `${receiver}|${shape.counter}|${terms.map((t) => `${t.sign}${t.key}`).join(",")}`; + const end = shape.offset + width - 1; + const group = affineChecks.get(key); + if (group) { + group.check.minOffset = Math.min(group.check.minOffset, shape.offset); + group.check.maxEnd = Math.max(group.check.maxEnd, end); + group.nodes.push(node); + for (const operand of operands) group.operands.add(operand); + } else + affineChecks.set(key, { + check: { receiver, scale: shape.counter, terms, minOffset: shape.offset, maxEnd: end }, + nodes: [node], + operands, + }); + return; + } + const range = facts.ranges.get(index); + if (!range) return; + const end = range.max + width - 1; + // Only a length a buffer can have: an outer loop must not pay for a + // guard that bounds an inner loop's unbounded counter. + if (range.min < 0 || end >= MAX_FIXED_END) return; + const receiver = receiverOf(ref); + const group = fixed.get(receiver); + if (group) { + group.check.end = Math.max(group.check.end, end); + group.nodes.push(node); + } else fixed.set(receiver, { check: { receiver, end }, nodes: [node] }); + }; + const visitExpr = (e: IrExpr): boolean => { + if (e.kind === "bytesIntrinsic") { + const numeric = byteNumberAccess(e); + if (numeric) access(e, e.receiver, e.args[numeric.offsetArg]!, numeric.width); + else if (e.method === "get" && e.args[0]) access(e, e.receiver, e.args[0], 1); + } + return everyExprChild(e, visitExpr, visitStmt); + }; + const visitStmt = (s: IrStmt): boolean => { + if (s.kind === "bytesSet") access(s, s.arr, s.index, 1); + return everyStmtChild(s, visitExpr, visitStmt); + }; + loop.body.every(visitStmt); + // Bound the counter so every counter operand stays a safe integer. + let bound = SAFE; + const affineGroups = [...affineChecks.values()].filter((group) => { + let limit = SAFE; + for (const operand of group.operands) { + const shape = shapes.get(operand)!; + const room = SAFE - termMagnitude(shape.terms) - Math.abs(shape.offset); + limit = Math.min(limit, Math.floor(room / Math.abs(shape.counter))); + } + if (limit < 0 || !counted) return false; + bound = Math.min(bound, limit); + return true; + }); + const fixedKept = [...fixed.values()].slice(0, MAX_CHECKS); + const affineKept = affineGroups.slice(0, MAX_CHECKS - fixedKept.length); + if (fixedKept.length === 0 && affineKept.length === 0) return null; + const bounds = new Set(); + const used = new Set(); + const ranges = new Map(); + const counterLow = Math.max(counted?.range.min ?? 0, -bound), + counterHigh = Math.min(counted?.range.max ?? 0, bound); + for (const group of fixedKept) { + used.add(group.check.receiver); + for (const node of group.nodes) bounds.add(node); + } + for (const group of affineKept) { + used.add(group.check.receiver); + for (const node of group.nodes) bounds.add(node); + for (const operand of group.operands) { + if (facts.ranges.get(operand)) continue; + const shape = shapes.get(operand)!; + const a = shape.counter * counterLow, + b = shape.counter * counterHigh; + let min = Math.min(a, b) + shape.offset, + max = Math.max(a, b) + shape.offset; + for (const term of shape.terms) { + const r = facts.ranges.get(term.expr)!; + min += term.sign > 0 ? r.min : -r.max; + max += term.sign > 0 ? r.max : -r.min; + } + if (counterLow <= counterHigh) ranges.set(operand, { min, max }); + } + } + // Renumber the receivers the kept checks use. + const order = [...used].sort((a, b) => a - b); + const renumber = new Map(order.map((old, index) => [old, index])); + const needsBound = + affineKept.length > 0 && + counted !== null && + (counted.range.min < -bound || counted.range.max > bound); + return { + bounds, + receivers: order.map((index) => receivers[index]!), + fixed: fixedKept.map((g) => ({ + receiver: renumber.get(g.check.receiver)!, + end: g.check.end, + })), + affine: affineKept.map((g) => ({ + receiver: renumber.get(g.check.receiver)!, + scale: g.check.scale, + terms: g.check.terms, + minOffset: g.check.minOffset, + maxEnd: g.check.maxEnd, + })), + counted: affineKept.length > 0 ? counted : null, + counterBound: needsBound ? bound : null, + ranges, + }; +} + +/** Branches to `fast` when every access the version proves is in bounds for + * every iteration, and to `slow` otherwise (including a counted loop whose + * integer form is not established, or a module receiver not yet set). */ +export function emitBytesLoopVersionGuard( + host: LlvmEmitterContext, + version: BytesLoopVersion, + fast: string, + slow: string, +): void { + const B = host.B; + const next = (cond: string): void => { + if (cond === "true") return; + const ok = B.newLabel("bytes.version.check"); + B.condBr(cond, ok, slow); + B.startBlock(ok); + }; + const counted = version.counted; + if (counted?.guarded) next(emitCountedLoopSafety(host, counted)); + const receivers = version.receivers.map(({ ref, global }) => { + const value = host.emitReadReceiver(ref).name; + if (global) { + const set = B.tmp(); + B.line(`${set} = icmp ne ptr ${value}, null`); + next(set); + } + return value; + }); + const lengths = receivers.map((receiver) => { + const len = emitBytesHeaderLength(host, receiver); + if (host.sizeType === "i64") return len; + const wide = B.tmp(); + B.line(`${wide} = zext ${host.sizeType} ${len} to i64`); + return wide; + }); + const conditions: string[] = []; + for (const check of version.fixed) { + const ok = B.tmp(); + B.line(`${ok} = icmp ugt i64 ${lengths[check.receiver]}, ${check.end}`); + conditions.push(ok); + } + if (counted && version.affine.length > 0) { + // The first and last counter values, in ascending order. The last value + // of a non-unit step is exact: `start + ⌊(limit ∓ 1 - start) / step⌋ * + // step`. A loop that runs no iteration may compute anything here. + const startValue = host.emitExpr(counted.start); + const startInteger = exactInteger(host, startValue, counted.start); + let start: string; + if (startInteger) start = widenInteger(host, startInteger); + else { + // The counted-loop safety test above proved an exact integer. + start = B.tmp(); + B.line(`${start} = fptosi double ${startValue.name} to i64`); + } + const limit = emitCountedLoopLimit(host, counted); + const step = Math.abs(counted.step); + const edge = B.tmp(); + B.line(`${edge} = add i64 ${limit}, ${counted.step > 0 ? -1 : 1}`); + let last = edge; + if (step !== 1) { + const span = B.tmp(), + whole = B.tmp(), + covered = B.tmp(); + last = B.tmp(); + B.line( + counted.step > 0 + ? `${span} = sub i64 ${edge}, ${start}` + : `${span} = sub i64 ${start}, ${edge}`, + ); + B.line(`${whole} = sdiv i64 ${span}, ${step}`); + B.line(`${covered} = mul i64 ${whole}, ${step}`); + B.line(`${last} = ${counted.step > 0 ? "add" : "sub"} i64 ${start}, ${covered}`); + } + const low = counted.step > 0 ? start : last; + const high = counted.step > 0 ? last : start; + if (version.counterBound !== null) { + const above = B.tmp(), + below = B.tmp(); + B.line(`${above} = icmp sge i64 ${low}, ${-version.counterBound}`); + B.line(`${below} = icmp sle i64 ${high}, ${version.counterBound}`); + conditions.push(above, below); + } + for (const check of version.affine) { + let base = "0"; + for (const term of check.terms) { + const value = host.emitExpr(term.expr); + const integer = exactInteger(host, value, term.expr); + if (!integer) { + // Unreachable: every term has a proven range. Stay checked. + B.br(slow); + B.startBlock(B.newLabel("bytes.version.dead")); + return; + } + const wide = widenInteger(host, integer); + const sum = B.tmp(); + B.line(`${sum} = ${term.sign > 0 ? "add" : "sub"} i64 ${base}, ${wide}`); + base = sum; + } + // Products wrap only for a loop with no iteration: within the + // iteration space every scaled counter is a proven safe integer. + const scaledLow = scaleCounter(host, low, check.scale), + scaledHigh = scaleCounter(host, high, check.scale); + const first = B.tmp(), + final = B.tmp(), + nonNegative = B.tmp(), + below = B.tmp(), + both = B.tmp(); + B.line(`${first} = add i64 ${check.scale > 0 ? scaledLow : scaledHigh}, ${base}`); + B.line(`${final} = add i64 ${check.scale > 0 ? scaledHigh : scaledLow}, ${base}`); + const firstOffset = B.tmp(), + lastEnd = B.tmp(); + B.line(`${firstOffset} = add i64 ${first}, ${check.minOffset}`); + B.line(`${lastEnd} = add i64 ${final}, ${check.maxEnd}`); + B.line(`${nonNegative} = icmp sge i64 ${firstOffset}, 0`); + // Lengths are at most 2^53 elements: a signed comparison is exact. + B.line(`${below} = icmp slt i64 ${lastEnd}, ${lengths[check.receiver]}`); + B.line(`${both} = and i1 ${nonNegative}, ${below}`); + conditions.push(both); + } + } + let valid = "true"; + for (const cond of conditions) { + if (valid === "true") valid = cond; + else { + const all = B.tmp(); + B.line(`${all} = and i1 ${valid}, ${cond}`); + valid = all; + } + } + if (valid === "true") B.br(fast); + else B.condBr(valid, fast, slow); +} + +function scaleCounter(host: LlvmEmitterContext, value: string, scale: number): string { + if (scale === 1) return value; + const scaled = host.B.tmp(); + host.B.line(`${scaled} = mul i64 ${value}, ${scale}`); + return scaled; +} diff --git a/packages/compiler/src/backend/llvm/counted-loops.ts b/packages/compiler/src/backend/llvm/counted-loops.ts index 03e0ed4f3..36fc63e1f 100644 --- a/packages/compiler/src/backend/llvm/counted-loops.ts +++ b/packages/compiler/src/backend/llvm/counted-loops.ts @@ -42,3 +42,54 @@ export function emitCountedLoopLimit( B.line(`${limit} = add nsw i64 ${integer}, ${ascending ? 1 : -1}`); return limit; } + +/** The run-time test of a guarded counted loop: an unproven limit is within + * the integer form's finite range and an unproven start is an exact safe + * integer. Returns `true` when nothing needs testing. */ +export function emitCountedLoopSafety( + host: LlvmEmitterContext, + loop: IntegerCountedForLoop, +): string { + const B = host.B; + let safe = "true"; + if (loop.limitGuarded) { + const bound = host.emitExpr(loop.limit); + safe = B.tmp(); + B.line( + `${safe} = fcmp ${loop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(loop.guardLimit)}`, + ); + } + if (loop.startGuarded) { + // An exact integer of at most 2^53 - 1 in magnitude, and not -0 + // (the bit patterns of the value and its round trip agree). + // NaN and infinities fail the ordered range tests. + const start = host.emitExpr(loop.start); + const low = B.tmp(), + high = B.tmp(), + raw = B.tmp(), + wide = B.tmp(), + back = B.tmp(), + bits = B.tmp(), + backBits = B.tmp(), + exact = B.tmp(), + inRange = B.tmp(), + ok = B.tmp(); + B.line(`${low} = fcmp oge double ${start.name}, ${f64Lit(-Number.MAX_SAFE_INTEGER)}`); + B.line(`${high} = fcmp ole double ${start.name}, ${f64Lit(Number.MAX_SAFE_INTEGER)}`); + B.line(`${inRange} = and i1 ${low}, ${high}`); + B.line(`${raw} = fptosi double ${start.name} to i64`); + B.line(`${wide} = freeze i64 ${raw}`); + B.line(`${back} = sitofp i64 ${wide} to double`); + B.line(`${bits} = bitcast double ${start.name} to i64`); + B.line(`${backBits} = bitcast double ${back} to i64`); + B.line(`${exact} = icmp eq i64 ${bits}, ${backBits}`); + B.line(`${ok} = and i1 ${inRange}, ${exact}`); + if (safe === "true") safe = ok; + else { + const both = B.tmp(); + B.line(`${both} = and i1 ${safe}, ${ok}`); + safe = both; + } + } + return safe; +} diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index cf7578c02..731385825 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -34,6 +34,7 @@ import { InternalCompilerError } from "../../errors.js"; * ScrDynPath { parent, key, index } — the %ScrDynPath type. */ import type { IrRecordShape, IrType, IrUnionDef } from "../../ir/ir.js"; import { + accessorSlotProp, DYN_HANDLE_KINDS, isDirentShape, isDynTypedRefType, @@ -129,6 +130,8 @@ export interface DynHost extends WalkerHost { /** The shared adapter record: live array capsules read its `arrayOps`, so a * narrower return type would hand the native compiler a copy without it. */ liveDynRefAdapter(t: IrType): LlStreamTypedRefAdapter; + /** See moduleUsesDynBuiltinStream. */ + usesDynBuiltinStream?(): boolean; liveDynUnionRefAdapter(t: IrType & { kind: "union" }): string; dynPromiseAdapter(t: IrType): string; isErrorClass(className: string): boolean; @@ -139,6 +142,19 @@ export interface DynHost extends WalkerHost { const FN_ATTRS = "#0"; +/** scr_dyn_builtin_stream_check's kind for a runtime stream class (the + * numbering of scr_dyn_builtin_class.c), or null. */ +function builtinStreamKind(className: string): number | null { + const kinds: Record = { + "%Readable": 2, + "%Writable": 3, + "%Duplex": 4, + "%Transform": 5, + "%PassThrough": 6, + }; + return kinds[className] ?? null; +} + export class LlDyn { private readonly dynMatchers = new Map(); private readonly dynBuilders = new Map(); @@ -613,6 +629,18 @@ export class LlDyn { * tracking does not count the read's reference. Returns the value and * the matching release. */ private memberRead(B: BlockBuilder, key: string, t: IrType): { value: string; release: string } { + // An accessor slot reads the getter or setter of the accessor property + // the checked-dynamic copy holds for it (see accessorSlotProp). + const slot = accessorSlotProp(key); + if (slot !== null) { + this.host.declare(`declare ptr @scr_dyn_obj_accessor_fn(ptr, ptr, ${this.S}, i1 zeroext)`); + this.host.declare(`declare void @scr_dyn_release(ptr)`); + const fn = B.tmp(); + B.line( + `${fn} = call ptr @scr_dyn_obj_accessor_fn(ptr %d, ptr ${this.host.cstr(slot.prop)}, ${this.S} ${Buffer.byteLength(slot.prop, "utf8")}, i1 ${slot.kind === "set" ? "true" : "false"})`, + ); + return { value: fn, release: "scr_dyn_release" }; + } const held = this.isRecursiveShape(t) || this.holdsRecursiveShape(t); const release = held ? "scr_dyn_release_held" : "scr_dyn_release"; this.host.declare(`declare void @${release}(ptr)`); @@ -762,7 +790,9 @@ export class LlDyn { B.terminate(`ret i1 true`); B.startBlock(lNext); } - if (t.kind !== "dyn") { + // A union matches a capsule of another type through its arms below: a + // class capsule in a `C | undefined` field matches the `C` arm. + if (t.kind !== "dyn" && t.kind !== "union") { const view = this.viewFn(wrapped); this.host.declare(`declare void @scr_dyn_release_v(ptr)`); const kind = this.kindOf(B, "%d"); @@ -1476,6 +1506,24 @@ export class LlDyn { B.line(`${ref} = call ptr @scr_dyn_typed_ref_unbox(ptr %d)`); B.terminate(`ret ptr ${ref}`); B.startBlock(lNext); + // A constructor-function stream (scr_dyn_builtin_stream.c) carries + // its runtime stream. + const streamKind = t.kind === "object" ? builtinStreamKind(t.className) : null; + if (streamKind !== null && this.host.usesDynBuiltinStream?.() === true) { + host.declare(`declare ptr @scr_dyn_builtin_stream_check(ptr, double)`); + const own = B.tmp(), + hit = B.tmp(); + B.line( + `${own} = call ptr @scr_dyn_builtin_stream_check(ptr %d, double ${f64Lit(streamKind)})`, + ); + B.line(`${hit} = icmp ne ptr ${own}, null`); + const lOwn = B.newLabel("dc.ctor"), + lNotOwn = B.newLabel("dc.notctor"); + B.condBr(hit, lOwn, lNotOwn); + B.startBlock(lOwn); + B.terminate(`ret ptr ${own}`); + B.startBlock(lNotOwn); + } if (t.kind !== "union" && preserveRefs) { const kind = this.kindOf(B, "%d"); const capsule = B.tmp(); @@ -2685,13 +2733,53 @@ export class LlDyn { // Keys insert in DECLARED order (JS insertion order); internal // '%'-fields follow so a record→dyn→record round trip keeps them. const byName = new Map(shape.fields.map((f) => [f.name, f])); - const order = shape.declaredOrder ?? shape.fields.map((f) => f.name); + // An accessor property is one key whose getter and setter are the + // slot closures (see accessorSlotProp), in accessorOrder's place. + const order = shape.accessorOrder ?? shape.declaredOrder ?? shape.fields.map((f) => f.name); const inOrder = new Set(order); - const dynFields = [ - ...order.map((n) => byName.get(n)).filter((f) => f !== undefined), - ...shape.fields.filter((f) => !inOrder.has(f.name)), + const accessors = new Set( + shape.accessorOrder === undefined + ? [] + : shape.fields.flatMap((f) => { + const slot = accessorSlotProp(f.name); + return slot === null ? [] : [slot.prop]; + }), + ); + type DynField = { name: string; type: IrType } | { accessor: string }; + const dynFields: DynField[] = [ + ...order.flatMap((n) => { + if (accessors.has(n)) return [{ accessor: n } as DynField]; + const f = byName.get(n); + return f === undefined ? [] : [f as DynField]; + }), + ...shape.fields.filter( + (f) => + !inOrder.has(f.name) && !(accessors.size > 0 && accessorSlotProp(f.name) !== null), + ), ]; - for (const f of dynFields) { + for (const entry of dynFields) { + if ("accessor" in entry) { + host.declare( + `declare void @scr_dyn_obj_set_accessor(ptr, ptr, ${host.sizeType}, ptr, ptr)`, + ); + const boxed = (kind: "get" | "set"): string => { + const slot = byName.get(`%${kind}:${entry.accessor}`); + if (slot === undefined) return "null"; + const fv = loadFieldOf(slot.name, slot.type); + const conv = B.tmp(); + B.line( + `${conv} = call ptr @${this.toDynHelper(slot.type)}(${this.valTy(slot.type)} ${fv})`, + ); + return conv; + }; + const getter = boxed("get"); + const setter = boxed("set"); + B.line( + `call void @scr_dyn_obj_set_accessor(ptr ${d}, ptr ${host.cstr(entry.accessor)}, ${host.sizeType} ${Buffer.byteLength(entry.accessor, "utf8")}, ptr ${getter}, ptr ${setter}) ; ${llvmCommentText(entry.accessor)}`, + ); + continue; + } + const f = entry; const klen = Buffer.byteLength(f.name, "utf8"); const fv = loadFieldOf(f.name, f.type); // An ABSENT optional field contributes no key. @@ -3263,8 +3351,9 @@ export class LlDyn { } B.startBlock(labels.get(DYN_KIND.BYTES)!); { - // Buffer-flavored values coerce utf8; plain Uint8Array joins - // its elements. + // Buffer-flavored values coerce utf8; typed arrays join their + // elements and a DataView answers "[object DataView]" (the + // runtime's conversion). const bufp = B.tmp(); const bufRaw = B.tmp(); const isBuf = B.tmp(); @@ -3286,10 +3375,9 @@ export class LlDyn { B.line(`call void @scr_str_release(ptr ${txt})`); B.br(done); B.startBlock(lJoin); - const bts = this.payloadOf(B, "%d", "ptr"); - host.declare(`declare ptr @scr_bytes_join(ptr, ptr)`); + host.declare(`declare ptr @scr_dyn_to_string(ptr, ptr)`); const joined = B.tmp(); - B.line(`${joined} = call ptr @scr_bytes_join(ptr ${bts}, ptr ${host.internLiteral(",")})`); + B.line(`${joined} = call ptr @scr_dyn_to_string(ptr %d, ptr null)`); this.putScrStr(B, "%b", joined); B.line(`call void @scr_str_release(ptr ${joined})`); B.br(done); diff --git a/packages/compiler/src/backend/llvm/emit-function.ts b/packages/compiler/src/backend/llvm/emit-function.ts new file mode 100644 index 000000000..9e50bea6d --- /dev/null +++ b/packages/compiler/src/backend/llvm/emit-function.ts @@ -0,0 +1,1352 @@ +/* LlEmitter's function emission: function bodies, generator and async scaffolding, async awaits, WASI fulfillment and trampolines. */ +import { + type LlArgPackAndTrampolinePrologue, + type LlEmitter, + type LlScopeEntry, + SUSPENDS, + UNOPTIMIZED_BOX_SLOTS, + boxSlotCount, +} from "./emitter.js"; +import { type IrFunction, type IrType, isRefCounted, typeKey } from "../../ir/ir.js"; +import { BlockBuilder } from "./blocks.js"; +import { findPrivateSplitLocals } from "./split-loops.js"; +import { + conditionalLoopWrites, + initializerLocalCandidates, + withInitializerBindings, + withInitializerLocals, +} from "../../ir/initializer-bindings.js"; +import { findUnaliasedLocalArrays } from "../../ir/array-length-stability.js"; +import { constructorClass } from "../../ir/final-fields.js"; +import { findInitializedByteLoopBindings } from "./byte-windows.js"; +import { findLoopArrayBorrows } from "./loop-array-borrows.js"; +import { findCallArrayReads, findLocalArrayReads } from "./local-array-reads.js"; +import { findScalarStringSlices, findScratchStringSlices } from "./string-slices.js"; +import { findMapReadLifetimes, findReadNumberMapWrites } from "./map-read-lifetimes.js"; +import { findMapEntryFusions } from "./map-entry-fusion.js"; +import { findLocalStackUnions } from "./stack-unions.js"; +import { findLocalStackObjects, findReassignedStackObjects } from "./stack-objects.js"; +import { findLocalUnionStorage } from "./local-union-storage.js"; +import { INT32_RANGE, analyzeIntegerRanges } from "../../ir/integer-ranges.js"; +import { findBytesBounds } from "./bytes-bounds.js"; +import { findInt32Locals } from "./int32-locals.js"; +import { findIntegerViews } from "./integer-views.js"; +import { + mangleArgPack, + mangleAsyncAdoptCall, + mangleAsyncAwaitCall, + mangleAsyncSpawn, + mangleBorrowedFunction, + mangleClassStruct, + mangleFunction, + mangleGenDrop, + mangleGenSourceClose, + mangleGenSpawn, + mangleGlobal, + mangleLocal, + mangleTrampoline, +} from "../mangle.js"; +import { InternalCompilerError } from "../../errors.js"; +import { classEnvironmentIndex } from "./classes.js"; +import { FN_ATTRS, boxNewCall, releaseSym, traceArg, vAdapters } from "./shapes.js"; +import type { LlValue } from "./expr-context.js"; +import { INT32_ABI_SUFFIX, SCALAR_UNION_RESULT } from "./int32-abi.js"; +import { f64Lit } from "./common.js"; +import { RESULT_ENTRY_PREFIX } from "./record-results.js"; +import { emitIntegerClone, emitIntegerDispatcher } from "./integer-clones.js"; + +export function emitFunction(emitter: LlEmitter, fn: IrFunction): string { + const B = new BlockBuilder(); + emitter.B = B; + emitter.currentFunctionName = fn.name; + // Fiber switches restore the active exception cell before control + // returns to a synchronous frame (emitPendingCheck). + B.excCellInvariant = fn.async !== true && fn.generator === undefined; + emitter.debugScope = emitter.debug?.function(fn) ?? null; + B.debugLocation = emitter.debug?.location(fn.loc, emitter.debugScope) ?? null; + emitter.frames = []; + emitter.scopes = []; + emitter.unwindCleanups.clear(); + emitter.jumpTargets = []; + emitter.currentLocals = new Map(fn.locals.map((l) => [l.id, l])); + emitter.lazyCaptureBoxes.clear(); + emitter.lazyStackBoxes.clear(); + // An unchanged captured parameter keeps its value in a plain slot; the + // binding is boxed only for closures (captureBox). + const lazyParameters = emitter.lazyCaptures.parameters(fn); + for (const id of lazyParameters) { + const { boxed: _boxed, ...plain } = emitter.currentLocals.get(id)!; + emitter.currentLocals.set(id, plain); + emitter.lazyCaptureBoxes.set(id, ""); + } + emitter.currentConstantCallbacks = + emitter.debug === null ? (emitter.constantCallbacks.get(fn.name) ?? new Map()) : new Map(); + // Preserve concrete source bindings for debugger inspection. Suspended + // functions keep the established array lifetime across continuations. + emitter.streamingSplitsEnabled = emitter.debug === null && !fn.async && !fn.generator; + emitter.stackCallbacks.reset(emitter.streamingSplitsEnabled); + emitter.stackCaptures.reset(fn, emitter.streamingSplitsEnabled, lazyParameters); + emitter.privateSplitLocals = emitter.streamingSplitsEnabled + ? findPrivateSplitLocals(fn) + : new Map(); + emitter.storedSplits.clear(); + emitter.splitSpans.clear(); + emitter.stringSlices.clear(); + const initializerBindings = emitter.initializerBindings.get(fn.name) ?? []; + const numericFn = withInitializerBindings(fn, initializerBindings); + emitter.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l])); + emitter.unaliasedArrays = findUnaliasedLocalArrays(fn); + emitter.borrowedParameters.clear(); + emitter.referenceWrites?.enter(fn); + emitter.reboundOwnerSlots.clear(); + emitter.projectedParameters.clear(); + const projectedParameterIndexes = emitter.callLifetimes.parameters.get(fn.name); + if (projectedParameterIndexes) + for (const index of projectedParameterIndexes) + emitter.projectedParameters.add(fn.params[index]!.localId); + emitter.stableCallBindings = emitter.callLifetimes.bindings.get(fn.name) ?? new Set(); + const borrowedParameterIndexes = emitter.callLifetimes.borrowed.get(fn.name); + if (borrowedParameterIndexes) { + // Rebound walk parameters borrow too, but only unchanged ones may + // serve as stable owners for call arguments and aliases. + for (const index of borrowedParameterIndexes) + if ( + !emitter.walkBorrowsByFunction.get(fn.name)?.has(fn.params[index]!.localId) && + !emitter.reboundByFunction.get(fn.name)?.has(fn.params[index]!.localId) + ) + emitter.borrowedParameters.add(fn.params[index]!.localId); + } + // A generator frame owns its unchanged parameters across suspensions. + const generatorParameters = emitter.callLifetimes.generatorParameters.get(fn.name); + if (generatorParameters !== undefined) + for (const id of generatorParameters) emitter.borrowedParameters.add(id); + emitter.captureIds = new Set( + [...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId), + ); + emitter.stableCaptureIds = + fn.captures !== undefined ? emitter.lazyCaptures.stableCaptures(fn.name) : new Set(); + emitter.integerLoopBindings.clear(); + emitter.countedLoopsEnabled = emitter.debug === null && !fn.async && !fn.generator; + emitter.speculativeLoopsEnabled = emitter.countedLoopsEnabled; + emitter.speculativeFunction = fn.name; + emitter.speculation = null; + emitter.speculationResume = null; + emitter.finalFieldBorrows = + !fn.async && + !fn.generator && + constructorClass(fn.name) === null && + emitter.finalFields.size > 0; + emitter.byteWindowsEnabled = emitter.countedLoopsEnabled; + emitter.bytesLoopVersionsEnabled = emitter.countedLoopsEnabled; + emitter.bytesLoopEntries = null; + emitter.bytesLoopFunction = fn; + emitter.denseLoopsEnabled = emitter.countedLoopsEnabled; + emitter.denseLoop = null; + emitter.denseLoopEntries = + emitter.denseLoopsEnabled && numericFn.locals.some((l) => l.type.kind === "array") + ? findInitializedByteLoopBindings(fn) + : new Map(); + emitter.byteWindowEntries = + emitter.byteWindowsEnabled && numericFn.locals.some((l) => l.type.kind === "bytes") + ? findInitializedByteLoopBindings(fn) + : new Map(); + emitter.integerViews.clear(); + emitter.fieldPointerTags.clear(); + emitter.integerArrayBindings.clear(); + emitter.loopArrayBorrows = + emitter.debug === null + ? findLoopArrayBorrows( + fn, + emitter.callLifetimes, + emitter.referenceEffects, + emitter.optionalArrayReads, + emitter.referenceWrites, + emitter.fnByName, + ) + : new Set(); + emitter.localArrayReads = findLocalArrayReads( + fn, + emitter.fnByName, + emitter.unionsById, + emitter.referenceEffects.functions, + emitter.callLifetimes, + emitter.optionalArrayReads, + emitter.loopArrayBorrows, + emitter.debug === null ? emitter.referenceEffects : undefined, + ); + emitter.callArrayReads = findCallArrayReads( + fn, + emitter.optionalArrayReads, + emitter.referenceEffects.functions, + emitter.callLifetimes, + emitter.debug === null ? emitter.referenceEffects : undefined, + ); + emitter.scalarStringSlices = + emitter.debug === null ? findScalarStringSlices(fn, emitter.callLifetimes) : new Map(); + emitter.scratchStringSlices = + emitter.debug === null + ? findScratchStringSlices( + fn, + emitter.callLifetimes, + emitter.scalarStringSlices, + emitter.lentStringParameters, + ) + : new Set(); + emitter.mapReadLifetimes = findMapReadLifetimes( + fn, + emitter.boxedUnionsById, + emitter.callLifetimes, + ); + emitter.readNumberMapWrites = findReadNumberMapWrites(fn); + emitter.mapEntryFusion = emitter.debug === null ? findMapEntryFusions(fn) : null; + emitter.localStackUnions = findLocalStackUnions( + fn, + emitter.callLifetimes, + emitter.boxedUnionsById, + ); + emitter.localStackObjects = emitter.stackObjects + ? findLocalStackObjects(emitter, fn, emitter.callLifetimes) + : new Map(); + emitter.reassignedStackObjects = emitter.stackObjects + ? findReassignedStackObjects(emitter, fn, emitter.callLifetimes) + : new Set(); + emitter.stackObjectSlots = new Map(); + emitter.stackObjectFieldPtrs = new Map(); + emitter.localUnionStorageProofs = findLocalUnionStorage( + fn, + emitter.callLifetimes, + emitter.unionSources, + ); + emitter.localUnionStorage = new Map(); + emitter.walkBorrows = emitter.walkBorrowsByFunction.get(fn.name) ?? new Set(); + const slotFacts = emitter.int32Slots.facts(fn.name); + emitter.integerRanges = analyzeIntegerRanges(numericFn, slotFacts); + emitter.int32Elements = slotFacts.elements ?? null; + emitter.bytesBounds = findBytesBounds(numericFn, emitter.integerRanges); + // Debug builds keep every number local a double for the debugger's + // variable view. Lazily boxed captured parameters keep their double, + // which the box is created from, and so do plain copies of a capture or + // such a parameter (copyStableCaptures): the source already holds the + // double, which an i32 copy would convert and widen again on every call. + // Initializer bindings proven int32 move into an i32 local of their + // initializer: no other code observes the global, so the loop-carried + // value never round-trips through the double slot. + const initializerLocals = initializerLocalCandidates(initializerBindings); + const int32Locals = + emitter.debug === null + ? findInt32Locals( + withInitializerLocals(fn, initializerLocals), + emitter.integerRanges, + emitter.int32Slots.facts(fn.name), + new Set([...emitter.lazyCaptureBoxes.keys(), ...emitter.doubleCopies(fn)]), + ) + : null; + emitter.int32Locals = int32Locals?.locals ?? new Map(); + emitter.int32LocalWrites = int32Locals?.values ?? new Set(); + const localizedGlobals = new Set(); + for (const global of initializerLocals) { + if (!emitter.int32Locals.has(global.id)) continue; + localizedGlobals.add(global.id); + emitter.currentLocals.set(global.id, { + id: global.id, + name: global.name, + type: global.type, + mutable: global.mutable, + }); + } + emitter.chainSlots.clear(); + emitter.finallyStack = []; + emitter.tryStack = []; + emitter.loopDepth = 0; + emitter.currentReturnType = fn.returnType; + const int32Abi = emitter.int32Abis.get(fn.name); + emitter.currentInt32Result = int32Abi?.result === true; + emitter.currentUnionResult = int32Abi?.union === true; + emitter.currentGenerator = fn.generator ?? null; + emitter.currentAsyncGenerator = fn.async === true && fn.generator !== undefined; + emitter.currentBorrowedReturn = emitter.borrowedReturns.has(fn.name); + emitter.lastUseMoves.clear(); + emitter.varRefCounts = null; + emitter.currentFunctionBody = fn.body; + emitter.inConstructor = emitter.constructorFunctions.has(fn.name); + emitter.currentCoro = null; + emitter.logArgSlots = 0; + + if (emitter.coroLowered(fn)) { + emitter.usesCoroutines = true; + emitter.declare(`declare token @llvm.coro.id(i32, ptr, ptr, ptr)`); + emitter.declare(`declare ${emitter.sizeType} @llvm.coro.size.${emitter.sizeType}()`); + emitter.declare(`declare ptr @llvm.coro.begin(token, ptr)`); + emitter.declare(`declare token @llvm.coro.save(ptr)`); + emitter.declare(`declare i8 @llvm.coro.suspend(token, i1)`); + emitter.declare(`declare ptr @llvm.coro.free(token, ptr)`); + emitter.declare(`declare void @llvm.coro.end(ptr, i1, token)`); + // Native frames come from the runtime's small-block allocator. + const frameAlloc = emitter.wasi ? "malloc" : "scr_rt_calloc"; + emitter.declare(`declare ptr @${frameAlloc}(${emitter.sizeType})`); + emitter.declare(`declare void @${emitter.wasi ? "free" : "scr_rt_free"}(ptr)`); + const started = emitter.wasi ? "scr_wasi_coro_started" : "scr_coro_started"; + emitter.declare(`declare void @${started}(ptr)`); + emitter.declare(`declare ptr @scr_fiber_self()`); + const id = B.tmp(); + const size = B.tmp(); + const mem = B.tmp(); + const handle = B.tmp(); + const self = B.tmp(); + B.line(`${id} = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)`); + B.line(`${size} = call ${emitter.sizeType} @llvm.coro.size.${emitter.sizeType}()`); + B.line(`${mem} = call ptr @${frameAlloc}(${emitter.sizeType} ${size})`); + B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`); + B.line(`call void @${started}(ptr ${handle})`); + B.line(`${self} = call ptr @scr_fiber_self()`); + // A native synchronous body owns its generator for the whole run (the + // ScrGen owns the frame), so its OUT slot address is fixed. + let out: string | null = null; + const yieldKind = fn.generator?.yieldT.kind; + if ( + !emitter.wasi && + fn.async !== true && + (yieldKind === "f64" || yieldKind === "date" || yieldKind === "bool") + ) { + emitter.declare(`declare ptr @scr_gen_of_fiber(ptr)`); + const gen = B.tmp(); + B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${self})`); + out = emitter.genOutSlot(gen); + } + emitter.currentCoro = { + kind: fn.generator !== undefined ? "generator" : "async", + id, + handle, + self, + finalLabel: B.newLabel("coro.final"), + cleanupLabel: B.newLabel("coro.cleanup"), + suspendLabel: B.newLabel("coro.suspend"), + out, + }; + } + + const paramIds = new Set(fn.params.map((p) => p.localId)); + if (emitter.debug === null) { + for (const id of findIntegerViews(numericFn)) { + // An i32 slot is its own view. + if (emitter.int32Locals.has(id)) continue; + const slot = B.slot(); + B.entryAllocas.push( + `${slot} = alloca i32 ; integer view ${emitter.numericLocals.get(id)!.name}`, + ); + emitter.integerViews.set(id, slot); + } + } + for (const global of initializerBindings) { + if (!emitter.integerViews.has(global.id)) continue; + const value = B.tmp(); + B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`); + emitter.storeIntegerView(global.id, { name: value, type: global.type }); + } + // A number global only its guarded, synchronous initializer reads or + // writes (findInitializerBindings) is invisible to every other function, + // and reentry returns before reaching it, so a local slot can stand in + // for it: LLVM keeps it in a register across calls and loops that would + // otherwise force a store and reload per iteration. The global's + // storage is never read again, so nothing writes the slot back. Bindings + // proven int32 already live in an i32 local (localizedGlobals above); + // this covers the remaining doubles. + emitter.privateGlobalSlots.clear(); + if (emitter.debug === null && emitter.currentCoro === null && !fn.async && !fn.generator) { + const conditional = initializerLocals.length ? conditionalLoopWrites(fn.body) : null; + for (const global of initializerLocals) { + if (localizedGlobals.has(global.id)) continue; + // A conditional update in a loop stays a memory update (see + // conditionalLoopWrites). + if (conditional?.has(global.id)) continue; + const slot = B.slot(); + const value = B.tmp(); + B.entryAllocas.push(`${slot} = alloca double ; module binding ${global.name}`); + B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`); + B.line(`store double ${value}, ptr ${slot}`); + emitter.privateGlobalSlots.set(global.id, slot); + } + } + for (const local of emitter.currentLocals.values()) { + // Boxed locals' slots hold their capture BOX (a ptr); captured + // (env-borrowed) locals bind the incoming box below. A caught-typed + // local is a catch binding: its slot holds the ScrCaught snapshot + // box the catch prologue takes (scr_exc_take). + const slotTy = + local.boxed || emitter.captureIds.has(local.id) || local.type.kind === "caught" + ? "ptr" + : emitter.int32Locals.has(local.id) + ? "i32" + : emitter.llType(local.type); + B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`); + emitter.emitDebugLocal( + local, + `%${mangleLocal(local.id)}`, + fn.params.findIndex((p) => p.localId === local.id) + 1, + emitter.captureIds.has(local.id), + ); + // A localized initializer binding starts at its global's zero value. + if (localizedGlobals.has(local.id)) B.line(`store i32 0, ptr %${mangleLocal(local.id)}`); + // Refcounted/boxed locals start NULL (the C prologue's `= NULL`): + // scope-exit releases run whether or not an assign ever did. + if (paramIds.has(local.id) || emitter.captureIds.has(local.id)) continue; + if (local.boxed || isRefCounted(local.type)) { + B.line(`store ptr null, ptr %${mangleLocal(local.id)}`); + } + } + // A body holding thousands of capture boxes compiles unoptimized, its + // box pointers kept in their slots (see boxSlotCount). + const unoptimized = emitter.currentCoro === null && boxSlotCount(fn) >= UNOPTIMIZED_BOX_SLOTS; + if (unoptimized) { + const slots = new Set(); + for (const local of emitter.currentLocals.values()) + if (local.boxed || emitter.captureIds.has(local.id)) slots.add(`%${mangleLocal(local.id)}`); + B.volatileSlots = slots; + // Without optimization a versioned copy only adds code. + emitter.bytesLoopVersionsEnabled = false; + } + // Captured bindings come in through the environment — borrowed for the + // whole call (the closure owns them): bound here, never released here. + (fn.captures ?? []).forEach((c, i) => { + const caps = B.tmp(); + const p = B.tmp(); + const box = B.tmp(); + B.line(`${caps} = getelementptr inbounds %ScrClosure, ptr %sc_env, i64 1 ; caps`); + B.line(`${p} = getelementptr inbounds ptr, ptr ${caps}, ${emitter.sizeType} ${i} ; caps[${i}]`); + B.line(`${box} = load ptr, ptr ${p}`); + B.line(`store ptr ${box}, ptr %${mangleLocal(c.localId)} ; captured ${c.name}`); + }); + (fn.classCaptures ?? []).forEach((c) => { + const self = fn.params[0]!; + if (self.type.kind !== "object") + throw new InternalCompilerError("class captures require an instance receiver"); + const classSlot = B.tmp(); + const classValue = B.tmp(); + const caps = B.tmp(); + const slot = B.tmp(); + const box = B.tmp(); + B.line( + `${classSlot} = getelementptr inbounds %${mangleClassStruct(self.type.className)}, ptr %p_${mangleLocal(self.localId)}, i64 0, i32 ${classEnvironmentIndex(emitter.classMeta.get(self.type.className)!)}`, + ); + B.line(`${classValue} = load ptr, ptr ${classSlot}`); + B.line(`${caps} = getelementptr inbounds %ScrClassObj, ptr ${classValue}, i64 1`); + B.line(`${slot} = getelementptr inbounds ptr, ptr ${caps}, ${emitter.sizeType} ${c.slot}`); + B.line(`${box} = load ptr, ptr ${slot}`); + B.line(`store ptr ${box}, ptr %${mangleLocal(c.localId)} ; class capture ${c.name}`); + }); + // Params spill into their slots; the function scope owns refcounted + // params (callees own their params — callers passed +1). Boxed params + // allocate the shared binding and move the raw value in. + const fnScope: LlScopeEntry[] = []; + for (const id of emitter.lazyCaptureBoxes.keys()) { + const cache = B.slot(); + B.entryAllocas.push(`${cache} = alloca ptr ; lazy box ${emitter.currentLocals.get(id)!.name}`); + B.line(`store ptr null, ptr ${cache}`); + fnScope.push({ + slot: cache, + type: emitter.currentLocals.get(id)!.type, + boxed: true, + lazy: true, + }); + emitter.lazyCaptureBoxes.set(id, cache); + // Environments that finish during the call can share an immortal + // frame box. Its payload owner is released with the function scope. + const stack = emitter.stackCaptures.emit(emitter, id, emitter.currentLocals.get(id)!.type); + if (stack) { + emitter.lazyStackBoxes.set(id, stack.box); + if (stack.owner) fnScope.push(stack.owner); + } + } + const borrowed = emitter.callLifetimes.borrowed.get(fn.name); + for (const [index, p] of fn.params.entries()) { + const local = emitter.currentLocals.get(p.localId)!; + const slot = `%${mangleLocal(p.localId)}`; + if (local.boxed) { + const box = B.tmp(); + B.line(`${box} = ${boxNewCall(emitter.shapeHost, p.type)} ; ${p.name} (boxed param)`); + emitter.boxSet(box, p.type, `%p_${mangleLocal(p.localId)}`); + B.line(`store ptr ${box}, ptr ${slot}`); + fnScope.push({ slot, type: p.type, boxed: true }); + continue; + } + const incoming: LlValue = { name: `%p_${mangleLocal(p.localId)}`, type: p.type }; + if (int32Abi?.params[index]) { + const bits = `%p_${mangleLocal(p.localId)}${INT32_ABI_SUFFIX}`; + incoming.uint32 = bits; + incoming.integer = { name: bits, type: "i32", signed: true, range: INT32_RANGE }; + } + if (emitter.storeInt32Local(p.localId, incoming)) continue; + B.line(`store ${emitter.llType(p.type)} %p_${mangleLocal(p.localId)}, ptr ${slot}`); + emitter.storeIntegerView(p.localId, incoming); + if (isRefCounted(p.type) && !borrowed?.has(index)) { + fnScope.push({ slot, type: p.type }); + emitter.noteParameterMove(fn, p.localId, slot); + } + if (emitter.reboundByFunction.get(fn.name)?.has(p.localId) && borrowed?.has(index)) { + // The caller's borrow stays in the parameter slot; values the body + // assigns are owned here until rebinding or exit releases them. + const owner = `${slot}.owner`; + B.entryAllocas.push(`${owner} = alloca ptr`); + B.line(`store ptr null, ptr ${owner}`); + fnScope.push({ slot: owner, type: p.type }); + emitter.reboundOwnerSlots.set(p.localId, owner); + } + } + emitter.scopes.push(fnScope); + // 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 ( + emitter.stackTraces && + !emitter.wasi && + fn.sourceName !== undefined && + !emitter.unframedFunctions.has(fn.name) + ) { + B.entryAllocas.push(`%source_frame = alloca { ptr, ptr }`); + const frame = emitter.cstr(` at ${fn.sourceName} (${fn.loc.file})`); + if ( + B.excCellInvariant && + emitter.mod.lib === undefined && + emitter.mod.workers !== true && + emitter.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. + emitter.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 { + emitter.declare(`declare void @scr_stack_enter(ptr, ptr)`); + emitter.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 (emitter.stackChecks?.has(fn.name)) emitter.emitStackCheck(); + if (emitter.workerEntryPolls?.has(fn.name)) emitter.emitPendingCheck(undefined, true); + emitter.emitStmts(fn.body, true); + // Implicit exit of a void function: release the function scope unless + // the body already terminated its final block (return, or a throw + // whose unwind released everything down to depth 0). + if (fn.returnType.kind === "void" && !B.isTerminated()) { + emitter.releaseScope(emitter.scopes[0]!); + // Completing by falling off the end has nothing to await. + if (emitter.currentAsyncGenerator) emitter.emitAsyncGenSettled(); + if (emitter.currentCoro !== null) { + emitter.emitWasiFulfill(null); + B.terminate(`br label %${emitter.currentCoro.finalLabel}`); + } else { + B.terminate("ret void"); + } + } + emitter.scopes.pop(); + + const coro = emitter.currentCoro; + if (coro !== null) { + B.startBlock(coro.finalLabel); + // Every exit of a coroutine body joins here: its source frame (part + // of the coroutine frame) leaves the generator's own chain. + const epilogue = B.returnEpilogue; + B.returnEpilogue = null; + if (epilogue !== null) B.line(epilogue); + if (fn.captures !== undefined) { + emitter.declare(`declare void @scr_closure_release(ptr)`); + B.line(`call void @scr_closure_release(ptr %sc_env)`); + } + if (coro.kind === "generator") { + const finish = emitter.wasi ? "scr_wasi_gen_finish" : "scr_gen_coro_finish"; + emitter.declare(`declare void @${finish}(ptr)`); + B.line(`call void @${finish}(ptr ${coro.self})`); + } else { + const finish = emitter.wasi ? "scr_wasi_async_finish" : "scr_coro_async_finish"; + emitter.declare(`declare void @${finish}(ptr)`); + B.line(`call void @${finish}(ptr ${coro.self})`); + } + const finalState = B.tmp(); + B.line(`${finalState} = call i8 @llvm.coro.suspend(token none, i1 true)`); + B.terminate( + `switch i8 ${finalState}, label %${coro.suspendLabel} [ i8 1, label %${coro.cleanupLabel} ]`, + ); + B.startBlock(coro.cleanupLabel); + const frame = B.tmp(); + B.line(`${frame} = call ptr @llvm.coro.free(token ${coro.id}, ptr ${coro.handle})`); + B.line(`call void @${emitter.wasi ? "free" : "scr_rt_free"}(ptr ${frame})`); + B.br(coro.suspendLabel); + B.startBlock(coro.suspendLabel); + B.line(`call void @llvm.coro.end(ptr ${coro.handle}, i1 false, token none)`); + const ret = emitter.llType(fn.returnType); + if (ret === "void") B.terminate(`ret void`); + else if (ret === "double") B.terminate(`ret double ${f64Lit(0)}`); + else if (ret === "i1") B.terminate(`ret i1 false`); + else B.terminate(`ret ptr null`); + } + + // Shared blocks have multiple source locations. Keep the location on + // each incoming exception check instead of assigning one to the cleanup. + B.debugLocation = null; + for (const cleanup of emitter.unwindCleanups.values()) { + B.startBlock(cleanup.label); + emitter.releaseScope(cleanup.entries); + B.terminate(cleanup.terminator); + } + + if (emitter.logArgSlots > 0) { + B.entryAllocas.push(`%logargs = alloca [${emitter.logArgSlots} x %ScrLogArg]`); + } + const params = fn.params.map((p, index) => + int32Abi?.params[index] + ? `i32 %p_${mangleLocal(p.localId)}${INT32_ABI_SUFFIX}` + : `${emitter.llType(p.type)} %p_${mangleLocal(p.localId)}`, + ); + // The i32 entry widens its i32 parameters once; the proof makes the + // double exact, so readers of the incoming number see the same value. + if (int32Abi) + fn.params.forEach((p, index) => { + if (!int32Abi.params[index]) return; + const name = `%p_${mangleLocal(p.localId)}`; + B.entryAllocas.push(`${name} = sitofp i32 ${name}${INT32_ABI_SUFFIX} to double`); + }); + // Lifted functions receive their closure first (the callValue ABI). + if (fn.captures !== undefined) params.unshift("ptr %sc_env"); + const ret = int32Abi?.result + ? "i32" + : int32Abi?.union + ? SCALAR_UNION_RESULT + : emitter.llType(fn.returnType); + let attrs = coro !== null ? "#1" : FN_ATTRS; + // The shared indexed-read helper (lowerSafeIndexRead) is a dense bounds + // test, a state test and a load behind cold runtime fallbacks, called + // from every `a[i]` of one element type. Without the hint LLVM keeps the + // hot instances out of line once they have many callers: every read pays + // a call, and an integer index converts to a double and back across it. + if (fn.name.startsWith("%arr.idxOr.")) attrs = `inlinehint ${attrs}`; + // Interface dispatch is a few key tests and direct calls ahead of the + // checked dynamic call. Keeping that fallback out of line leaves the + // tests small enough to inline at each call site. + if (fn.name.startsWith("%dyn.class.protocol.fallback.")) attrs = `noinline ${attrs}`; + else if (fn.name.startsWith("%dyn.class.protocol.")) attrs = `inlinehint ${attrs}`; + if (fn.name.startsWith(RESULT_ENTRY_PREFIX)) attrs = `inlinehint ${attrs}`; + if (unoptimized && attrs === FN_ATTRS) attrs = `noinline optnone ${attrs}`; + const debug = emitter.debugScope === null ? "" : ` !dbg ${emitter.debugScope}`; + const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name); + emitter.privateGlobalSlots.clear(); + let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`; + if (int32Abi) body += "\n" + emitter.emitInt32AbiWrapper(fn, symbol, int32Abi); + if (emitter.integerClones.members.has(fn.name)) { + body += "\n" + emitIntegerClone(fn, emitter, emitter.stackChecks?.has(fn.name) === true); + if (emitter.integerClones.dispatched.has(fn.name)) + body += + "\n" + + emitIntegerDispatcher( + fn, + emitter, + `${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}`, + int32Abi?.params ?? [], + int32Abi?.result === true, + ); + } + if (!borrowed) return body; + const slot = emitter.implSlotBorrowed(fn.name); + const virtual = + slot.size > 0 && slot.size !== borrowed.size + ? "\n" + emitter.emitOwnedCallAdapter(fn, borrowed, slot) + : ""; + return body + "\n" + emitter.emitOwnedCallAdapter(fn, borrowed) + virtual; +} + +/** Per-generator-function machinery — the async scaffolding's lazy + * sibling (async.ts's emitGenScaffolding): the same argument pack, + * a fiber trampoline whose epilogue stores the COMPLETION value (or + * consumes the GENRET sentinel, promoting the parked .return value), a + * spawn wrapper that only ALLOCATES the suspended fiber, and the + * never-started teardown that drops the packed (+1) arguments. */ +export function emitGenScaffolding(emitter: LlEmitter): string[] { + const out: string[] = []; + for (const fn of emitter.mod.functions) { + if (fn.generator === undefined) continue; + const { definitions, pack, lifted, fieldTys, ret, tr, spawnParams, argPackLines } = + emitter.emitArgPackAndTrampolinePrologue(fn); + for (const line of definitions) out.push(line); + emitter.declare(`declare zeroext i1 @scr_exc_genret_pending()`); + emitter.declare(`declare void @scr_exc_clear()`); + emitter.declare(`declare ptr @scr_gen_of_fiber(ptr)`); + emitter.declare(`declare void @scr_gen_ret_to_out(ptr)`); + emitter.declare(`declare ptr @scr_gen_new(ptr, ptr, ptr)`); + emitter.needOom(); + const coroBody = emitter.coroLowered(fn); + if (lifted && !coroBody) { + emitter.declare(`declare void @scr_closure_release(ptr)`); + } + // Normal completion stores the (typed) return value; void completes + // with the NONE slot — JS's undefined done-value. A GENRET unwind + // consumes the sentinel and promotes the parked .return value; a + // real exception stays pending (the consumer-side resume moves it). + if (coroBody) { + tr.push(` ret void`, `}`, ``); + } else { + tr.push( + ` %g = call ptr @scr_gen_of_fiber(ptr %self)`, + ...emitter.pendingTestLines("%pend"), + ` br i1 %pend, label %thrown, label %clean`, + `clean:`, + ); + switch (ret.kind) { + case "void": + tr.push(` br label %done ; void body: the done value is undefined (NONE)`); + break; + case "f64": + case "date": + emitter.declare(`declare void @scr_gen_out_f64(ptr, double)`); + tr.push(` call void @scr_gen_out_f64(ptr %g, double %r)`, ` br label %done`); + break; + case "bool": + emitter.declare(`declare void @scr_gen_out_bool(ptr, i1 zeroext)`); + tr.push(` call void @scr_gen_out_bool(ptr %g, i1 %r)`, ` br label %done`); + break; + default: { + const v = vAdapters(emitter.shapeHost, ret); + emitter.declare(`declare void @scr_gen_out_ref(ptr, ptr, ptr)`); + tr.push( + ` call void @scr_gen_out_ref(ptr %g, ptr %r, ptr ${v.release})`, + ` br label %done`, + ); + } + } + tr.push( + `thrown:`, + ` %genret = call zeroext i1 @scr_exc_genret_pending()`, + ` br i1 %genret, label %promote, label %dropdummy`, + `promote:`, + ` call void @scr_exc_clear()`, + ` call void @scr_gen_ret_to_out(ptr %g)`, + ` br label %dropdummy`, + `dropdummy:`, + ); + if (ret.kind !== "void" && isRefCounted(ret)) { + tr.push( + ` call void ${releaseSym(emitter.shapeHost, ret)}(ptr %r) ; unwound: the never-read dummy`, + ); + } + tr.push(` br label %done`, `done:`, ` ret void`, `}`, ``); + } + for (const line of tr) out.push(line); + + let settleAsync: string | null = null; + if (fn.async) { + emitter.declare(`declare ptr @scr_async_gen_new(ptr, ptr, ptr, ptr)`); + const genT: IrType & { kind: "generator" } = { + kind: "generator", + async: true, + yieldT: fn.generator.yieldT, + retT: ret, + nextT: fn.generator.nextT, + }; + const resultT = fn.generator.resultType; + const build = emitter.genResultThunkFor(genT, resultT); + settleAsync = `${build}_async`; + const settleKey = `ags:${typeKey(genT)}`; + if (!emitter.resolveThunks.has(settleKey)) { + emitter.resolveThunks.set(settleKey, settleAsync); + const adapters = vAdapters(emitter.shapeHost, resultT); + emitter.resolveThunkDefs.push( + `define internal void @${settleAsync}(ptr %g, ptr %p) ${FN_ATTRS} {`, + `entry:`, + ` %r = call ptr @${build}(ptr %g)`, + ` call void @scr_promise_fulfill_ref(ptr %p, ptr %r, ptr ${adapters.retain}, ptr ${adapters.release}, ptr ${traceArg(emitter.shapeHost, resultT)})`, + ` ret void`, + `}`, + ``, + ); + } + emitter.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`); + } + + // The never-started teardown: drop the packed (+1) arguments. + const dr: string[] = [ + `define internal void @${mangleGenDrop(fn.name)}(ptr %ap) ${FN_ATTRS} {`, + `entry:`, + ]; + fieldTys.forEach((ty, i) => { + const pType = lifted && i === 0 ? null : fn.params[lifted ? i - 1 : i]!.type; + const refcounted = pType === null || isRefCounted(pType); + if (!refcounted) return; + dr.push( + ` %dp${i} = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${i}`, + ` %dv${i} = load ptr, ptr %dp${i}`, + ); + if (pType === null) { + emitter.declare(`declare void @scr_closure_release(ptr)`); + dr.push(` call void @scr_closure_release(ptr %dv${i})`); + } else { + dr.push(` call void ${releaseSym(emitter.shapeHost, pType)}(ptr %dv${i})`); + } + }); + 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 (fn.generator.tag !== undefined) emitter.declare(`declare void @scr_gen_set_tag(ptr, ptr)`); + if (fn.generator.inheritsTag === true) + emitter.declare(`declare void @scr_gen_copy_tag(ptr, ptr)`); + if (closesSource) { + const field = lifted ? 1 : 0; + emitter.declare(`declare void @scr_gen_close(ptr)`); + emitter.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) { + emitter.declare(`declare ptr @scr_closure_retain_v(ptr)`); + } + const sp: string[] = [ + `define internal ptr @${mangleGenSpawn(fn.name)}(${spawnParams.join(", ")}) ${FN_ATTRS} { ; gen spawn ${fn.name}`, + `entry:`, + ...argPackLines, + ]; + const genNew = coroBody && !emitter.wasi ? "scr_gen_new_stackless" : "scr_gen_new"; + if (genNew !== "scr_gen_new") emitter.declare(`declare ptr @${genNew}(ptr, ptr, ptr)`); + sp.push( + settleAsync === null + ? ` %gg = call ptr @${genNew}(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)})` + : ` %gg = call ptr @scr_async_gen_new(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)}, ptr @${settleAsync})`, + ...(fn.generator.tag !== undefined + ? [` call void @scr_gen_set_tag(ptr %gg, ptr ${emitter.cstr(fn.generator.tag)})`] + : []), + ...(fn.generator.inheritsTag === true + ? [` call void @scr_gen_copy_tag(ptr %gg, ptr %a${lifted ? 1 : 0})`] + : []), + ...(closesSource + ? [ + ` call void @scr_gen_set_unstarted_return(ptr %gg, ptr @${mangleGenSourceClose(fn.name)})`, + ] + : []), + ` ret ptr %gg`, + `}`, + ``, + ); + for (const line of sp) out.push(line); + } + return out; +} + +/** Per-async-function machinery — async.ts's scaffolding, .ll + * flavored: an argument-pack struct type, a fiber trampoline (unpacks, + * frees the pack, runs the ordinary compiled body, settles the + * promise — fulfilling on clean return, leaving a pending exception + * for the runtime to reject with), and a spawn wrapper call sites and + * closures enter through (packs the args +1, scr_async_spawn runs the + * fiber eagerly to its first suspension and returns the promise). */ +export function emitAsyncScaffolding(emitter: LlEmitter): string[] { + const out: string[] = []; + if (emitter.wasi) { + emitter.declare(`declare void @llvm.coro.resume(ptr)`); + emitter.declare(`declare void @llvm.coro.destroy(ptr)`); + out.push( + `define void @scr_wasi_coro_resume(ptr %handle) ${FN_ATTRS} {`, + `entry:`, + ` call void @llvm.coro.resume(ptr %handle)`, + ` ret void`, + `}`, + `define void @scr_wasi_coro_destroy(ptr %handle) ${FN_ATTRS} {`, + `entry:`, + ` call void @llvm.coro.destroy(ptr %handle)`, + ` ret void`, + `}`, + ``, + ); + } + const fiberless = emitter.fiberlessAsyncFunctions(); + for (const fn of emitter.mod.functions) { + if (fn.async !== true || fn.generator !== undefined) continue; + if (fiberless.has(fn.name) && emitter.awaitCallees.has(fn.name)) { + for (const line of emitter.emitAsyncAwaitCall(fn, null)) out.push(line); + } + if (fiberless.has(fn.name) && emitter.adoptCallees.has(fn.name)) { + for (const line of emitter.emitAsyncAwaitCall(fn, null, true)) out.push(line); + } + if (emitter.inlineAwaitCallees.has(fn.name)) { + for (const line of emitter.emitAsyncInlineAwaitCall(fn)) out.push(line); + } + if (fiberless.has(fn.name)) { + const fieldTys = [ + ...(fn.captures !== undefined ? ["ptr"] : []), + ...fn.params.map((p) => emitter.llType(p.type)), + ]; + const params = fieldTys.map((ty, i) => `${ty} %a${i}`); + for (const line of emitter.emitFiberlessAsyncSpawn(fn, params)) out.push(line); + continue; + } + const { definitions, ret, tr, spawnParams, argPackLines } = + emitter.emitArgPackAndTrampolinePrologue(fn); + for (const line of definitions) out.push(line); + if (emitter.awaitCallees.has(fn.name)) { + for (const line of emitter.emitAsyncAwaitCall(fn, argPackLines)) out.push(line); + } + if (emitter.adoptCallees.has(fn.name)) { + for (const line of emitter.emitAsyncAwaitCall(fn, argPackLines, true)) out.push(line); + } + const stackless = emitter.stacklessAsync.has(fn.name); + const spawn = stackless ? "scr_async_spawn_stackless" : "scr_async_spawn"; + emitter.declare(`declare ptr @scr_fiber_promise(ptr)`); + emitter.declare(`declare ptr @${spawn}(ptr, ptr)`); + emitter.needOom(); + if (emitter.wasi || stackless) { + // The coroutine body settles its own promise and owns the retained + // lifted environment until final suspension. Its initial call + // returns here at the first suspend (or final suspend). + tr.push(` ret void`, `}`, ``); + } else { + if (fn.captures !== undefined) { + emitter.declare(`declare void @scr_closure_release(ptr)`); + } + tr.push( + ...emitter.pendingTestLines("%pend"), + ` br i1 %pend, label %thrown, label %clean`, + `clean:`, + ` %pr = call ptr @scr_fiber_promise(ptr %self)`, + ); + for (const line of emitter.asyncFulfillLines(ret, "%pr")) tr.push(line); + tr.push(` ret void`, `thrown:`); + if (ret.kind !== "void" && isRefCounted(ret)) { + // An escaping throw means %r is the never-read dummy (NULL). + tr.push(` call void ${releaseSym(emitter.shapeHost, ret)}(ptr %r)`); + } + tr.push(` ret void`, `}`, ``); + } + for (const line of tr) out.push(line); + + // Spawn wrapper: pack the args (+1 moves in), spawn the fiber. + const cache = fn.asyncCacheGlobal !== undefined ? mangleGlobal(fn.asyncCacheGlobal) : null; + const cycleCache = + fn.asyncCycleCacheGlobal !== undefined ? mangleGlobal(fn.asyncCycleCacheGlobal) : null; + if (cache !== null || cycleCache !== null) { + emitter.declare(`declare ptr @scr_promise_retain_v(ptr)`); + emitter.declare(`declare void @scr_promise_release(ptr)`); + } + if (cache !== null) { + emitter.declare(`declare void @scr_promise_mark_handled(ptr)`); + } + if (fn.captures !== undefined) { + emitter.declare(`declare ptr @scr_closure_retain_v(ptr)`); + } + const sp: string[] = [ + `define internal ptr @${mangleAsyncSpawn(fn.name)}(${spawnParams.join(", ")}) ${FN_ATTRS} { ; spawn ${fn.name}`, + `entry:`, + ...(cache !== null + ? [ + ` %cached = load ptr, ptr @${cache}`, + ` %cache_hit = icmp ne ptr %cached, null`, + ` br i1 %cache_hit, label %cached_return, label %cache_miss`, + `cached_return:`, + ` %cached_owned = call ptr @scr_promise_retain_v(ptr %cached)`, + ` ret ptr %cached_owned`, + `cache_miss:`, + ] + : []), + ...argPackLines, + ]; + sp.push( + ` %p = call ptr @${spawn}(ptr @${mangleTrampoline(fn.name)}, ptr %ap)`, + ...(cache !== null + ? [ + // The module loader owns this evaluation promise + // immediately. A later sibling can throw before the + // aggregate dependency wait is built, but this rejection + // must never become an unrelated unhandled rejection. + ` call void @scr_promise_mark_handled(ptr %p)`, + ] + : []), + ...(cache !== null + ? [ + ` %cache_owned = call ptr @scr_promise_retain_v(ptr %p)`, + // The eager spawn may have re-entered this guarded module + // through an admitted async cycle and installed a temporary + // cache entry. Drop that owned slot before replacing it + // with the outer evaluation promise. + ` %replaced_cache = load ptr, ptr @${cache}`, + ` call void @scr_promise_release(ptr %replaced_cache)`, + ` store ptr %cache_owned, ptr @${cache}`, + ] + : []), + ...(cycleCache !== null + ? [ + // Eager recursive spawns publish from the inside out. The + // runtime-requested outermost member writes last and is the + // SCC's actual evaluation root. + ` %cycle_cache_owned = call ptr @scr_promise_retain_v(ptr %p)`, + ` %replaced_cycle_cache = load ptr, ptr @${cycleCache}`, + ` call void @scr_promise_release(ptr %replaced_cycle_cache)`, + ` store ptr %cycle_cache_owned, ptr @${cycleCache}`, + ] + : []), + ` ret ptr %p`, + `}`, + ``, + ); + for (const line of sp) out.push(line); + } + for (const line of emitter.emitGenScaffolding()) out.push(line); + return out; +} + +/** The await-call entry of an async function (`sc_aw_*`): one direct + * `await f(...)` as a call returning the awaited value, so the call site + * needs no promise of its own. A fiberless body runs on the caller's + * stack as in its spawn wrapper; a clean result then takes the await's + * one microtask turn (scr_await_hop) and is returned without ever + * materializing the promise, which nothing else could observe. A throw + * builds the rejected promise exactly as the spawn wrapper would and + * awaits it, so the rejection reaches the awaiter on the same turn. Other + * bodies pack their arguments as the spawn wrapper does and enter + * scr_async_spawn_await_*, which parks the caller on the promise as soon + * as the body first suspends. The adopting entry (`sc_ad_*`, for + * `return f(...)` in an async function) adds the turn of the then job + * that adopts the returned promise, before the await proper. */ +export function emitAsyncAwaitCall( + emitter: LlEmitter, + fn: IrFunction, + argPackLines: string[] | null, + adopt: boolean, +): string[] { + const ret = fn.returnType; + const retTy = emitter.llType(ret); + const params = fn.params.map((p, i) => `${emitter.llType(p.type)} %a${i}`); + const awaitLine = (dest: string, promise: string): string => { + switch (ret.kind) { + case "void": + emitter.declare(`declare void @scr_await_void(ptr)`); + return ` call void @scr_await_void(ptr ${promise})`; + case "f64": + case "date": + emitter.declare(`declare double @scr_await_f64(ptr)`); + return ` ${dest} = call double @scr_await_f64(ptr ${promise})`; + case "bool": + emitter.declare(`declare zeroext i1 @scr_await_bool(ptr)`); + return ` ${dest} = call zeroext i1 @scr_await_bool(ptr ${promise})`; + case "string": + emitter.declare(`declare ptr @scr_await_str(ptr)`); + return ` ${dest} = call ptr @scr_await_str(ptr ${promise})`; + default: + emitter.declare(`declare ptr @scr_await_ref(ptr)`); + return ` ${dest} = call ptr @scr_await_ref(ptr ${promise})`; + } + }; + const retLine = (v: string): string => (retTy === "void" ? ` ret void` : ` ret ${retTy} ${v}`); + const head = adopt + ? `define internal ${retTy} @${mangleAsyncAdoptCall(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; adopt ${fn.name}` + : `define internal ${retTy} @${mangleAsyncAwaitCall(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; await ${fn.name}`; + const args = params.join(", "); + if (adopt && argPackLines !== null) { + // A body that may suspend settles later than the adoption's then job + // runs: materialize its promise, take that turn, then await it. + emitter.declare(`declare void @scr_await_hop()`); + emitter.declare(`declare void @scr_promise_release(ptr)`); + return [ + head, + `entry:`, + ` %p = call ptr @${mangleAsyncSpawn(fn.name)}(${args})`, + ` call void @scr_await_hop()`, + awaitLine("%v", "%p"), + ` call void @scr_promise_release(ptr %p)`, + retLine("%v"), + `}`, + ``, + ]; + } + if (argPackLines !== null && emitter.stacklessAsync.has(fn.name)) { + // A stackless body's eager prefix already runs on the awaiter's + // stack, which then awaits the promise like the two separate steps. + emitter.declare(`declare void @scr_promise_release(ptr)`); + return [ + head, + `entry:`, + ` %p = call ptr @${mangleAsyncSpawn(fn.name)}(${args})`, + awaitLine("%v", "%p"), + ` call void @scr_promise_release(ptr %p)`, + retLine("%v"), + `}`, + ``, + ]; + } + if (argPackLines !== null) { + const kind = + ret.kind === "void" + ? "void" + : ret.kind === "f64" || ret.kind === "date" + ? "f64" + : ret.kind === "bool" + ? "bool" + : ret.kind === "string" + ? "str" + : "ref"; + const cret = kind === "bool" ? "zeroext i1" : retTy; + emitter.declare(`declare ${cret} @scr_async_spawn_await_${kind}(ptr, ptr)`); + emitter.needOom(); + const call = `call ${cret} @scr_async_spawn_await_${kind}(ptr @${mangleTrampoline(fn.name)}, ptr %ap)`; + return [ + head, + `entry:`, + ...argPackLines, + kind === "void" ? ` ${call}` : ` %v = ${call}`, + retLine("%v"), + `}`, + ``, + ]; + } + emitter.declare(`declare void @scr_async_inline_enter(ptr)`); + emitter.declare(`declare void @scr_async_inline_exit(ptr)`); + emitter.declare(`declare void @scr_await_hop()`); + emitter.declare(`declare ptr @scr_promise_new()`); + emitter.declare(`declare void @scr_promise_reject_pending(ptr)`); + emitter.declare(`declare void @scr_promise_release(ptr)`); + const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${args})`; + const out = [ + head, + `entry:`, + ` %frame = alloca { ptr, ptr }`, + ` call void @scr_async_inline_enter(ptr %frame)`, + retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`, + ...emitter.pendingTestLines("%pend"), + ` call void @scr_async_inline_exit(ptr %frame)`, + // Adopting: the then job's turn comes before the settled promise's + // own reaction, whichever way the body completed. + ...(adopt ? [` call void @scr_await_hop()`] : []), + ` br i1 %pend, label %thrown, label %clean`, + `clean:`, + ` call void @scr_await_hop()`, + retLine("%r"), + `thrown:`, + ]; + if (ret.kind !== "void" && isRefCounted(ret)) { + // An escaping throw means %r is the never-read dummy (NULL). + out.push(` call void ${releaseSym(emitter.shapeHost, ret)}(ptr %r)`); + } + out.push( + ` %p = call ptr @scr_promise_new()`, + ` call void @scr_promise_reject_pending(ptr %p)`, + awaitLine("%v", "%p"), + ` call void @scr_promise_release(ptr %p)`, + retLine("%v"), + `}`, + ``, + ); + return out; +} + +/** Move a clean async return value into the current fiber's promise. */ +export function emitWasiFulfill(emitter: LlEmitter, v: LlValue | null): void { + const coro = emitter.currentCoro; + if (coro === null) + throw new InternalCompilerError("llvm emitter bug: wasm fulfillment outside async body"); + const ret = emitter.currentReturnType; + if (coro.kind === "generator") { + emitter.declare(`declare ptr @scr_gen_of_fiber(ptr)`); + const gen = emitter.B.tmp(); + emitter.B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${coro.self})`); + switch (ret.kind) { + case "void": + return; + case "f64": + case "date": + emitter.declare(`declare void @scr_gen_out_f64(ptr, double)`); + emitter.B.line(`call void @scr_gen_out_f64(ptr ${gen}, double ${v!.name})`); + return; + case "bool": + emitter.declare(`declare void @scr_gen_out_bool(ptr, i1 zeroext)`); + emitter.B.line(`call void @scr_gen_out_bool(ptr ${gen}, i1 ${v!.name})`); + return; + default: + emitter.declare(`declare void @scr_gen_out_ref(ptr, ptr, ptr)`); + emitter.B.line( + `call void @scr_gen_out_ref(ptr ${gen}, ptr ${v!.name}, ptr ${vAdapters(emitter.shapeHost, ret).release})`, + ); + return; + } + } + emitter.declare(`declare ptr @scr_fiber_promise(ptr)`); + const pr = emitter.B.tmp(); + emitter.B.line(`${pr} = call ptr @scr_fiber_promise(ptr ${coro.self})`); + switch (ret.kind) { + case "void": + emitter.declare(`declare void @scr_promise_fulfill_void(ptr)`); + emitter.B.line(`call void @scr_promise_fulfill_void(ptr ${pr})`); + break; + case "f64": + case "date": + emitter.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`); + emitter.B.line(`call void @scr_promise_fulfill_f64(ptr ${pr}, double ${v!.name})`); + break; + case "bool": + emitter.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`); + emitter.B.line(`call void @scr_promise_fulfill_bool(ptr ${pr}, i1 ${v!.name})`); + break; + case "string": + emitter.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`); + emitter.B.line(`call void @scr_promise_fulfill_str(ptr ${pr}, ptr ${v!.name}) ; moves in`); + break; + case "dyn": + emitter.declare(`declare void @scr_promise_resolve_dyn(ptr, ptr)`); + emitter.B.line(`call void @scr_promise_resolve_dyn(ptr ${pr}, ptr ${v!.name}) ; moves in`); + break; + default: { + const rc = vAdapters(emitter.shapeHost, ret); + emitter.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`); + emitter.B.line( + `call void @scr_promise_fulfill_ref(ptr ${pr}, ptr ${v!.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(emitter.shapeHost, ret)})`, + ); + } + } +} + +/** The argument-pack ABI and trampoline prefix shared by async functions + * and generators. Their promise/generator completion and spawn tails stay + * with the callers below. */ +export function emitArgPackAndTrampolinePrologue( + emitter: LlEmitter, + fn: IrFunction, +): LlArgPackAndTrampolinePrologue { + const pack = mangleArgPack(fn.name); + const lifted = fn.captures !== undefined; + const fieldTys = [...(lifted ? ["ptr"] : []), ...fn.params.map((p) => emitter.llType(p.type))]; + const definitions = [`%${pack} = type { ${fieldTys.join(", ") || "i8"} } ; ${fn.name} args`]; + const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${emitter.sizeType})`; + + // Async argument packs live from the spawn to the trampoline's first + // instructions, so they take the runtime's small-block allocator. + // Generator packs keep malloc: the runtime frees never-started ones. + const smallPack = fn.generator === undefined; + const packAlloc = smallPack ? "scr_rt_calloc" : "malloc"; + const packFree = smallPack ? "scr_rt_free" : "free"; + emitter.declare(`declare void @${packFree}(ptr)`); + emitter.declare(`declare ptr @${packAlloc}(${emitter.sizeType})`); + + const tr: string[] = [ + `define internal void @${mangleTrampoline(fn.name)}(ptr %self, ptr %ap) ${FN_ATTRS} {`, + `entry:`, + ]; + const loads: string[] = []; + fieldTys.forEach((ty, i) => { + tr.push( + ` %fp${i} = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${i}`, + ` %a${i} = load ${ty}, ptr %fp${i}`, + ); + loads.push(`${ty} %a${i}`); + }); + tr.push(` call void @${packFree}(ptr %ap)`); + const ret = fn.returnType; + const retTy = emitter.llType(ret); + const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${loads.join(", ")})`; + tr.push(retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`); + if (lifted && !emitter.coroLowered(fn)) { + tr.push(` call void @scr_closure_release(ptr %a0)`); + } + + const spawnParams = fieldTys.map((ty, i) => `${ty} %a${i}`); + const argPackLines = [ + ` %ap = call ptr @${packAlloc}(${emitter.sizeType} ${sizeOf})`, + ` %isnull = icmp eq ptr %ap, null`, + ` br i1 %isnull, label %oom, label %ok`, + `oom:`, + ` call void @sc_oom()`, + ` unreachable`, + `ok:`, + ]; + if (lifted) { + // scr_closure_retain is a header static inline — the `_v` twin is + // the exported symbol. + argPackLines.push(` %env = call ptr @scr_closure_retain_v(ptr %a0)`); + } + fieldTys.forEach((ty, i) => { + const src = lifted && i === 0 ? "%env" : `%a${i}`; + argPackLines.push( + ` %sp${i} = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${i}`, + ` store ${ty} ${src}, ptr %sp${i}`, + ); + }); + return { definitions, pack, lifted, fieldTys, ret, tr, spawnParams, argPackLines }; +} + +/** The module functions whose bodies may park their execution context: + * their emitted text mentions a suspension entry point (SUSPENDS), or + * they mention such a function (transitively). Null where async bodies + * must keep fibers regardless: wasm32 (its coroutine lowering owns async + * bodies), worker programs, and programs that use node:test subtests, + * whose `t.test()` awaits on the CURRENT fiber even from synchronous + * code. */ +export function parkingFunctions(emitter: LlEmitter): Set | null { + if (emitter.wasi || emitter.mod.workers === true) return null; + for (const decl of emitter.decls) if (/@scr_test_sub\b/.test(decl)) return null; + for (const text of emitter.fnDefText.values()) if (/@scr_test_sub\b/.test(text)) return null; + // Reverse mention edges, then propagate "may park" to every mentioner. + const mentionedBy = new Map>(); + const parks = new Set(); + const work: string[] = []; + for (const [name, text] of emitter.fnDefText) { + if (SUSPENDS.test(text)) { + parks.add(name); + work.push(name); + } + for (const callee of emitter.mentionedFunctions(text)) { + if (callee === name) continue; + let callers = mentionedBy.get(callee); + if (callers === undefined) mentionedBy.set(callee, (callers = new Set())); + callers.add(name); + } + } + while (work.length > 0) { + const callers = mentionedBy.get(work.pop()!); + if (callers === undefined) continue; + for (const caller of callers) { + if (parks.has(caller)) continue; + parks.add(caller); + work.push(caller); + } + } + return parks; +} + +/** Async functions whose calls can skip the fiber (scr_async_inline_*). + * A body qualifies only when nothing it runs can park its execution + * context: its emitted text, and the text of every module function it + * mentions (transitively), contains no suspension entry point. Such a + * body always completes before the call returns, which is exactly the + * behavior of a fiber that never suspends. Excluded conservatively: + * wasm32 (its coroutine lowering owns async bodies), module-evaluation + * bodies (their promise caches), + * `dyn` results (resolution may adopt a thenable), and every program + * that uses node:test subtests — `t.test()` runs a subtest inline and + * awaits it on the CURRENT fiber even from synchronous code. Worker + * programs keep fibers too: their fiber epilogue owns context + * termination (a stopping worker rejects and observes the promise), which + * the fiberless wrapper does not reproduce. A missed suspension still + * cannot reorder execution undetected: the runtime traps when a context parks while a + * fiberless body is on its stack. */ +export function fiberlessAsyncFunctions(emitter: LlEmitter): Set { + const parks = emitter.parkingFunctions(); + const eligible = parks === null ? new Set() : emitter.neverParkingAsync(parks); + for (const name of emitter.inlineAwaitCallees) + if (!eligible.has(name)) + throw new InternalCompilerError(`llvm emitter bug: inline await of suspending ${name}`); + return eligible; +} diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts new file mode 100644 index 000000000..4e4f11828 --- /dev/null +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -0,0 +1,2255 @@ +/* LlEmitter's module-level output: module parts, helper definitions, library declarations and FFI callback definitions. */ +import { emitWasmModules } from "./wasm-modules.js"; +import { type LlEmitter, ffiCallbackDummyLl, llStrBytes } from "./emitter.js"; +import { + mangleAsyncSpawn, + mangleClassObj, + mangleFnClosure, + mangleFunction, + mangleGlobal, + mangleVtInstance, +} from "../mangle.js"; +import { + type IrGlobal, + type IrType, + RUNTIME_ERROR_CLASSES, + RUNTIME_EVENT_CLASS, + RUNTIME_EVENT_TARGET_CLASS, + ffiCallbackType, + isFfiContextParam, + isRefCounted, + typeKey, +} from "../../ir/ir.js"; +import { + FN_ATTRS, + limitInlineDynReleases, + releaseSym, + typedRefConstructor, + vAdapters, +} from "./shapes.js"; +import { emitLlvmLayouts } from "./layouts.js"; +import { moduleRuntimeFeatures } from "../../ir/runtime-features.js"; +import { emitArraySearchHelpers } from "./array-search.js"; +import { emitClassMembershipHelper } from "./classes.js"; +import { type RegexMatcherCode, compileRegexMatcher } from "./regex-matchers.js"; +import { + MAP_ITER_DECL, + NUMBER_MAP_ENTRY_DECL, + STRING_MAP_ENTRY_DECL, +} from "./map-number-lookup.js"; +import { RUNTIME_ABI_MARKER } from "../runtime-abi.js"; +import { threadSlotsBlockDefinitions } from "./thread-slots.js"; +import { + ABSENT_FIELD_PAYLOAD, + exactIndexLines, + f64Lit, + ffiNativeParamLl, + ffiNativeReturnLl, + ffiNativeTypeLl, +} from "./common.js"; +import { literalCapWord, literalCapWord32 } from "./string-key-hash.js"; +import { hasLoneSurrogate, wtf8Bytes } from "../literals.js"; +import { undefinedArmTag } from "../../ir/analysis.js"; +import { NULLABLE_ABSENT, NULLABLE_NULL } from "./nullable-unions.js"; +import { literalEqualityDefinition } from "./string-literal-equality.js"; +import { STRING_READ_HELPERS, stringScanDecl } from "./string-reads.js"; +import { emitLibraryIdentityLines } from "../library-identity-markers.js"; +import { InternalCompilerError } from "../../errors.js"; + +export function emitModuleParts(emitter: LlEmitter): 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. + const fnDefs: string[] = []; + for (const fn of emitter.mod.functions) { + const def = emitter.emitFunction(fn); + fnDefs.push(def); + emitter.fnDefText.set(fn.name, def); + } + emitter.selectStacklessAsync(fnDefs); + const errorViews: string[] = []; + for (const property of ["name", "message"]) + if (emitter.mod.functions.some((fn) => fn.name === `%error.${property}.read`)) { + emitter.declare(`declare void @scr_error_install_${property}_reader(ptr)`); + errorViews.push( + ` call void @scr_error_install_${property}_reader(ptr @${mangleFunction(`%error.${property}.read`)})`, + ); + } + for (const meta of emitter.classMeta.values()) { + if (meta.def.runtime || meta.root.def.name !== "%Error") continue; + const type: IrType = { kind: "object", className: meta.def.name }; + const adapter = emitter.liveDynRefAdapter(type); + const rc = vAdapters(emitter.shapeHost, type); + const key = typeKey(type); + const name = `sc_error_view_${errorViews.length}`; + emitter.resolveThunkDefs.push( + `define internal ptr @${name}(ptr %v) ${FN_ATTRS} {`, + `entry:`, + ` %d = call ptr ${typedRefConstructor(emitter.shapeHost, type)}(ptr %v, ptr ${rc.retain}, ptr ${rc.release}, ptr ${emitter.cstr(key)}, ${emitter.sizeType} ${Buffer.byteLength(key, "utf8")}, ptr @${adapter.snapshot}, ptr ${adapter.commit})`, + ` ret ptr %d`, + `}`, + ``, + ); + emitter.declare(`declare void @scr_error_register_dyn(ptr, ptr)`); + errorViews.push( + ` call void @scr_error_register_dyn(ptr @${mangleVtInstance(meta.def.name)}, ptr @${name})`, + ); + } + const layouts = emitLlvmLayouts( + emitter.shapeHost, + emitter.mod, + emitter.classMeta, + emitter.classObjs, + emitter.fnByName, + (t) => emitter.llType(t), + emitter.nullableFields, + emitter.int32Slots, + ); + const shapes = layouts.records; + const classShapes = layouts.classes; + const classObjDefs = layouts.classObjects; + const wrappers = emitter.emitFnValueDefs(); + const asyncDefs = emitter.emitAsyncScaffolding(); + const ffiCallbacks = emitter.emitFfiCallbackDefs(); + const hasNoInlineRecordClone = [...emitter.recordCloneShapes].some( + (shapeId) => (emitter.recordsById.get(shapeId)?.fields.length ?? 0) >= 16, + ); + + // Module globals, FIRST OCCURRENCE per id: a class-expression static + // instantiated through several mixin applications registers one global + // id several times (2041-mixin-values) — C's tentative definitions + // accept the duplicates without an error, LLVM rejects redefinition, so the + // storage AND the release emit once per id here. + const seenGlobalIds = new Set(); + const globals = (emitter.mod.globals ?? []).filter((g) => { + if (seenGlobalIds.has(g.id)) return false; + seenGlobalIds.add(g.id); + return true; + }); + // Refcounted globals release before main returns — the runtime ABI’s + // sc_release_globals, keeping the RC audit's live count exact. Built + // before the declaration table flushes (it adds the release symbols). + // Two spellings with distinct temp names: the normal exit and the + // uncaught-exception exit are separate blocks of the same function. + // Interned function-value closures are IMMORTAL (rc == SIZE_MAX), so + // an own-property table Object.defineProperties hung on one would + // outlive the RC audit — release it with the globals (the runtime ABI’s + // sc_release_globals tail). Only when the dispatch unit is even + // linked (defineProps is the only writer). + const runtimeFeatures = moduleRuntimeFeatures(emitter.mod); + const fnValueProps = [...emitter.fnValues]; + if (fnValueProps.length > 0) emitter.declare(`declare void @scr_box_release(ptr)`); + if (emitter.classObjs.size > 0) + emitter.declare(`declare void @scr_classobj_clear_properties(ptr)`); + const globalReleaseLines = (prefix: string): string[] => { + const lines: string[] = []; + globals.forEach((g, i) => { + if (!isRefCounted(g.type)) return; + lines.push( + ` %${prefix}${i} = load ptr, ptr @${mangleGlobal(g.id)}`, + ` call void ${releaseSym(emitter.shapeHost, g.type)}(ptr %${prefix}${i}) ; ${g.name}`, + ); + }); + fnValueProps.forEach((name, i) => { + // props sits at %ScrClosure field 3; release is NULL-tolerant — + // cleared so a second release path stays idempotent. + lines.push( + ` %${prefix}fp${i} = load ptr, ptr getelementptr inbounds (%ScrClosure, ptr @${mangleFnClosure(name)}, i64 0, i32 3)`, + ` call void @scr_box_release(ptr %${prefix}fp${i}) ; ${name}.props`, + ` store ptr null, ptr getelementptr inbounds (%ScrClosure, ptr @${mangleFnClosure(name)}, i64 0, i32 3)`, + ); + }); + for (const name of emitter.classObjs.keys()) + lines.push(` call void @scr_classobj_clear_properties(ptr @${mangleClassObj(name)})`); + return lines; + }; + // Exit listeners can read MODULE GLOBALS directly, so they must run + // BEFORE the global releases (the runtime ABI’s runExitListeners + // ordering — the atexit half becomes an idempotent no-op). + const usesEvents = runtimeFeatures.processEvents; + const usesChildProcess = runtimeFeatures.childProcess; + const usesFsWatch = runtimeFeatures.fsWatch; + // Stream-surface programs fill the loop's stream hook (the deferred + // next-tick emissions) and the emitter's post-registration flow kick + // before %main — scr_stream.c links only when the line is emitted + // (native/executable.ts gates on the same predicate). + const usesStream = runtimeFeatures.stream; + // Net-surface programs fill the loop's net hooks (and the netSocket + // handle-dispatch ops for the checked-dynamic boundary); http-surface + // programs additionally stamp the httpReq/httpRes ops — the C main's + // install lines, gated on the same predicates native/executable.ts links by. + const usesNet = runtimeFeatures.net; + const usesNetLookup = runtimeFeatures.netLookup; + const usesDgram = runtimeFeatures.dgram; + const usesHttp2 = runtimeFeatures.http2; + const usesHttp = runtimeFeatures.http; + // Fetch-referencing programs register native fetch before main. + // Runtime selection compiles scr_fetch.c on the same predicate. + const usesFetch = runtimeFeatures.fetch; + const snapshotsTlsCa = runtimeFeatures.tls || runtimeFeatures.tlsCa; + // The process verdict has the same precedence as the C reference + // emitter: node:test owns the final status when present; otherwise an + // embedded process.exitCode owns it; ordinary programs return zero. + const usesNodeTest = runtimeFeatures.nodeTest; + // Declared NOW — the extern block flushes before main assembles. + if (usesEvents) emitter.declare(`declare void @scr_events_install()`); + if (usesChildProcess) emitter.declare(`declare void @scr_child_dyn_install()`); + const childStreamBoxes = + usesChildProcess && + usesStream && + emitter.classMeta.has("%Readable") && + emitter.classMeta.has("%Writable") + ? [ + emitter.dyn.toDynHelper({ kind: "object", className: "%Readable" }), + emitter.dyn.toDynHelper({ kind: "object", className: "%Writable" }), + ] + : null; + // worker.stdout and worker.stderr box the stream unit's Readable the + // same way, and worker.stdin its Writable when the program has one + // (scr_worker_stdio_install, scr_worker_events.c). + const workerStdioBox = + emitter.mod.workers === true && usesStream && emitter.classMeta.has("%Readable") + ? (childStreamBoxes?.[0] ?? + emitter.dyn.toDynHelper({ kind: "object", className: "%Readable" })) + : null; + const workerStdinBox = + emitter.mod.workers === true && usesStream && emitter.classMeta.has("%Writable") + ? (childStreamBoxes?.[1] ?? + emitter.dyn.toDynHelper({ kind: "object", className: "%Writable" })) + : null; + if (childStreamBoxes || workerStdioBox || workerStdinBox) { + emitter.declare( + `@scr_stream_child_ops = external constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }`, + ); + } + if (childStreamBoxes) + emitter.declare(`declare void @scr_child_dyn_streams_install(ptr, ptr, ptr)`); + if (workerStdioBox || workerStdinBox) + emitter.declare(`declare void @scr_worker_stdio_install(ptr, ptr, ptr)`); + if (usesFsWatch) emitter.declare(`declare void @scr_watch_install()`); + if (emitter.ffiHasForeignCallback) emitter.declare(`declare void @scr_ffi_install()`); + if (usesStream) emitter.declare(`declare void @scr_stream_install()`); + if (usesNetLookup) emitter.declare(`declare void @scr_net_lookup_install()`); + if (usesNet) { + emitter.declare(`declare void @scr_net_install()`); + emitter.declare(`declare void @scr_net_dyn_install()`); + } + if (usesDgram) emitter.declare(`declare void @scr_dgram_install()`); + if (usesHttp2) emitter.declare(`declare void @scr_http2_dyn_install()`); + if (usesHttp) emitter.declare(`declare void @scr_http_dyn_install()`); + if (usesFetch) emitter.declare(`declare void @scr_fetch_install()`); + + if (usesNodeTest) { + emitter.declare(`declare i32 @scr_test_exit_code()`); + emitter.declare(`declare void @scr_test_init()`); + } + if (snapshotsTlsCa) { + emitter.declare(`declare void @scr_tls_ca_install()`); + } + // Inline exit listeners run when something they must beat exists: + // the refcounted-global releases, or the retained-FFI atexit ledger + // sweep (a listener may legitimately release or pump a registration, + // and only the inline call orders ahead of every atexit handler — + // the runtime ABI’s runExitListeners stance). Plain event programs + // with neither keep the atexit path, so their listener timing is + // unchanged. + const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0; + const inlineExitListeners = + usesEvents && (emitter.mod.workers || hasRefGlobals || emitter.ffiHasRetainedCallback); + emitter.declare(`declare i32 @scr_exit_code_hint_get()`); + if (inlineExitListeners) { + emitter.declare(`declare void @scr_run_exit_listeners(double)`); + emitter.declare(`declare i32 @scr_exit_code_hint_get()`); + } + const exitListenerLines = (prefix: string): string[] => { + if (!inlineExitListeners) return []; + return [ + ` %${prefix}h = call i32 @scr_exit_code_hint_get()`, + ` %${prefix}hd = sitofp i32 %${prefix}h to double`, + ` call void @scr_run_exit_listeners(double %${prefix}hd)`, + ]; + }; + // The entry's exits release the module globals only where the thread + // frees its heap when its script ends (scr_exit_frees: workers, and + // builds that check for leaks); the main thread of other builds leaves + // it to the operating system. + const exitReleaseLines = (prefix: string): string[] => { + const releases = globalReleaseLines(prefix); + if (releases.length === 0) return releases; + emitter.declare(`declare zeroext i1 @scr_exit_frees()`); + return [ + ` %${prefix}.frees = call zeroext i1 @scr_exit_frees()`, + ` br i1 %${prefix}.frees, label %${prefix}.release, label %${prefix}.kept`, + `${prefix}.release:`, + ...releases, + ` br label %${prefix}.kept`, + `${prefix}.kept:`, + ]; + }; + const globalReleases = exitReleaseLines("g"); + const asyncEntry = emitter.fnByName.get(emitter.mod.entry)?.async === true; + const entryMayThrow = emitter.mayThrow.has(emitter.mod.entry); + // The event loop runs when timers appeared OR any async/generator + // function exists (the C main's hasAsync || hasGenerators || + // usesTimers gate). Generator programs run + // the loop too: its exit accounting notes still-suspended generator + // fibers as abandoned, so the RC audit downgrades exactly like the + // async loop-exhaustion story. + const runsLoop = + emitter.usesTimers || + emitter.ffiHasForeignCallback || + emitter.mod.functions.some((f) => f.async === true || f.generator !== undefined); + const uncaughtReleases = entryMayThrow && !asyncEntry ? exitReleaseLines("gu") : []; + const loopReleasesU = runsLoop ? exitReleaseLines("gl") : []; + const loopReleasesR = runsLoop ? exitReleaseLines("gr") : []; + const topRejectReleases = asyncEntry ? exitReleaseLines("gt") : []; + const topPendingReleases = asyncEntry ? exitReleaseLines("gp") : []; + const loopReportedReleases = runsLoop ? exitReleaseLines("gq") : []; + // main's epilogues read the flag / the loop entry points — declared + // HERE, before the extern block flushes. They run once, so they keep + // the out-of-line scr_exc_pending call; per-call checks inline the + // test (pendingTestLines). + if (entryMayThrow || runsLoop) emitter.declare(`declare zeroext i1 @scr_exc_pending()`); + if (runsLoop) { + emitter.declare(`declare zeroext i1 @scr_loop_run(ptr)`); + if (emitter.mod.commonJsEntry === true) + emitter.declare(`declare void @scr_loop_commonjs_entry()`); + if (emitter.readsLoopMetrics()) emitter.declare(`declare void @scr_perf_loop_mark()`); + emitter.declare(`declare zeroext i1 @scr_report_unhandled_rejections()`); + emitter.declare(`declare void @scr_discard_unhandled_rejections()`); + } + if (asyncEntry) { + emitter.declare(`declare i32 @scr_promise_finish_top_level(ptr)`); + emitter.declare(`declare void @scr_promise_rethrow_top_level(ptr)`); + emitter.declare(`declare void @scr_promise_release(ptr)`); + emitter.declare(`declare void @scr_exit_code_note(i32)`); + } + const topPendingExitLines = (): string[] => { + if (!asyncEntry) return []; + const lines: string[] = []; + // finish_top_level noted 13 unless the program set process.exitCode. + // A failed node:test run outranks either before exit listeners run. + if (usesNodeTest) { + lines.push( + ` %tla_program_exit = call i32 @scr_test_exit_code()`, + ` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`, + ` %tla_hint = call i32 @scr_exit_code_hint_get()`, + ` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_hint, i32 %tla_program_exit`, + ` call void @scr_exit_code_note(i32 %tla_exit_status)`, + ); + } + for (const line of exitListenerLines("xp")) lines.push(line); + for (const line of topPendingReleases) lines.push(line); + // An exit listener may set process.exitCode again, as in Node. + lines.push(` %tla_exit_code = call i32 @scr_exit_code_hint_get()`, ` ret i32 %tla_exit_code`); + return lines; + }; + // LIBRARY mode: the runtime entry points the generated library + // symbols delegate to — declared before the extern block flushes. + if (emitter.mod.lib !== undefined) { + if (emitter.wasi) { + emitter.declare(`declare ptr @malloc(${emitter.sizeType})`); + emitter.declare(`declare void @free(ptr)`); + } + emitter.declare(`declare void @scr_library_entry(i1 zeroext, ptr)`); + emitter.declare(`declare void @scr_library_reset()`); + emitter.declare(`declare void @scr_library_check_exc()`); + emitter.declare(`declare void @scr_library_set_sink(ptr, ptr)`); + emitter.declare(`declare void @scr_library_callback_entry_guard(ptr)`); + emitter.declare(`declare void @scr_library_arena_reset()`); + emitter.declare(`declare void @scr_library_collect()`); + if ((emitter.mod.lib.callbacks?.length ?? 0) > 0) { + // Host-callback channels: the registration define's dispatch + // (strcmp over the declared names + the runtime slot store). + // The call-site fetch pair (scr_library_cb_require/_ctx) is + // declared at ffiCall emission like every body-driven runtime + // symbol. + emitter.declare(`declare void @scr_library_cb_set(${emitter.sizeType}, ptr, ptr)`); + emitter.declare(`declare i32 @strcmp(ptr, ptr)`); + } + if (emitter.mod.lib.exports.some((e) => e.params.includes("string"))) { + emitter.declare(`declare ptr @scr_library_str_in(ptr, ${emitter.sizeType})`); + } + if (emitter.mod.lib.exports.some((e) => e.params.includes("bytes"))) { + emitter.declare(`declare ptr @scr_library_bytes_in(ptr, ${emitter.sizeType}, ptr)`); + } + if (emitter.mod.lib.exports.some((e) => e.params.includes("i64"))) { + emitter.declare(`declare double @scr_library_i64_in(i64, ptr)`); + } + if (emitter.mod.lib.exports.some((e) => e.params.includes("u64"))) { + emitter.declare(`declare double @scr_library_u64_in(i64, ptr)`); + } + if (emitter.mod.lib.exports.some((e) => e.returns === "string")) { + emitter.declare(`declare void @scr_library_str_out(ptr, ptr, ptr)`); + } + if (emitter.mod.lib.exports.some((e) => e.returns === "bytes")) { + emitter.declare(`declare void @scr_library_bytes_out(ptr, ptr, ptr)`); + } + if (emitter.mod.lib.exports.some((e) => e.params.includes("cstring"))) { + emitter.declare(`declare ptr @scr_library_cstr_in(ptr, ptr)`); + } + if (emitter.mod.lib.exports.some((e) => e.returns === "cstring")) { + emitter.declare(`declare ptr @scr_library_cstr_out(ptr)`); + } + } + // Helpers assemble BEFORE the declaration table flushes (they add + // write/abort declarations). + const helpers = [ + ...emitter.helperDefs(), + ...emitArraySearchHelpers(emitter.arraySearchKinds, emitter.sizeType, (line) => + emitter.declare(line), + ), + ...emitClassMembershipHelper(emitter.classMeta, emitter.sizeType), + ...(emitter.publishWalkersCache?.defs() ?? []), + ...emitter.publishedClassNamesDefs(), + ...(emitter.regions?.moduleDefs() ?? []), + ]; + if (emitter.constantNumericTables.size > 0) + emitter.declare(`declare double @scr_arr_get_number(ptr, double)`); + // Native matchers of the regex literals inside the compiled subset. + const regexMatchers = new Map(); + for (const [key, re] of emitter.regexInstances) { + const code = compileRegexMatcher( + `${re.sym}_match`, + re.pattern, + re.flags, + emitter.sizeType, + !emitter.wasi, + ); + if (code === null) continue; + regexMatchers.set(key, code); + for (const decl of code.decls) emitter.declare(decl); + } + + const out: string[] = [ + `; Generated by scriptc (LLVM backend) from ${emitter.mod.sourceFile}. Do not edit.`, + ``, + // Type shapes shared with the runtime's C ABI. ScrStr is the header + // prefix only (the flexible-array tail is concrete per literal); + // ScrLogArg is { i32 tag; 8-byte union } — i64 at offset 8 matches + // the C layout (4 bytes padding after the tag). ScrUnion/ScrClosure + // mirror scr_runtime.h field-for-field (tag reads, slot peeks, the + // fn pointer, and the caps[] tail all address through them). + `%ScrStr = type { ${emitter.sizeType}, ${emitter.sizeType}, ${emitter.sizeType} }`, + `%ScrLogArg = type { i32, i64 }`, + `%ScrVt = type { ${emitter.sizeType}, ${emitter.sizeType}, ptr }`, + `%ScrUnion = type { ${emitter.sizeType}, i32, ptr, ptr, ptr, i64 }`, + `%ScrClosure = type { ${emitter.sizeType}, ptr, ${emitter.sizeType}, ptr, i32, i32, ptr, ptr }`, + `%ScrFfiTable = type { ptr, ${emitter.sizeType}, ${emitter.sizeType}, ptr, i8, ptr, ptr, ${emitter.sizeType}, ${emitter.sizeType}, ${emitter.sizeType}, ptr, ptr }`, + `%ScrRegex = type { ${emitter.sizeType}, ptr, ptr, ptr, double, ptr, ptr, ptr }`, + // ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem, + // cyc, spare, filled, ops, data, present, ext } (field numbers in + // SCR_ARR_FIELD), where filled bounds the hole-free prefix, ops points at the element type's constant RC descriptor + // (%ScrArrOps, SCR_ELEM_REF only) and ext at the out-of-line sparse, + // property and metadata storage (NULL until first used). Live dynamic + // stream commits swap its dense, presence and out-of-line storage while + // preserving the target object's identity. + `%ScrArr = type { ${emitter.sizeType}, ${emitter.sizeType}, ${emitter.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`, + `%ScrArrOps = type { ptr, ptr, ptr }`, + // The ScrMap prefix { rc, key_kind, val_kind, traced } — the inline + // RC fast paths test the traced byte (shapes.ts). + ...(emitter.rcHelpers === null ? [] : [`%ScrMapRc = type { ${emitter.sizeType}, i8, i8, i8 }`]), + // The complete ScrMap and its { key, val, hash } entry, read by the + // inline number-key lookup (sc_map_entry_f64) and the inline + // iteration steps (sc_map_iter_entry): field 8 is the i32 entry + // count, field 10 the i64 absent-key filter, entries is field 11, + // nbuckets and buckets fields 13 and 14, the direct index and its i32 + // length fields 15 and 16. + ...(emitter.decls.has(NUMBER_MAP_ENTRY_DECL) || + emitter.decls.has(STRING_MAP_ENTRY_DECL) || + emitter.decls.has(MAP_ITER_DECL) + ? [ + `%ScrMapIx = type { ${emitter.sizeType}, i8, i8, i8, i8, i32, ptr, ptr, i32, i32, i64, ptr, i32, i32, ptr, ptr, i32, i32 }`, + ] + : []), + // String-keyed reference reads load the value slot of the entry + // scr_map_entry_str returns (map-number-lookup.ts). + ...(emitter.decls.has(NUMBER_MAP_ENTRY_DECL) || + emitter.decls.has(STRING_MAP_ENTRY_DECL) || + emitter.decls.has(MAP_ITER_DECL) + ? [`%ScrMapEntry = type { i64, i64, i64 }`] + : []), + // The runtime error prefix { rc, vt, name, message, code, cause } and the + // class-object shape { rc, pre, post, ctor, name } — field reads on + // builtin errors and classval loads GEP through these. + `%ScrError = type { ${emitter.sizeType}, ptr, ptr, ptr, ptr, ptr, i8, i8, i8, i8, i8, ptr, ptr }`, + `%ScrClassObj = type { ${emitter.sizeType}, ${emitter.sizeType}, ${emitter.sizeType}, ptr, ptr, ${emitter.sizeType}, ${emitter.sizeType}, ptr, ptr }`, + // The runtime emitter prefix { rc, vt, reg, cls } — user subclasses + // embed it (classes.ts), and bare-emitter GEPs address through it. + `%ScrEmitter = type { ${emitter.sizeType}, ptr, ptr, ptr }`, + // The runtime stream layout { rc, vt, reg, cls, st } — one struct + // for all five stream classes; stream subclasses embed it. + `%ScrStream = type { ${emitter.sizeType}, ptr, ptr, ptr, ptr }`, + // The EventTarget and Event subclass prefixes { rc, vt, state, cls }. + ...(emitter.classMeta.has(RUNTIME_EVENT_TARGET_CLASS) + ? [`%ScrEventTargetObj = type { ${emitter.sizeType}, ptr, ptr, ptr }`] + : []), + ...(emitter.classMeta.has(RUNTIME_EVENT_CLASS) + ? [`%ScrEventObj = type { ${emitter.sizeType}, ptr, ptr, ptr }`] + : []), + // The catch-binding snapshot box { rc, kind, f64, b, payload, + // retain_fn, release_fn, trace_fn } — caughtTest kind reads and + // caughtNarrow payload extraction GEP through it (offsets match + // scr_runtime.h's natural alignment: kind at 8, f64 at 16, b at 24, + // payload at 32). + `%ScrCaught = type { ${emitter.sizeType}, i32, double, i8, ptr, ptr, ptr, ptr }`, + // ScrBytes { rc, len, elem(i32+pad), data, backing, brands, shared }. Indexed + // typed-array access GEPs through this directly: the IR type already + // fixes elem, so the hot path needs neither a runtime kind load nor + // the generic scr_bytes_get/set call. + `%ScrBytes = type { ${emitter.sizeType}, ${emitter.sizeType}, i32, ptr, ptr, i8, i8, i8, i8, ptr }`, + // The capture box { rc, kind, obj_retain, obj_release, obj_trace, + // slot } — TDZ reads peek the payload slot (offset 40) directly. + `%ScrBox = type { ${emitter.sizeType}, i32, ptr, ptr, ptr, i64 }`, + // The stack buffer of the emitted JSON serializers { data, len, cap }. + `%ScrJsonBuf = type { ptr, ${emitter.sizeType}, ${emitter.sizeType}, ptr, ${emitter.sizeType}, ${emitter.sizeType} }`, + // The dynCheck error-path spine { parent, key, index } — the emitted + // builders stack-allocate one per recursion level (dyn.ts). + `%ScrDynPath = type { ptr, ptr, ${emitter.sizeType} }`, + `%ScrIslandModule = type { ptr, ptr, ${emitter.sizeType}, ${emitter.sizeType}, i32, ptr, ${emitter.sizeType}, ${emitter.sizeType} }`, + `%ScrIslandEdge = type { ptr, ptr, ptr, i32 }`, + // A generator's prefix { rc, receiver, fiber, state, out } and its + // value slot { kind, f64, b, payload, release_fn } (scr_async.c pins + // both): number and boolean yields and drives address OUT directly. + ...(emitter.usesGenSlots + ? [ + `%ScrGenSlot = type { i32, double, i8, ptr, ptr }`, + `%ScrGenHead = type { ${emitter.sizeType}, ptr, ptr, i32, %ScrGenSlot }`, + ] + : []), + ]; + for (const line of shapes.typeDefs) out.push(line); + for (const line of classShapes.typeDefs) out.push(line); + // Thread-instanced library state (abi.instance_per_thread): the + // program TU's mutable globals — module globals, run-once guards, the + // lazily-compiled regex literal caches — and the runtime globals its + // init stamps live in thread-local storage, matching the runtime + // objects compiled with -DSCR_THREAD_INSTANCES. Immutable interned + // data (string literals, unit arms, template arrays, vtables) stays + // shared. + const tl = + emitter.mod.workers === true || emitter.mod.lib?.threadInstances === true + ? "thread_local " + : ""; + out.push( + ``, + `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, + `declare void @scr_init()`, + `declare void @scr_lib_init(i32, ptr)`, + ...(emitter.mod.workers ? [`declare void @scr_runtime_workers_v8()`] : []), + ...(emitter.stackChecks?.size ? [`declare void @scr_stack_guard_init()`] : []), + ...(emitter.runtimeAbiMarker && emitter.mod.lib === undefined + ? [`declare void @${RUNTIME_ABI_MARKER}()`] + : []), + // Overrides the runtime's weak default (scr_lib.c). + ...(emitter.detachedExit && emitter.mod.lib === undefined + ? [`@scr_detached_exit = constant i8 1`] + : []), + ); + for (const d of emitter.decls) out.push(d); + out.push(``); + const flushedDecls = new Set(emitter.decls); + { + const tl = + emitter.mod.workers === true || emitter.mod.lib?.threadInstances === true + ? "thread_local " + : ""; + out.push(...threadSlotsBlockDefinitions(emitter.threadSlotsBlocks, tl !== "")); + for (let site = 0; site < emitter.dynReadIcSites; site++) + out.push( + `@sc_dyn_read_ic_${site} = internal ${tl}global { ptr, ${emitter.sizeType}, ${emitter.sizeType}, ptr } zeroinitializer`, + ); + } + for (const table of emitter.constantNumericTables.values()) { + const n = table.values.length; + let tableTmp = 0; + const tableIndex = exactIndexLines("%i", String(n), "i64", () => `%ix${tableTmp++}`); + out.push( + `@${table.symbol} = private constant [${n} x double] [${table.values.map((v) => `double ${f64Lit(v)}`).join(", ")}]`, + `define internal double @${table.symbol}_get(ptr %a, double %i) alwaysinline #0 {`, + `entry:`, + ` %initialized = icmp ne ptr %a, null`, + ...tableIndex.lines.map((line) => ` ${line}`), + ` %safe = and i1 %initialized, ${tableIndex.ok}`, + ` br i1 %safe, label %read, label %fallback`, + `read:`, + ` %slot = getelementptr inbounds [${n} x double], ptr @${table.symbol}, i64 0, i64 ${tableIndex.wide}`, + ` %value = load double, ptr %slot`, + ` ret double %value`, + `fallback:`, + ` %generic = call double @scr_arr_get_number(ptr %a, double %i)`, + ` ret double %generic`, + `}`, + ``, + ); + } + for (const [text, lit] of emitter.literals) { + // Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } — + // the runtime ABI’s static table, retain/release skip rc == SIZE_MAX. + // 64-bit (little-endian) targets split the capacity word: the high + // half carries the precomputed Map key hash (SCR_STR_HASH_CACHE). + // Bit 31 marks all-ASCII literals (SCR_STR_ASCII_BIT) on every target. + const capWord = + emitter.sizeType === "i64" + ? literalCapWord(wtf8Bytes(text)) + : literalCapWord32(wtf8Bytes(text)); + out.push( + `@${lit.sym} = internal global { ${emitter.sizeType}, ${emitter.sizeType}, ${emitter.sizeType}, [${lit.len + 1} x i8] } ` + + `{ ${emitter.sizeType} -1, ${emitter.sizeType} ${lit.len}, ${emitter.sizeType} ${capWord}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`, + ); + } + if (emitter.literals.size > 0) out.push(``); + for (const lit of emitter.bigintLiterals.values()) { + // Immortal ScrBigInt: { rc = SIZE_MAX, len, sign, cap, limbs } with + // pointer-sized little-endian limbs (scr_bigint.c); zero keeps one + // zero limb so limb 0 is always readable. + const size = emitter.sizeType; + const limbBits = size === "i64" ? 64n : 32n; + const mask = (1n << limbBits) - 1n; + const limbs: string[] = []; + for (let m = lit.value < 0n ? -lit.value : lit.value; m > 0n; m = m >> limbBits) { + limbs.push(`${size} ${m & mask}`); + } + const len = limbs.length; + if (len === 0) limbs.push(`${size} 0`); + const sign = lit.value < 0n ? -1 : lit.value > 0n ? 1 : 0; + out.push( + `@${lit.sym} = internal global { ${size}, ${size}, i32, i32, [${limbs.length} x ${size}] } ` + + `{ ${size} -1, ${size} ${len}, i32 ${sign}, i32 ${limbs.length}, [${limbs.length} x ${size}] [${limbs.join(", ")}] }`, + ); + } + if (emitter.bigintLiterals.size > 0) out.push(``); + for (const [key, sym] of emitter.unitInstances) { + // One immortal instance per unit-armed (union, tag): tag set, payload + // slot and RC entry points zero — retain/release/collector all skip + // rc == SIZE_MAX, so these never join the RC audit or a trace walk. + const [unionId, tag] = key.split(":"); + out.push( + `@${sym} = internal global %ScrUnion { ${emitter.sizeType} -1, i32 ${tag}, ptr null, ptr null, ptr null, i64 0 } ; ${unionId} unit arm`, + ); + } + if (emitter.unitInstances.size > 0) out.push(``); + for (const [unionId, sym] of emitter.retypeGuards) { + // Thread-local like module globals: a thread's cast caches hold only + // its own boxes. Never written, the flag folds to false. + out.push(`@${sym} = internal ${tl}global i8 0 ; ${unionId} boxes in a cast cache`); + } + if (emitter.retypeGuards.size > 0) out.push(``); + for (const [unionId, sym] of emitter.absentInstances) { + // One immortal ABSENT field-slot instance per undefined-armed union: + // the undefined arm's tag with payload 1 (ordinary unit instances + // carry 0). Every tag reader sees undefined; recordHas and the + // field reads compare tag and payload, never the address, so the + // per-object copies of separately compiled units agree. + const tag = undefinedArmTag({ kind: "union", unionId }, emitter.unionsById); + out.push( + `@${sym} = internal global %ScrUnion { ${emitter.sizeType} -1, i32 ${tag}, ptr null, ptr null, ptr null, i64 ${ABSENT_FIELD_PAYLOAD} } ; ${unionId} absent field`, + ); + } + if (emitter.absentInstances.size > 0) out.push(``); + if (emitter.needsUnionNone) + out.push( + `@sc_union_none = internal constant %ScrUnion zeroinitializer ; NULL union dummy read by inline stores`, + ``, + ); + if (emitter.nullableUnions.usesNullSentinel) { + // `null` in every `C | null | undefined` union (NULL is undefined): + // one module-scope immortal object like the ABSENT sentinel. + const S = emitter.sizeType; + out.push( + `${NULLABLE_NULL} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable null arm`, + ``, + ); + } + if (emitter.needsNullableAbsent) { + // The ABSENT field-slot state of every nullable-pointer union: one + // module-scope immortal object (rc == SIZE_MAX), distinct from NULL + // (undefined) and from every instance. RC entry points and the + // collector skip it like any immortal; field reads compare its address. + const S = emitter.sizeType; + out.push( + `${NULLABLE_ABSENT} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable absent field`, + ``, + ); + } + for (const [key, re] of emitter.regexInstances) { + // One immortal ScrRegex per (pattern, flags) literal, pointing at + // the interned source/flags strings. The bc and translated-program + // slots start null (lazy compile, cached by the runtime) — a mutable + // global, not constant. The last slot is the build-time matcher. + const matcher = regexMatchers.has(key) ? `@${re.sym}_match` : "null"; + out.push( + `@${re.sym} = internal ${tl}global %ScrRegex { ${emitter.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null, double 0.0, ptr null, ptr null, ptr ${matcher} } ; ${key.replace(/\n/g, "\\n")}`, + ); + } + if (emitter.regexInstances.size > 0) out.push(``); + for (const code of regexMatchers.values()) out.push(...code.lines, ``); + for (const [, inst] of emitter.templateStringsInstances) { + // One immortal ScrArr per tagged-template site: a [N x ptr] data + // global of interned cooked-string literals, and the ScrArr header + // over it (rc == SIZE_MAX, len == cap == filled, SCR_ELEM_STR = 2, + // no REF entry points). Every dense slot is present; reads retain + // immortal strings — a no-op. + const n = inst.slots.length; + const present = n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`; + out.push( + `@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, + `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, + `@${inst.sym} = internal global %ScrArr { ${emitter.sizeType} -1, ${emitter.sizeType} ${n}, ${emitter.sizeType} ${n}, i8 2, i8 0, i16 0, i32 ${n}, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null }`, + ); + } + if (emitter.templateStringsInstances.size > 0) out.push(``); + for (const [text, c] of emitter.cstrs) { + // NUL-terminated byte-array constants for scr_jb_puts and indentation. + out.push(`@${c.sym} = internal constant [${c.len + 1} x i8] c"${llStrBytes(text)}"`); + } + if (emitter.cstrs.size > 0) out.push(``); + for (const [body, sym] of emitter.mapOpsConsts) { + out.push(`@${sym} = internal constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr } { ${body} }`); + } + if (emitter.mapOpsConsts.size > 0) out.push(``); + + for (const line of ffiCallbacks.globals) out.push(line); + if (ffiCallbacks.globals.length > 0) out.push(``); + for (const g of globals) { + const ty = emitter.llType(g.type); + const zero = ty === "double" ? f64Lit(0) : ty === "ptr" ? "null" : "false"; + const debug = emitter.debug?.global(g); + out.push( + `@${mangleGlobal(g.id)} = internal ${tl}global ${ty} ${zero}${debug ? `, !dbg ${debug}` : ""} ; ${g.name}`, + ); + } + if (globals.length > 0) out.push(``); + for (const line of helpers) out.push(line); + for (const line of ffiCallbacks.defs) out.push(line); + for (const line of shapes.defs) out.push(line); + for (const line of classShapes.defs) out.push(line); + for (const line of classObjDefs) out.push(line); + for (const line of emitter.walkers.defs) out.push(line); + for (const line of emitter.dyn.defs) out.push(line); + for (const line of wrappers) out.push(line); + for (const line of asyncDefs) out.push(line); + for (const line of emitter.resolveThunkDefs) out.push(line); + out.push(fnDefs[0] ?? ""); + for (let i = 1; i < fnDefs.length; i++) out.push("", fnDefs[i]!); + out.push(""); + // WebAssembly modules translated into this program, and the registry. + for (const line of emitWasmModules(emitter.mod, flushedDecls, emitter.wasmBoundsChecks)) + out.push(line); + + // main(): scr_init, the program-dependent error-vt interval stamps, + // scr_lib_init(argc, argv), then the entry function. An uncaught + // exception escaping top-level code prints and exits 1 (Node). + // The exception epilogue is emitted when the entry may throw. + const stamps: string[] = [...errorViews]; + for (const iv of emitter.errorIntervals) { + const fields: [number, number][] = [ + [0, iv.pre], + [1, iv.post], + ]; + for (const [field, value] of fields) { + stamps.push( + ` store ${emitter.sizeType} ${value}, ptr getelementptr inbounds ([${RUNTIME_ERROR_CLASSES.size} x %ScrVt], ptr @scr_error_vts, i64 0, i64 ${iv.kind}, i32 ${field})${field === 1 ? ` ; ${iv.lib}` : ""}`, + ); + } + } + if (emitter.emitterInterval !== null) { + // The runtime emitter vtable's interval (emitterVtStampLines): bare + // EventEmitter instances answer instanceof and dispatch dynamic + // teardown under THIS module's preorder numbering. + out.push(`@scr_emitter_vt = external ${tl}global %ScrVt`, ``); + stamps.push( + ` store ${emitter.sizeType} ${emitter.emitterInterval.pre}, ptr getelementptr inbounds (%ScrVt, ptr @scr_emitter_vt, i64 0, i32 0)`, + ` store ${emitter.sizeType} ${emitter.emitterInterval.post}, ptr getelementptr inbounds (%ScrVt, ptr @scr_emitter_vt, i64 0, i32 1) ; EventEmitter`, + ); + } + for (const iv of emitter.streamIntervals) { + // The runtime stream vtables' intervals (streamVtStampLines) — the + // emitter story: instanceof and dynamic teardown dispatch through + // them. + out.push(`@${iv.vt} = external ${tl}global %ScrVt`); + stamps.push( + ` store ${emitter.sizeType} ${iv.pre}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 0)`, + ` store ${emitter.sizeType} ${iv.post}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 1) ; ${iv.lib}`, + ); + } + if (emitter.streamIntervals.length > 0) out.push(``); + if ([...emitter.literals.keys(), ...emitter.cstrs.keys()].some(hasLoneSurrogate)) { + // The runtime skips surrogate work until a lone surrogate may exist + // (scr_wtf8_any); its own producers note one, and literals do here. + out.push(`declare void @scr_wtf8_note()`, ``); + stamps.push(` call void @scr_wtf8_note()`); + } + if (emitter.errorIntervals.length > 0 && emitter.tracedShapes.has("object:%Error")) { + // The cycle fixpoint marked the Error hierarchy (a user subclass + // holds cycle-capable fields — capability is hierarchy-uniform), so + // the runtime's own error allocations need collector headers too. + // Declared inline: the extern block already flushed (LLVM is + // order-free, the helper-defs precedent). + out.push(`declare void @scr_error_set_traced()`, ``); + stamps.push(` call void @scr_error_set_traced()`); + } + if ((entryMayThrow || runsLoop) && emitter.mod.lib === undefined) { + // Declared inline: the extern block already flushed (LLVM is + // order-free — the scr_error_set_traced precedent). Only the + // printer emits here (nothing else declares it); scr_exc_pending + // and the loop entry points rode the Set before the flush. + out.push( + `declare void @scr_exc_print_uncaught()`, + `declare zeroext i1 @scr_exc_handle_uncaught(i1 zeroext)`, + ``, + ); + } + if (emitter.mod.lib !== undefined) { + // LIBRARY mode: no @main — the profile-declared external + // symbols specified by the library IR instead. + for (const line of emitter.emitLibDefs(globals, globalReleaseLines, stamps)) out.push(line); + out.push(...emitter.inlineRcTail(flushedDecls)); + out.push(`attributes #0 = { sanitize_address }`); + if (emitter.usesCoroutines) out.push(`attributes #1 = { sanitize_address presplitcoroutine }`); + if (hasNoInlineRecordClone) out.push(`attributes #2 = { noinline sanitize_address }`); + out.push(``); + out.push(...emitter.fieldAliasMetadata()); + if (emitter.debug !== null) out.push(emitter.debug.render()); + return out; + } + out.push( + `define i32 @${emitter.mod.workers ? "sc_context_entry" : emitter.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`, + `entry:`, + ...(emitter.runtimeAbiMarker ? [` call void @${RUNTIME_ABI_MARKER}()`] : []), + ...(emitter.mod.workers ? [` call void @scr_runtime_workers_v8()`] : []), + ` call void @scr_init()`, + // Each context (the main thread, every worker) installs its stack + // guard before running compiled code that checks it. + ...(emitter.stackChecks?.size ? [` call void @scr_stack_guard_init()`] : []), + ...stamps, + // Event-surface programs (signal/exit listeners) fill the loop's + // nullable event hooks before %main — scr_events.c links only when + // this line is emitted (native/executable.ts gates on the same predicate). + ...(usesEvents ? [` call void @scr_events_install()`] : []), + ...(usesChildProcess ? [` call void @scr_child_dyn_install()`] : []), + ...(childStreamBoxes + ? [ + ` call void @scr_child_dyn_streams_install(ptr @scr_stream_child_ops, ptr @${childStreamBoxes[0]}, ptr @${childStreamBoxes[1]})`, + ] + : []), + ...(workerStdioBox || workerStdinBox + ? [ + ` call void @scr_worker_stdio_install(ptr @scr_stream_child_ops, ptr ${workerStdioBox ? `@${workerStdioBox}` : "null"}, ptr ${workerStdinBox ? `@${workerStdinBox}` : "null"})`, + ] + : []), + // fs.watch programs fill the loop's watch hooks the same way — + // scr_watch.c links only when this line is emitted. + ...(usesFsWatch ? [` call void @scr_watch_install()`] : []), + ...(emitter.ffiHasForeignCallback ? [` call void @scr_ffi_install()`] : []), + ...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []), + ...(usesFetch ? [` call void @scr_fetch_install()`] : []), + ...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []), + ...(usesNetLookup ? [` call void @scr_net_lookup_install()`] : []), + ...(usesHttp ? [` call void @scr_http_dyn_install()`] : []), + ...(usesDgram ? [` call void @scr_dgram_install()`] : []), + ...(usesHttp2 ? [` call void @scr_http2_dyn_install()`] : []), + ...(usesStream ? [` call void @scr_stream_install()`] : []), + ` call void @scr_lib_init(i32 %argc, ptr %argv)`, + // node:test takes Node's --test-update-snapshots out of argv first. + ...(usesNodeTest ? [` call void @scr_test_init()`] : []), + ...(asyncEntry + ? [` %top = call ptr @${mangleAsyncSpawn(emitter.mod.entry)}()`] + : [` call void @${mangleFunction(emitter.mod.entry)}()`]), + // Uncaught exception from top-level code: Node exits 1. + ...(entryMayThrow && !asyncEntry + ? [ + ` %exc = call zeroext i1 @scr_exc_pending()`, + ` br i1 %exc, label %uncaught, label %ok`, + `uncaught:`, + ` %handled = call zeroext i1 @scr_exc_handle_uncaught(i1 false)`, + ` br i1 %handled, label %ok, label %fatal`, + `fatal:`, + ` call void @scr_exc_print_uncaught()`, + ...exitListenerLines("xu"), + ...uncaughtReleases, + ` %failure_code_0 = call i32 @scr_exit_code_hint_get()`, + ` ret i32 %failure_code_0`, + `ok:`, + ] + : []), + // The event loop runs to exhaustion (microtasks before timers). A + // throw escaping a timer callback and unhandled promise rejections + // both exit 1, like Node — the C main's loop block exactly. + ...(runsLoop + ? [ + ...(emitter.mod.commonJsEntry === true ? [` call void @scr_loop_commonjs_entry()`] : []), + // performance.eventLoopUtilization measures from here on. + ...(emitter.readsLoopMetrics() ? [` call void @scr_perf_loop_mark()`] : []), + ` %loop_rejection = call zeroext i1 @scr_loop_run(ptr ${asyncEntry ? "%top" : "null"})`, + ` %lexc = call zeroext i1 @scr_exc_pending()`, + ` br i1 %lexc, label %luncaught, label %lok`, + `luncaught:`, + ` call void @scr_exc_print_uncaught()`, + ...(asyncEntry ? [` call void @scr_promise_release(ptr %top)`] : []), + ...exitListenerLines("xl"), + ...loopReleasesU, + ` %failure_code_1 = call i32 @scr_exit_code_hint_get()`, + ` ret i32 %failure_code_1`, + `lok:`, + ` br i1 %loop_rejection, label %lreported, label %lclean`, + `lreported:`, + ` call void @scr_discard_unhandled_rejections()`, + ...(asyncEntry ? [` call void @scr_promise_release(ptr %top)`] : []), + ...exitListenerLines("xq"), + ...loopReportedReleases, + ` %failure_code_2 = call i32 @scr_exit_code_hint_get()`, + ` ret i32 %failure_code_2`, + `lclean:`, + ...(asyncEntry + ? [ + ` %tla_status = call i32 @scr_promise_finish_top_level(ptr %top)`, + ` %tla_rejected = icmp eq i32 %tla_status, 1`, + ` br i1 %tla_rejected, label %tla_fail, label %tla_not_rejected`, + `tla_fail:`, + // The loop already delivered every earlier-checkpoint + // rejection. Drop same-checkpoint competitors before + // surfacing the fatal module verdict. + ` call void @scr_discard_unhandled_rejections()`, + ` call void @scr_promise_rethrow_top_level(ptr %top)`, + ` call void @scr_promise_release(ptr %top)`, + ` call void @scr_exc_print_uncaught()`, + ...exitListenerLines("xt"), + ...topRejectReleases, + ` %failure_code_3 = call i32 @scr_exit_code_hint_get()`, + ` ret i32 %failure_code_3`, + `tla_not_rejected:`, + ` call void @scr_promise_release(ptr %top)`, + ] + : []), + ` %rej = call zeroext i1 @scr_report_unhandled_rejections()`, + ` br i1 %rej, label %lrej, label %lrok`, + `lrej:`, + ...exitListenerLines("xr"), + ...loopReleasesR, + ` %failure_code_4 = call i32 @scr_exit_code_hint_get()`, + ` ret i32 %failure_code_4`, + `lrok:`, + ...(asyncEntry + ? [ + ` %tla_pending = icmp eq i32 %tla_status, 13`, + ` br i1 %tla_pending, label %tla_stuck, label %tla_ok`, + `tla_stuck:`, + ...topPendingExitLines(), + `tla_ok:`, + ] + : []), + ] + : []), + ...exitListenerLines("xn"), + ...globalReleases, + ...(usesNodeTest + ? [` %test_exit = call i32 @scr_test_exit_code()`, ` ret i32 %test_exit`] + : [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]), + `}`, + ``, + ...emitter.inlineRcTail(flushedDecls), + // sanitize_address is inert under the plain pipeline; the sanitized + // lane's -fsanitize=address link activates instrumentation over the + // emitted functions too (the runtime TUs get theirs from clang). + `attributes #0 = { sanitize_address }`, + ...(emitter.usesCoroutines ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), + ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []), + ``, + ); + if (emitter.mod.workers) { + out.push( + `declare void @scr_context_cleanup()`, + `declare i32 @scr_worker_argc()`, + `declare ptr @scr_worker_argv()`, + `define i32 @sc_worker_entry(i32 %root) ${FN_ATTRS} {`, + `entry:`, + ` %argc = call i32 @scr_worker_argc()`, + ` %argv = call ptr @scr_worker_argv()`, + ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, + ` ret i32 %result`, + `}`, + `define i32 @main(i32 %argc, ptr %argv) ${FN_ATTRS} {`, + `entry:`, + ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, + ` call void @scr_context_cleanup()`, + ` ret i32 %result`, + `}`, + ); + } + out.push(...emitter.fieldAliasMetadata()); + if (emitter.debug !== null) out.push(emitter.debug.render()); + return out; +} + +/** The shared abort helpers (emitted only when referenced): the OOM + * abort of untraced shape allocation and the invalid-union-tag abort — + * both print the runtime ABI’s exact message on fd 2 and abort. */ +export function helperDefs(emitter: LlEmitter): string[] { + const defs: string[] = []; + const msgHelper = (fnName: string, msgSym: string, msg: string): void => { + // The message routes through the runtime's trap funnel: executable + // builds expand to the historical bytes-on-stderr + abort; library + // builds route to the registered panic sink (scr_runtime.h). + const bytes = Buffer.byteLength(msg, "utf8"); + emitter.declare(`declare void @scr_trap(ptr)`); + defs.push( + `@${msgSym} = internal constant [${bytes + 1} x i8] c"${llStrBytes(msg)}"`, + `define internal void @${fnName}() ${FN_ATTRS} {`, + `entry:`, + ` call void @scr_trap(ptr @${msgSym})`, + ` unreachable`, + `}`, + ``, + ); + }; + if (emitter.needsOom) msgHelper("sc_oom", "sc_oom_msg", "scriptc: out of memory\n"); + if (emitter.needsBadTag) { + msgHelper("sc_bad_tag", "sc_bad_tag_msg", "scriptc: internal error: invalid union tag\n"); + } + if (emitter.needsBadKey) { + // The keyed-read miss on a result type that cannot say `undefined`: + // trap like an array OOB read instead of corrupting a typed slot + // (SEMANTICS.md; the C helper's message additionally interpolates + // the runtime key — a trap-path debugging nicety, never reachable + // by a program whose behavior matches Node). + msgHelper( + "sc_bad_key", + "sc_bad_key_msg", + "scriptc: TypeError: record has no key (typed slot — no undefined is representable)\n", + ); + } + if (emitter.needsRetainBox) { + // scr_box_retain is a static inline (increment-unless-immortal); + // emitted once with internal linkage, like the record retains. + defs.push( + `define internal ptr @sc_retain_box(ptr %b) ${FN_ATTRS} {`, + `entry:`, + ` %rc = load ${emitter.sizeType}, ptr %b`, + ` %imm = icmp eq ${emitter.sizeType} %rc, -1`, + ` br i1 %imm, label %done, label %inc`, + `inc:`, + ` %n = add ${emitter.sizeType} %rc, 1`, + ` store ${emitter.sizeType} %n, ptr %b`, + ` br label %done`, + `done:`, + ` ret ptr %b`, + `}`, + ``, + ); + } + // Number-key Map/Set lookups probe the runtime's direct index inline: + // while ndense is nonzero every live key is an integer below it, so an + // in-range integral key reads its slot (entry index + 1, 0 = absent) and + // any other key is absent. Maps without the index (ndense 0) hash the + // key in sc_map_entry_f64_hashed, which scans the entries of a small map + // or probes a compact bucket table as scr_map_find_f64 does, and calls + // the runtime lookup for a wide table. It stays out of line so that the + // direct-index path stays small at every call site. Both only read + // memory, so LLVM merges lookups of one key when nothing is stored in + // between (a has() followed by get(), for example). + if (emitter.decls.has(NUMBER_MAP_ENTRY_DECL)) { + const sz = emitter.sizeType; + let mapTmp = 0; + const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`); + defs.push( + `define internal ptr @sc_map_entry_f64(ptr %m, double %k) ${FN_ATTRS} {`, + `entry:`, + ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 16`, + ...(sz === "i64" + ? [` %nd32 = load i32, ptr %ndp`, ` %nd = zext i32 %nd32 to i64`] + : [` %nd = load i32, ptr %ndp`]), + // A non-integral key inside the table is as absent as one outside it. + ...mapIndex.lines.map((line) => ` ${line}`), + ` br i1 ${mapIndex.ok}, label %slot, label %outside`, + `slot:`, + sz === "i64" + ? ` %i = add i64 ${mapIndex.wide}, 0` + : ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, + ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 15`, + ` %dense = load ptr, ptr %dp`, + ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, + ` %s = load i32, ptr %sp`, + ` %empty = icmp eq i32 %s, 0`, + ` br i1 %empty, label %miss, label %hit`, + `hit:`, + ` %e32 = sub i32 %s, 1`, + sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, + ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, + ` %entries = load ptr, ptr %ep`, + ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, + ` ret ptr %found`, + `miss:`, + ` ret ptr null`, + `outside:`, + ` %dense.any = icmp ne ${sz} %nd, 0`, + ` br i1 %dense.any, label %miss, label %slow`, + `slow:`, + ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, + ` ret ptr %hashed`, + `}`, + ``, + // Hashing (scr_map_find_f64): the SameValueZero key word, its + // scr_key_mix hash (zero remapped to one) and the absent-key filter. + `define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, + `entry:`, + ` %nan = fcmp uno double %k, %k`, + ` %zero = fcmp oeq double %k, 0.0`, + ` %kz = select i1 %zero, double 0.0, double %k`, + ` %kzb = bitcast double %kz to i64`, + ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, + ` %x1s = lshr i64 %kb, 30`, + ` %x1 = xor i64 %kb, %x1s`, + ` %x2 = mul i64 %x1, -4658895280553007687`, + ` %x3s = lshr i64 %x2, 27`, + ` %x3 = xor i64 %x2, %x3s`, + ` %x4 = mul i64 %x3, -7723592293110705685`, + ` %x5s = lshr i64 %x4, 31`, + ` %x5 = xor i64 %x4, %x5s`, + ` %hz = icmp eq i64 %x5, 0`, + ` %h = select i1 %hz, i64 1, i64 %x5`, + ` %f1s = lshr i64 %h, 44`, + ` %f1a = and i64 %f1s, 63`, + ` %f1 = shl i64 1, %f1a`, + ` %f2s = lshr i64 %h, 50`, + ` %f2a = and i64 %f2s, 63`, + ` %f2 = shl i64 1, %f2a`, + ` %fb = or i64 %f1, %f2`, + ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, + ` %filter = load i64, ptr %fp`, + ` %fm = and i64 %filter, %fb`, + ` %fin = icmp eq i64 %fm, %fb`, + ` br i1 %fin, label %table, label %miss`, + `table:`, + ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, + ...(sz === "i64" + ? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`] + : [` %nb = load i32, ptr %nbp`]), + ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, + ` %ents = load ptr, ptr %eps`, + ` %linear = icmp eq ${sz} %nb, 0`, + ` br i1 %linear, label %scan, label %buckets`, + // Small maps keep no bucket table: scan the cached entry hashes. + `scan:`, + ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, + ...(sz === "i64" + ? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`] + : [` %ne = load i32, ptr %nep`]), + ` br label %scan.loop`, + `scan.loop:`, + ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, + ` %sdone = icmp eq ${sz} %si, %ne`, + ` br i1 %sdone, label %miss, label %scan.entry`, + `scan.entry:`, + ` %se = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %si`, + ` %seh = getelementptr inbounds %ScrMapEntry, ptr %se, i32 0, i32 2`, + ` %sh = load i64, ptr %seh`, + ` %sk = load i64, ptr %se`, + ` %shq = icmp eq i64 %sh, %h`, + ` %skq = icmp eq i64 %sk, %kb`, + ` %sq = and i1 %shq, %skq`, + ` br i1 %sq, label %scan.found, label %scan.next`, + `scan.found:`, + ` ret ptr %se`, + `scan.next:`, + ` %si.next = add ${sz} %si, 1`, + ` br label %scan.loop`, + // Compact tables tag each 24-bit entry index (+1) with the hash's + // top byte; wider tables take the runtime lookup. + `buckets:`, + ` %wide = icmp ugt ${sz} %nb, 16777216`, + ` br i1 %wide, label %runtime, label %probe`, + `probe:`, + ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, + ` %bk = load ptr, ptr %bp`, + ` %mask = sub ${sz} %nb, 1`, + ` %ht = lshr i64 %h, 32`, + ` %ht32 = trunc i64 %ht to i32`, + ` %tag = and i32 %ht32, -16777216`, + sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, + ` %i0 = and ${sz} %hi, %mask`, + ` br label %probe.loop`, + `probe.loop:`, + ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, + ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, + ` %b = load i32, ptr %bep`, + ` %bempty = icmp eq i32 %b, 0`, + ` br i1 %bempty, label %miss, label %probe.tag`, + `probe.tag:`, + ` %bt = and i32 %b, -16777216`, + ` %btq = icmp eq i32 %bt, %tag`, + ` br i1 %btq, label %probe.entry, label %probe.next`, + `probe.entry:`, + ` %bix = and i32 %b, 16777215`, + ` %bix1 = sub i32 %bix, 1`, + sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, + ` %pe = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %bi`, + ` %peh = getelementptr inbounds %ScrMapEntry, ptr %pe, i32 0, i32 2`, + ` %ph = load i64, ptr %peh`, + ` %pk = load i64, ptr %pe`, + ` %phq = icmp eq i64 %ph, %h`, + ` %pkq = icmp eq i64 %pk, %kb`, + ` %pq = and i1 %phq, %pkq`, + ` br i1 %pq, label %probe.found, label %probe.next`, + `probe.found:`, + ` ret ptr %pe`, + `probe.next:`, + ` %pi.inc = add ${sz} %pi, 1`, + ` %pi.next = and ${sz} %pi.inc, %mask`, + ` br label %probe.loop`, + `miss:`, + ` ret ptr null`, + `runtime:`, + ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k) memory(read) nounwind willreturn`, + ` ret ptr %r`, + `}`, + ``, + ); + } + // Map/Set iteration steps read the entries array inline: an index is + // live when it is below the entry count and its entry's cached hash is + // nonzero (a tombstone's is zero), as scr_map_iter_live decides. The + // entry count is read again at every step, so entries appended during + // the iteration are visited. + if (emitter.decls.has(MAP_ITER_DECL)) { + const sz = emitter.sizeType; + defs.push( + `define internal ptr @sc_map_iter_entry(ptr %m, double %i) ${FN_ATTRS} {`, + `entry:`, + ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, + ` %n = load i32, ptr %np`, + ` %nf = uitofp i32 %n to double`, + ` %lo = fcmp oge double %i, 0.0`, + ` %hi = fcmp olt double %i, %nf`, + ` %in = and i1 %lo, %hi`, + ` br i1 %in, label %load, label %none`, + `load:`, + ` %ix = fptoui double %i to ${sz}`, + ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, + ` %entries = load ptr, ptr %ep`, + ` %e = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %ix`, + ` %hp = getelementptr inbounds %ScrMapEntry, ptr %e, i32 0, i32 2`, + ` %h = load i64, ptr %hp`, + ` %live = icmp ne i64 %h, 0`, + ` br i1 %live, label %yes, label %none`, + `yes:`, + ` ret ptr %e`, + `none:`, + ` ret ptr null`, + `}`, + ``, + `define internal double @sc_map_iter_count(ptr %m) ${FN_ATTRS} {`, + `entry:`, + ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, + ` %n = load i32, ptr %np`, + ` %nf = uitofp i32 %n to double`, + ` ret double %nf`, + `}`, + ``, + ); + } + // String-key Map/Set lookups test the map's absent-key filter inline + // (scr_map.c scr_map_filter_bits): a key whose two filter bits are not + // both set is absent, so most misses on small and medium maps return + // without a call. On 64-bit targets the key's cached hash h is the high + // half of its capacity word, the runtime's widened hash is (h << 32) | h, + // and the filter reads its bits 44..55, which are the capacity word's + // own. A key without a cached hash, and every possible hit, take the + // runtime lookup. + if (emitter.decls.has(STRING_MAP_ENTRY_DECL)) { + defs.push( + `define internal ptr @sc_map_entry_str(ptr %m, ptr %k) ${FN_ATTRS} {`, + `entry:`, + ...(emitter.sizeType === "i64" + ? [ + ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, + ` %filter = load i64, ptr %fp`, + ` %capp = getelementptr inbounds %ScrStr, ptr %k, i32 0, i32 2`, + ` %cap = load i64, ptr %capp`, + ` %cached = icmp uge i64 %cap, 4294967296`, + ` br i1 %cached, label %test, label %slow`, + `test:`, + ` %s1 = lshr i64 %cap, 44`, + ` %i1 = and i64 %s1, 63`, + ` %b1 = shl i64 1, %i1`, + ` %s2 = lshr i64 %cap, 50`, + ` %i2 = and i64 %s2, 63`, + ` %b2 = shl i64 1, %i2`, + ` %bits = or i64 %b1, %b2`, + ` %have = and i64 %filter, %bits`, + ` %maybe = icmp eq i64 %have, %bits`, + ` br i1 %maybe, label %slow, label %absent`, + `absent:`, + ` ret ptr null`, + ] + : [` br label %slow`]), + `slow:`, + ` %r = call ptr @scr_map_entry_str(ptr %m, ptr %k)`, + ` ret ptr %r`, + `}`, + ``, + ); + } + // String comparisons enter through small internal wrappers that LLVM + // inlines at each site: interned literals and shared values compare by + // identity, and equality rejects different byte lengths, before any + // runtime call (scr_str_eq/scr_str_cmp keep the complete semantics). + if (emitter.decls.has(`declare zeroext i1 @scr_str_eq(ptr, ptr)`)) { + defs.push( + `define internal zeroext i1 @sc_str_eq(ptr %a, ptr %b) ${FN_ATTRS} {`, + `entry:`, + ` %same = icmp eq ptr %a, %b`, + ` br i1 %same, label %yes, label %lengths`, + `lengths:`, + ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, + ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, + ` %alen = load ${emitter.sizeType}, ptr %alen.p`, + ` %blen = load ${emitter.sizeType}, ptr %blen.p`, + ` %samelen = icmp eq ${emitter.sizeType} %alen, %blen`, + ` br i1 %samelen, label %bytes, label %no`, + `bytes:`, + ` %eq = call zeroext i1 @scr_str_eq(ptr %a, ptr %b)`, + ` ret i1 %eq`, + `yes:`, + ` ret i1 true`, + `no:`, + ` ret i1 false`, + `}`, + ``, + ); + } + for (const [text, name] of emitter.literalEqualities) { + defs.push( + ...literalEqualityDefinition( + name, + wtf8Bytes(text), + emitter.internLiteral(text), + emitter.sizeType, + FN_ATTRS, + ), + ``, + ); + } + // String length and charCodeAt read proven-ASCII strings inline: bit 31 + // of the capacity word (SCR_STR_ASCII_BIT, the low half's sign bit on + // little-endian targets) makes UTF-16 units bytes. Unproven strings and + // anything but an in-range index take the runtime's UTF-16 mapping. + // A scan's receiver reads inline too where runtime state is ordinary + // global storage (ScrStrScan; SCR_TL makes it thread-local otherwise). + const scanInline = emitter.mod.workers !== true && emitter.mod.lib?.threadInstances !== true; + let scanDeclared = false; + for (const helper of STRING_READ_HELPERS) { + if (!emitter.decls.has(helper.decl)) continue; + if (scanInline && !scanDeclared) { + defs.push(stringScanDecl(emitter.sizeType), ``); + scanDeclared = true; + } + defs.push(...helper.define(emitter.sizeType, FN_ATTRS, scanInline), ``); + } + // Ordering decides inline from the first differing bytes. The bytes + // before them are equal, so both strings are at the same position of a + // character: either both bytes continue characters with the same lead + // byte, or both start a character. Byte order is the UTF-16 order for + // two continuation bytes, and for two lead bytes when one starts a + // character below U+D000, whose UTF-16 unit is below every unit of a + // character with a lead byte of at least 0xED. Between lead bytes of at + // least 0xED, a supplementary character (0xF0..0xF4, a high surrogate + // unit) orders below U+E000..U+FFFF (0xEE, 0xEF), and a lead byte of + // 0xED (U+D000..U+D7FF or a lone surrogate) takes the runtime. + // Differing words without a byte of at least 0xED decide in byte + // order; other differing words decide by their first differing byte, + // and strings shorter than a word by their first byte. Otherwise a + // proven-ASCII string on either side orders by bytes (scr_str_cmp), and + // other pairs call scr_str_cmp_u16_bytes, which only reads the two + // strings, so LLVM can merge repeated comparisons of one pair. Callers + // only test the sign of the result. + if (emitter.decls.has(`declare i32 @scr_str_cmp_u16(ptr, ptr)`)) { + const fn = "scr_str_cmp_u16"; + emitter.declare("declare i64 @llvm.bswap.i64(i64)"); + emitter.declare("declare i64 @llvm.cttz.i64(i64, i1 immarg)"); + emitter.declare("declare i8 @llvm.umin.i8(i8, i8)"); + emitter.declare("declare i32 @scr_str_cmp(ptr, ptr)"); + emitter.declare("declare i32 @scr_str_cmp_u16_bytes(ptr, ptr)"); + defs.push( + `define internal i32 @${fn.replace(/^scr_/, "sc_")}(ptr %a, ptr %b) ${FN_ATTRS} {`, + `entry:`, + ` %same = icmp eq ptr %a, %b`, + ` br i1 %same, label %equal, label %lengths`, + `lengths:`, + ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, + ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, + ` %alen = load ${emitter.sizeType}, ptr %alen.p`, + ` %blen = load ${emitter.sizeType}, ptr %blen.p`, + ` %aempty = icmp eq ${emitter.sizeType} %alen, 0`, + ` %bempty = icmp eq ${emitter.sizeType} %blen, 0`, + ` %empty = or i1 %aempty, %bempty`, + ` br i1 %empty, label %order, label %words`, + // Both at least 8 bytes: the first word of each. + `words:`, + ` %along = icmp uge ${emitter.sizeType} %alen, 8`, + ` %blong = icmp uge ${emitter.sizeType} %blen, 8`, + ` %long = and i1 %along, %blong`, + ` br i1 %long, label %word, label %first`, + `word:`, + ` %awp = getelementptr inbounds %ScrStr, ptr %a, i32 1`, + ` %bwp = getelementptr inbounds %ScrStr, ptr %b, i32 1`, + ` %aw = load i64, ptr %awp, align 1`, + ` %bw = load i64, ptr %bwp, align 1`, + ` %wdiffer = icmp ne i64 %aw, %bw`, + ` br i1 %wdiffer, label %differ, label %wsame`, + // Equal first words and one length of at most 16 bytes: the word + // ending at that length covers the rest, and any difference lies + // past the equal first word. + `wsame:`, + ` %samelen = icmp eq ${emitter.sizeType} %alen, %blen`, + ` %short = icmp ule ${emitter.sizeType} %alen, 16`, + ` %tailable = and i1 %samelen, %short`, + ` br i1 %tailable, label %tail, label %order`, + `tail:`, + ` %toff = sub ${emitter.sizeType} %alen, 8`, + ` %atp = getelementptr inbounds i8, ptr %awp, ${emitter.sizeType} %toff`, + ` %btp = getelementptr inbounds i8, ptr %bwp, ${emitter.sizeType} %toff`, + ` %at = load i64, ptr %atp, align 1`, + ` %bt = load i64, ptr %btp, align 1`, + ` %tdiffer = icmp ne i64 %at, %bt`, + ` br i1 %tdiffer, label %differ, label %equal`, + `first:`, + ` %adata = getelementptr inbounds %ScrStr, ptr %a, i32 1`, + ` %bdata = getelementptr inbounds %ScrStr, ptr %b, i32 1`, + ` %x = load i8, ptr %adata`, + ` %y = load i8, ptr %bdata`, + ` %fdiffer = icmp ne i8 %x, %y`, + ` br i1 %fdiffer, label %bytes, label %order`, + // Two differing words. When neither has a byte of at least 0xED + // (a byte b is when its high bit and bit 7 of (b & 0x7f) + 0x13 are + // set), byte-swapped little-endian words compare in byte order. + // Otherwise the lowest set byte of their xor is the first differing + // byte. + `differ:`, + ` %dx = phi i64 [ %aw, %word ], [ %at, %tail ]`, + ` %dy = phi i64 [ %bw, %word ], [ %bt, %tail ]`, + ` %xl = and i64 %dx, 9187201950435737471`, + ` %yl = and i64 %dy, 9187201950435737471`, + ` %xa = add i64 %xl, 1374463283923456787`, + ` %ya = add i64 %yl, 1374463283923456787`, + ` %xh = and i64 %dx, %xa`, + ` %yh = and i64 %dy, %ya`, + ` %hh = or i64 %xh, %yh`, + ` %whigh = and i64 %hh, -9187201950435737472`, + ` %dlow = icmp eq i64 %whigh, 0`, + ` br i1 %dlow, label %wordorder, label %locate`, + `wordorder:`, + ` %xsw = call i64 @llvm.bswap.i64(i64 %dx)`, + ` %ysw = call i64 @llvm.bswap.i64(i64 %dy)`, + ` %wbelow = icmp ult i64 %xsw, %ysw`, + ` %wsign = select i1 %wbelow, i32 -1, i32 1`, + ` ret i32 %wsign`, + `locate:`, + ` %dxor = xor i64 %dx, %dy`, + ` %dtz = call i64 @llvm.cttz.i64(i64 %dxor, i1 true)`, + ` %dshift = and i64 %dtz, 56`, + ` %dxs = lshr i64 %dx, %dshift`, + ` %dys = lshr i64 %dy, %dshift`, + ` %dxb = trunc i64 %dxs to i8`, + ` %dyb = trunc i64 %dys to i8`, + ` br label %bytes`, + // The first differing bytes. + `bytes:`, + ` %bx = phi i8 [ %x, %first ], [ %dxb, %locate ]`, + ` %by = phi i8 [ %y, %first ], [ %dyb, %locate ]`, + ` %bmin = call i8 @llvm.umin.i8(i8 %bx, i8 %by)`, + ` %blow = icmp ult i8 %bmin, -19`, + ` br i1 %blow, label %byte, label %high`, + // Both at least 0xED. A lone surrogate (0xED) takes the runtime; + // supplementary lead bytes (0xF0..0xF4) order below 0xEE and 0xEF + // once 0x10 is subtracted from them. + `high:`, + ` %xed = icmp eq i8 %bx, -19`, + ` %yed = icmp eq i8 %by, -19`, + ` %ed = or i1 %xed, %yed`, + ` br i1 %ed, label %order, label %planes`, + `planes:`, + ` %xsupp = icmp uge i8 %bx, -16`, + ` %ysupp = icmp uge i8 %by, -16`, + ` %xlow = add i8 %bx, -16`, + ` %ylow = add i8 %by, -16`, + ` %xk = select i1 %xsupp, i8 %xlow, i8 %bx`, + ` %yk = select i1 %ysupp, i8 %ylow, i8 %by`, + ` %kbelow = icmp ult i8 %xk, %yk`, + ` %ksign = select i1 %kbelow, i32 -1, i32 1`, + ` ret i32 %ksign`, + `byte:`, + ` %below = icmp ult i8 %bx, %by`, + ` %sign = select i1 %below, i32 -1, i32 1`, + ` ret i32 %sign`, + `order:`, + ` %acw.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 2`, + ` %bcw.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 2`, + ` %acw = load i32, ptr %acw.p`, + ` %bcw = load i32, ptr %bcw.p`, + ` %cws = or i32 %acw, %bcw`, + ` %anyascii = icmp slt i32 %cws, 0`, + ` br i1 %anyascii, label %memorder, label %units`, + `memorder:`, + ` %m = call i32 @scr_str_cmp(ptr %a, ptr %b)`, + ` ret i32 %m`, + `units:`, + ` %c = call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`, + ` ret i32 %c`, + `equal:`, + ` ret i32 0`, + `}`, + ``, + ); + } + // The declarations these helpers added must land in the extern block, + // which already flushed — append here instead (LLVM is order-free). + return defs.length > 0 ? [...defs] : defs; +} + +/** LIBRARY mode: the profile-declared external definitions — the + * export-map wrappers plus init / sink-registration / reset / collect. + * Plain `define` (not `define internal`) — the exact linkage distinction + * that separates the executable lane's @main from everything else. The + * bodies delegate every runtime half to scr_library.c, mirroring the C + * emission line for line, so the two lanes are identical by + * construction. */ +export function emitLibDefs( + emitter: LlEmitter, + globals: IrGlobal[], + globalReleaseLines: (prefix: string) => string[], + stamps: string[], +): string[] { + const lib = emitter.mod.lib!; + const autoReset = lib.resultResetSymbol === null; + const out: string[] = [``, `; ── library-mode entries (profile: ${lib.profileName}) ──`, ``]; + // Every entry's prologue records its external symbol in the funnel's + // current-entry slot (structured trap-teaching field 2); the symbols + // live as internal constants, one per entry. + const symConst = (sym: string): string => `@sc_lib_sym_${sym}`; + const emitSymConst = (sym: string): void => { + out.push( + `${symConst(sym)} = internal constant [${Buffer.byteLength(sym, "utf8") + 1} x i8] c"${llStrBytes(sym)}"`, + ); + }; + emitSymConst(lib.initSymbol); + emitSymConst(lib.sinkRegisterSymbol); + if (lib.callbackRegisterSymbol !== null && lib.callbackRegisterSymbol !== undefined) + emitSymConst(lib.callbackRegisterSymbol); + if (lib.resultResetSymbol !== null) emitSymConst(lib.resultResetSymbol); + if (lib.collectSymbol !== null) emitSymConst(lib.collectSymbol); + for (const e of lib.exports) emitSymConst(e.symbol); + out.push(``); + if (emitter.wasi) { + for (const symbol of ["scriptc_alloc", "scriptc_free"]) emitSymConst(symbol); + out.push( + `define ptr @scriptc_alloc(i32 %size) ${FN_ATTRS} {`, + `entry:`, + ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst("scriptc_alloc")})`, + ` %p = call ptr @malloc(i32 %size)`, + ` ret ptr %p`, + `}`, + ``, + `define void @scriptc_free(ptr %p) ${FN_ATTRS} {`, + `entry:`, + ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst("scriptc_free")})`, + ` call void @free(ptr %p)`, + ` ret void`, + `}`, + ``, + ); + } + // The runtime detected-trap overlay table (scr_runtime.h declares it, + // the library trap funnel consults it): flat code/teaching/remediation + // triples, one per runtime trap code (SC4013–SC4019) the profile + // declares text for. The funnel assembles the structured sink message. + // The empty table still defines the symbols the funnel links against. + const ovlCells: string[] = []; + lib.trapOverlays.forEach((o, i) => { + const cell = (name: string, text: string | undefined): void => { + if (text === undefined) { + ovlCells.push("ptr null"); + return; + } + const sym = `@sc_lib_ovl_${i}_${name}`; + out.push( + `${sym} = internal constant [${Buffer.byteLength(text, "utf8") + 1} x i8] c"${llStrBytes(text)}"`, + ); + ovlCells.push(`ptr ${sym}`); + }; + cell("code", o.code); + cell("teach", o.teaching); + cell("rem", o.remediation); + }); + out.push( + ovlCells.length === 0 + ? `@scr_library_trap_overlays = constant [1 x ptr] zeroinitializer` + : `@scr_library_trap_overlays = constant [${ovlCells.length} x ptr] [${ovlCells.join(", ")}]`, + `@scr_library_trap_overlays_len = constant ${emitter.sizeType} ${lib.trapOverlays.length}`, + ``, + ); + // The init entry: full deterministic reset-and-reevaluate. Program + // globals release and zero first (run-once guards included), then the + // runtime session reset, the error-vt interval stamps verbatim from + // the executable main, %main itself, and the escaped-exception check. + const zeroStores = globals.map((g) => { + const ty = emitter.llType(g.type); + const zero = ty === "double" ? f64Lit(0) : ty === "ptr" ? "null" : "false"; + return ` store ${ty} ${zero}, ptr @${mangleGlobal(g.id)} ; ${g.name}`; + }); + out.push( + `define void @${lib.initSymbol}() ${FN_ATTRS} {`, + `entry:`, + ` call void @scr_library_entry(i1 zeroext true, ptr ${symConst(lib.initSymbol)}) ; init always resets the result arena`, + ...globalReleaseLines("ci"), + ...zeroStores, + ` call void @scr_library_reset()`, + ...stamps, + ` call void @${mangleFunction(emitter.mod.entry)}()`, + ` call void @scr_library_check_exc()`, + ` ret void`, + `}`, + ``, + `define void @${lib.sinkRegisterSymbol}(ptr %fn, ptr %ctx) ${FN_ATTRS} {`, + `entry:`, + ` call void @scr_library_callback_entry_guard(ptr ${symConst(lib.sinkRegisterSymbol)})`, + ` call void @scr_library_set_sink(ptr %fn, ptr %ctx)`, + ` ret void`, + `}`, + ``, + ); + if (lib.callbacks !== undefined && lib.callbacks.length > 0) { + // Host-callback channels: the per-channel name constants (the + // registration dispatch's strcmp operands), the per-channel + // unregistered-call trap constants (the ffiCall sites' + // scr_library_cb_require operands), and the registration function: + // a pure store dispatch + // (the sink registration's rule — no entry prologue, no poison + // guard) whose first operation rejects callback-time re-entry + // (SC4026). An unknown or NULL name is a defined -1, never a store. + for (const cb of lib.callbacks) { + out.push( + `@sc_lib_cb_name_${cb.slot} = internal constant [${Buffer.byteLength(cb.name, "utf8") + 1} x i8] c"${llStrBytes(cb.name)}"`, + `@sc_lib_cb_trap_${cb.slot} = internal constant [${Buffer.byteLength(cb.unregisteredTrap, "utf8") + 1} x i8] c"${llStrBytes(cb.unregisteredTrap)}"`, + ); + } + out.push( + ``, + `define i32 @${lib.callbackRegisterSymbol}(ptr %name, ptr %fn, ptr %ctx) ${FN_ATTRS} {`, + `entry:`, + ` call void @scr_library_callback_entry_guard(ptr ${symConst(lib.callbackRegisterSymbol!)})`, + ` %isnull = icmp eq ptr %name, null`, + ` br i1 %isnull, label %miss, label %try0`, + ); + lib.callbacks.forEach((cb, i) => { + const next = i + 1 < lib.callbacks!.length ? `try${i + 1}` : "miss"; + out.push( + `try${i}: ; channel '${cb.name}'`, + ` %cmp${i} = call i32 @strcmp(ptr %name, ptr @sc_lib_cb_name_${cb.slot})`, + ` %eq${i} = icmp eq i32 %cmp${i}, 0`, + ` br i1 %eq${i}, label %set${i}, label %${next}`, + `set${i}:`, + ` call void @scr_library_cb_set(${emitter.sizeType} ${cb.slot}, ptr %fn, ptr %ctx)`, + ` ret i32 0`, + ); + }); + out.push(`miss:`, ` ret i32 -1`, `}`, ``); + } + if (lib.identity !== undefined && emitter.emitLibraryIdentity) { + // Profile-declared identity getters (the ask-2 sidecar's boot-time + // pairing fence): pure data returns with NO entry prologue — exempt + // from the poisoned guard and every runtime touch (ratified), so a + // host can read them before init and after a trap. The u64 rides + // i64 two's-complement (LLVM integer constants are signed). + for (const line of emitLibraryIdentityLines(lib.identity, FN_ATTRS)) out.push(line); + } + if (lib.resultResetSymbol !== null) { + out.push( + `define void @${lib.resultResetSymbol}() ${FN_ATTRS} {`, + `entry:`, + ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst(lib.resultResetSymbol)})`, + ` call void @scr_library_arena_reset()`, + ` ret void`, + `}`, + ``, + ); + } + if (lib.collectSymbol !== null) { + out.push( + `define void @${lib.collectSymbol}() ${FN_ATTRS} {`, + `entry:`, + ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst(lib.collectSymbol)})`, + ` call void @scr_library_collect() ; arena reset + a full cycle collection`, + ` ret void`, + `}`, + ``, + ); + } + for (const e of lib.exports) { + const params: string[] = []; + const body: string[] = [ + ` call void @scr_library_entry(i1 zeroext ${autoReset ? "true" : "false"}, ptr ${symConst(e.symbol)})`, + ]; + const args: string[] = []; + if (e.inboundBytesTrap !== undefined) { + // The bytes-in helper's trap message: the compiler-assembled + // structured trap-teaching form (0x01 text 0x1F SC4012 0x1F symbol + // [0x1F remediation]) consumed by the runtime sink. + const trapBytes = Buffer.byteLength(e.inboundBytesTrap, "utf8"); + out.push( + `@sc_lib_bytes_trap_${e.symbol} = internal constant [${trapBytes + 1} x i8] c"${llStrBytes(e.inboundBytesTrap)}"`, + ``, + ); + } + if (e.inboundIntTrap !== undefined) { + // The i64/u64-in helpers' host-contract trap message (ask 4): an + // inbound integer past ±(2^53−1) cannot ride f64 exactly. Same + // assembled structured form, same SC4012 code, same + // emission-invariance argument as the bytes trap. + const trapBytes = Buffer.byteLength(e.inboundIntTrap, "utf8"); + out.push( + `@sc_lib_int_trap_${e.symbol} = internal constant [${trapBytes + 1} x i8] c"${llStrBytes(e.inboundIntTrap)}"`, + ``, + ); + } + if (e.inboundCstringTrap !== undefined) { + // The cstring-in helper's host-contract trap message (a NULL + // `const char *`): same assembled structured form and SC4012 code. + const trapBytes = Buffer.byteLength(e.inboundCstringTrap, "utf8"); + out.push( + `@sc_lib_cstr_trap_${e.symbol} = internal constant [${trapBytes + 1} x i8] c"${llStrBytes(e.inboundCstringTrap)}"`, + ``, + ); + } + e.params.forEach((cls, i) => { + switch (cls) { + case "f64": + params.push(`double %a${i}`); + args.push(`double %a${i}`); + break; + case "bool": + params.push(`i8 %a${i}`); + body.push(` %c${i} = icmp ne i8 %a${i}, 0`); + args.push(`i1 %c${i}`); + break; + case "u8": + params.push(`i8 %a${i}`); + body.push(` %c${i} = uitofp i8 %a${i} to double`); + args.push(`double %c${i}`); + break; + case "u32": + params.push(`i32 %a${i}`); + body.push(` %c${i} = uitofp i32 %a${i} to double`); + args.push(`double %c${i}`); + break; + case "i32": + params.push(`i32 %a${i}`); + body.push(` %c${i} = sitofp i32 %a${i} to double`); + args.push(`double %c${i}`); + break; + case "i64": + // Inbound declared-integer edge (ask 4): the helper converts + // exactly or delivers the host-contract trap (past ±(2^53−1) + // the value cannot ride f64 without rounding). + params.push(`i64 %a${i}`); + body.push( + ` %c${i} = call double @scr_library_i64_in(i64 %a${i}, ptr @sc_lib_int_trap_${e.symbol})`, + ); + args.push(`double %c${i}`); + break; + case "u64": + params.push(`i64 %a${i}`); + body.push( + ` %c${i} = call double @scr_library_u64_in(i64 %a${i}, ptr @sc_lib_int_trap_${e.symbol})`, + ); + args.push(`double %c${i}`); + break; + case "string": + params.push(`ptr %a${i}_ptr`, `${emitter.sizeType} %a${i}_len`); + body.push( + ` %c${i} = call ptr @scr_library_str_in(ptr %a${i}_ptr, ${emitter.sizeType} %a${i}_len)`, + ); + args.push(`ptr %c${i}`); + break; + case "bytes": + params.push(`ptr %a${i}_ptr`, `${emitter.sizeType} %a${i}_len`); + body.push( + ` %c${i} = call ptr @scr_library_bytes_in(ptr %a${i}_ptr, ${emitter.sizeType} %a${i}_len, ptr @sc_lib_bytes_trap_${e.symbol})`, + ); + args.push(`ptr %c${i}`); + break; + case "cstring": + params.push(`ptr %a${i}`); + body.push( + ` %c${i} = call ptr @scr_library_cstr_in(ptr %a${i}, ptr @sc_lib_cstr_trap_${e.symbol})`, + ); + args.push(`ptr %c${i}`); + break; + } + }); + if (e.returns === "string" || e.returns === "bytes") { + params.push(`ptr %out`, `ptr %out_len`); + } + const target = `@${mangleFunction(e.fnName)}`; + const callArgs = args.join(", "); + let retType = "void"; + switch (e.returns) { + case "void": + body.push( + ` call void ${target}(${callArgs})`, + ` call void @scr_library_check_exc()`, + ` ret void`, + ); + break; + case "f64": + retType = "double"; + body.push( + ` %r = call double ${target}(${callArgs})`, + ` call void @scr_library_check_exc()`, + ` ret double %r`, + ); + break; + case "i64": + case "u64": + // The outbound declared-integer edge (ask 4): every value + // reaching this return was PROVEN whole and inside the class's + // range at compile time, so the fp-to-int conversion is exact + // by construction — the crossing carries the mathematically + // exact integer the f64 held. + retType = "i64"; + body.push( + ` %r = call double ${target}(${callArgs})`, + ` call void @scr_library_check_exc()`, + ` %z = ${e.returns === "i64" ? "fptosi" : "fptoui"} double %r to i64`, + ` ret i64 %z`, + ); + break; + case "bool": + retType = "i8"; + body.push( + ` %r = call i1 ${target}(${callArgs})`, + ` call void @scr_library_check_exc()`, + ` %z = zext i1 %r to i8`, + ` ret i8 %z`, + ); + break; + case "string": + body.push( + ` %r = call ptr ${target}(${callArgs})`, + ` call void @scr_library_check_exc()`, + ` call void @scr_library_str_out(ptr %r, ptr %out, ptr %out_len)`, + ` ret void`, + ); + break; + case "bytes": + body.push( + ` %r = call ptr ${target}(${callArgs})`, + ` call void @scr_library_check_exc()`, + ` call void @scr_library_bytes_out(ptr %r, ptr %out, ptr %out_len)`, + ` ret void`, + ); + break; + case "cstring": + // The result's arena copy, returned as `const char *`. + retType = "ptr"; + body.push( + ` %r = call ptr ${target}(${callArgs})`, + ` call void @scr_library_check_exc()`, + ` %z = call ptr @scr_library_cstr_out(ptr %r)`, + ` ret ptr %z`, + ); + break; + } + out.push( + `define ${retType} @${e.symbol}(${params.join(", ")}) ${FN_ATTRS} { ; library export ${e.fnName}`, + `entry:`, + ...body, + `}`, + ``, + ); + } + return out; +} + +/** C-callable scalar callback trampolines. A callback with an explicit + * context entry receives the closure at that exact ABI position. A raw + * callback loads it from a call-scoped TLS slot installed around the + * outer native call. */ +export function emitFfiCallbackDefs(emitter: LlEmitter): { globals: string[]; defs: string[] } { + const globals: string[] = []; + const defs: string[] = []; + if (emitter.ffiCallbackAdapters.size === 0) return { globals, defs }; + emitter.declare(`declare void @scr_trap(ptr)`); + const expired = emitter.cstr( + "scriptc: native callback invoked outside its call-scoped lifetime\n", + ); + const released = emitter.cstr("scriptc: native callback invoked outside its retained lifetime\n"); + for (const adapter of emitter.ffiCallbackAdapters.values()) { + const cb = adapter.callback; + if (adapter.tls !== null) + globals.push(`@${adapter.tls} = internal thread_local global ptr null`); + if (adapter.global !== null) + globals.push(`@${adapter.global} = internal thread_local global ptr null`); + if (adapter.table !== null) { + globals.push( + `@${adapter.table} = internal ${emitter.mod.workers ? "thread_local " : ""}global %ScrFfiTable zeroinitializer`, + ); + } + const params = cb.params.flatMap((param, i): string[] => { + if (isFfiContextParam(param)) return [`ptr %ctx`]; + if (param === "string" || param === "bytes") { + return [`ptr %a${i}`, `${emitter.sizeType} %a${i}_len`]; + } + return [`${ffiNativeParamLl(param, emitter.ffiExtendNarrowIntegers)} %a${i}`]; + }); + if (cb.invoke === "foreign") { + if ( + adapter.table === null || + !cb.params.some(isFfiContextParam) || + cb.returns !== "void" || + cb.params.some((param) => param === "i64" || param === "u64" || param === "pointer") + ) { + throw new InternalCompilerError( + "llvm emitter bug: invalid foreign FFI callback descriptor", + ); + } + const dispatch = `${adapter.symbol}_dispatch`; + const scriptArgs: string[] = []; + const dispatchBody: string[] = [ + `define internal void @${dispatch}(ptr %cb, ptr %call) ${FN_ATTRS} {`, + `entry:`, + ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, + ` %fn = load ptr, ptr %fnp`, + ]; + for (let i = 0; i < cb.params.length; i++) { + const param = cb.params[i]!; + if (isFfiContextParam(param)) continue; + switch (param) { + case "f64": + case "f32": + case "i8": + case "u16": + case "i16": + emitter.declare(`declare double @scr_ffi_call_get_f64(ptr, ${emitter.sizeType})`); + dispatchBody.push( + ` %s${i} = call double @scr_ffi_call_get_f64(ptr %call, ${emitter.sizeType} ${i})`, + ); + scriptArgs.push(`double %s${i}`); + break; + case "bool": + emitter.declare(`declare zeroext i1 @scr_ffi_call_get_bool(ptr, ${emitter.sizeType})`); + dispatchBody.push( + ` %s${i} = call zeroext i1 @scr_ffi_call_get_bool(ptr %call, ${emitter.sizeType} ${i})`, + ); + scriptArgs.push(`i1 %s${i}`); + break; + case "u8": + case "u32": + case "i32": + emitter.declare(`declare double @scr_ffi_call_get_${param}(ptr, ${emitter.sizeType})`); + dispatchBody.push( + ` %s${i} = call double @scr_ffi_call_get_${param}(ptr %call, ${emitter.sizeType} ${i})`, + ); + scriptArgs.push(`double %s${i}`); + break; + case "cstring": + case "string": + case "bytes": { + emitter.declare(`declare ptr @scr_ffi_call_get_data(ptr, ${emitter.sizeType})`); + emitter.declare( + `declare ${emitter.sizeType} @scr_ffi_call_get_len(ptr, ${emitter.sizeType})`, + ); + dispatchBody.push( + ` %data${i} = call ptr @scr_ffi_call_get_data(ptr %call, ${emitter.sizeType} ${i})`, + ` %len${i} = call ${emitter.sizeType} @scr_ffi_call_get_len(ptr %call, ${emitter.sizeType} ${i})`, + ); + if (param === "bytes") { + emitter.declare(`declare ptr @scr_bytes_from_data(ptr, ${emitter.sizeType})`); + dispatchBody.push( + ` %s${i} = call ptr @scr_bytes_from_data(ptr %data${i}, ${emitter.sizeType} %len${i})`, + ); + } else { + emitter.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${emitter.sizeType})`); + dispatchBody.push( + ` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %data${i}, ${emitter.sizeType} %len${i})`, + ); + } + scriptArgs.push(`ptr %s${i}`); + break; + } + } + } + dispatchBody.push( + ` call void %fn(${[`ptr %cb`, ...scriptArgs].join(", ")})`, + ` ret void`, + `}`, + ``, + ); + for (const line of dispatchBody) defs.push(line); + + emitter.declare(`declare ptr @scr_ffi_call_new(ptr, ptr, ptr, ${emitter.sizeType})`); + emitter.declare(`declare void @scr_ffi_post(ptr)`); + defs.push( + `define internal void @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, + `entry:`, + ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, + ` %call = call ptr @scr_ffi_call_new(ptr @${adapter.table}, ptr %cb, ptr @${dispatch}, ${emitter.sizeType} ${cb.params.length})`, + ); + for (let i = 0; i < cb.params.length; i++) { + const param = cb.params[i]!; + if (isFfiContextParam(param)) continue; + if (param === "cstring") { + emitter.declare(`declare void @scr_ffi_call_copy_cstring(ptr, ${emitter.sizeType}, ptr)`); + defs.push( + ` call void @scr_ffi_call_copy_cstring(ptr %call, ${emitter.sizeType} ${i}, ptr %a${i})`, + ); + } else if (param === "string" || param === "bytes") { + emitter.declare( + `declare void @scr_ffi_call_copy_${param}(ptr, ${emitter.sizeType}, ptr, ${emitter.sizeType})`, + ); + defs.push( + ` call void @scr_ffi_call_copy_${param}(ptr %call, ${emitter.sizeType} ${i}, ptr %a${i}, ${emitter.sizeType} %a${i}_len)`, + ); + } else if (param === "f32" || param === "i8" || param === "u16" || param === "i16") { + const nativeTy = ffiNativeTypeLl(param); + const op = param === "f32" ? "fpext" : param === "u16" ? "uitofp" : "sitofp"; + emitter.declare(`declare void @scr_ffi_call_set_f64(ptr, ${emitter.sizeType}, double)`); + defs.push( + ` %wide${i} = ${op} ${nativeTy} %a${i} to double`, + ` call void @scr_ffi_call_set_f64(ptr %call, ${emitter.sizeType} ${i}, double %wide${i})`, + ); + } else { + const nativeTy = ffiNativeTypeLl(param); + emitter.declare( + `declare void @scr_ffi_call_set_${param}(ptr, ${emitter.sizeType}, ${nativeTy})`, + ); + defs.push( + ` call void @scr_ffi_call_set_${param}(ptr %call, ${emitter.sizeType} ${i}, ${nativeTy} %a${i})`, + ); + } + } + defs.push(` call void @scr_ffi_post(ptr %call)`, ` ret void`, `}`, ``); + continue; + } + defs.push( + `define internal ${ffiNativeReturnLl(cb.returns, emitter.ffiExtendNarrowIntegers)} @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, + `entry:`, + adapter.tls !== null + ? ` %cb = load ptr, ptr @${adapter.tls}` + : adapter.global !== null + ? ` %cb = load ptr, ptr @${adapter.global}` + : ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, + ` %missing = icmp eq ptr %cb, null`, + ` br i1 %missing, label %expired, label %ready`, + `expired:`, + ` call void @scr_trap(ptr ${adapter.callback.lifetime === "call" ? expired : released})`, + ` unreachable`, + `ready:`, + ...emitter.pendingTestLines("%pending"), + ` br i1 %pending, label %skip, label %invoke`, + `skip:`, + ` ret ${ffiCallbackDummyLl(cb)}`, + `invoke:`, + ); + // Validate every native pointer before allocating any copy-in value. + // A later bad slot therefore cannot leak an earlier materialization. + for (let i = 0; i < cb.params.length; i++) { + const param = cb.params[i]!; + if (param !== "cstring" && param !== "string" && param !== "bytes") continue; + const invalid = `%invalid${i}`; + defs.push(` %null${i} = icmp eq ptr %a${i}, null`); + if (param === "cstring") { + defs.push(` ${invalid} = or i1 %null${i}, false`); + } else { + defs.push( + ` %nonempty${i} = icmp ne ${emitter.sizeType} %a${i}_len, 0`, + ` ${invalid} = and i1 %null${i}, %nonempty${i}`, + ); + } + const message = + param === "cstring" + ? "scriptc: native callback passed a NULL cstring\n" + : `scriptc: native callback passed a NULL ${param} span with nonzero length\n`; + defs.push( + ` br i1 ${invalid}, label %invalid_param${i}, label %param_ok${i}`, + `invalid_param${i}:`, + ` call void @scr_trap(ptr ${emitter.cstr(message)})`, + ` unreachable`, + `param_ok${i}:`, + ); + } + defs.push( + ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, + ` %fn = load ptr, ptr %fnp`, + ); + const scriptArgs: string[] = []; + for (let i = 0; i < cb.params.length; i++) { + const param = cb.params[i]!; + if (isFfiContextParam(param)) continue; + switch (param) { + case "i64": + case "u64": + case "pointer": { + const ty = ffiNativeTypeLl(param); + emitter.declare(`declare ptr @scr_bigint_from_${param}(${ty})`); + defs.push(` %s${i} = call ptr @scr_bigint_from_${param}(${ty} %a${i})`); + scriptArgs.push(`ptr %s${i}`); + break; + } + case "f64": + scriptArgs.push(`double %a${i}`); + break; + case "f32": + case "i8": + case "u16": + case "i16": { + const nativeTy = ffiNativeTypeLl(param); + const op = param === "f32" ? "fpext" : param === "u16" ? "uitofp" : "sitofp"; + defs.push(` %s${i} = ${op} ${nativeTy} %a${i} to double`); + scriptArgs.push(`double %s${i}`); + break; + } + case "bool": + defs.push(` %s${i} = icmp ne i8 %a${i}, 0`); + scriptArgs.push(`i1 %s${i}`); + break; + case "u8": + defs.push(` %s${i} = uitofp i8 %a${i} to double`); + scriptArgs.push(`double %s${i}`); + break; + case "u32": + defs.push(` %s${i} = uitofp i32 %a${i} to double`); + scriptArgs.push(`double %s${i}`); + break; + case "i32": + defs.push(` %s${i} = sitofp i32 %a${i} to double`); + scriptArgs.push(`double %s${i}`); + break; + case "cstring": + emitter.declare(`declare ${emitter.sizeType} @strlen(ptr)`); + emitter.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${emitter.sizeType})`); + defs.push( + ` %len${i} = call ${emitter.sizeType} @strlen(ptr %a${i})`, + ` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %a${i}, ${emitter.sizeType} %len${i})`, + ); + scriptArgs.push(`ptr %s${i}`); + break; + case "string": + emitter.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${emitter.sizeType})`); + defs.push( + ` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %a${i}, ${emitter.sizeType} %a${i}_len)`, + ); + scriptArgs.push(`ptr %s${i}`); + break; + case "bytes": + emitter.declare(`declare ptr @scr_bytes_from_data(ptr, ${emitter.sizeType})`); + defs.push( + ` %s${i} = call ptr @scr_bytes_from_data(ptr %a${i}, ${emitter.sizeType} %a${i}_len)`, + ); + scriptArgs.push(`ptr %s${i}`); + break; + } + } + const ft = ffiCallbackType(cb); + const internalRet = emitter.llType(ft.ret); + const callArgs = [`ptr %cb`, ...scriptArgs].join(", "); + if (cb.lifetime === "retained") { + emitter.declare(`declare ptr @scr_closure_retain_v(ptr)`); + emitter.declare(`declare void @scr_closure_release_v(ptr)`); + defs.push(` %invoke_pin = call ptr @scr_closure_retain_v(ptr %cb)`); + } + if (cb.returns === "void") { + defs.push( + ` call void %fn(${callArgs})`, + ...(cb.lifetime === "retained" + ? [` call void @scr_closure_release_v(ptr %invoke_pin)`] + : []), + ` ret void`, + `}`, + ``, + ); + continue; + } + defs.push(` %result = call ${internalRet} %fn(${callArgs})`); + if (cb.lifetime === "retained") { + defs.push(` call void @scr_closure_release_v(ptr %invoke_pin)`); + } + switch (cb.returns) { + case "i64": + case "u64": + case "pointer": { + const ty = ffiNativeTypeLl(cb.returns); + emitter.declare(`declare ${ty} @scr_bigint_to_${cb.returns}(ptr)`); + emitter.declare(`declare void @scr_bigint_release(ptr)`); + defs.push( + ` %out = call ${ty} @scr_bigint_to_${cb.returns}(ptr %result)`, + ` call void @scr_bigint_release(ptr %result)`, + ` ret ${ty} %out`, + ); + break; + } + case "f64": + defs.push(` ret double %result`); + break; + case "f32": + defs.push(` %out = fptrunc double %result to float`, ` ret float %out`); + break; + case "bool": + defs.push(` %out = zext i1 %result to i8`, ` ret i8 %out`); + break; + case "u8": + case "i8": + case "u16": + case "i16": + case "u32": + emitter.declare(`declare double @scr_bit_ushr(double, double)`); + defs.push( + ` %coerced = call double @scr_bit_ushr(double %result, double ${f64Lit(0)})`, + ` %wide = fptoui double %coerced to i32`, + ); + if (cb.returns !== "u32") { + const nativeTy = ffiNativeTypeLl(cb.returns); + defs.push(` %out = trunc i32 %wide to ${nativeTy}`, ` ret ${nativeTy} %out`); + } else { + defs.push(` ret i32 %wide`); + } + break; + case "i32": + emitter.declare(`declare double @scr_bit_or(double, double)`); + defs.push( + ` %coerced = call double @scr_bit_or(double %result, double ${f64Lit(0)})`, + ` %out = fptosi double %coerced to i32`, + ` ret i32 %out`, + ); + break; + } + defs.push(`}`, ``); + } + return { globals, defs }; +} + +export function emitParts(emitter: LlEmitter): string[] { + const parts = [ + ...limitInlineDynReleases(emitter.emitModuleParts()), + ...emitter.disposeObserverGates(), + ]; + if (!emitter.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 + // ones, because the runtime is always linked statically into the + // executable. So on ELF they can all use the local-exec model: LLVM's + // default general-dynamic sequence (relaxed by the linker to `mov %fs:0` + // plus an add), and even initial-exec (relaxed to `mov $offset` plus an + // indexed access), become one %fs-relative operand LLVM can fold into + // loads and compares. Darwin's thread-local variables have one model. + return parts.map((part) => + part + .replace( + /^(@[-$._A-Za-z0-9]+ = internal )thread_local global /gm, + "$1thread_local(localexec) global ", + ) + .replace( + /^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, + "$1thread_local(localexec) global ", + ), + ); +} diff --git a/packages/compiler/src/backend/llvm/emit-statements.ts b/packages/compiler/src/backend/llvm/emit-statements.ts new file mode 100644 index 000000000..a5da56f55 --- /dev/null +++ b/packages/compiler/src/backend/llvm/emit-statements.ts @@ -0,0 +1,2083 @@ +/* LlEmitter's statement emission: the statement switch, try/catch, switch, loop copies, speculative writes and string self-concatenation. */ +import { + type LlEmitter, + type LlScopeEntry, + adoptingAwaitOf, + MAX_BYTES_LOOP_VERSION_DEPTH, +} from "./emitter.js"; +import { CAUGHT, F64, type IrExpr, type IrStmt, STRING, isRefCounted } from "../../ir/ir.js"; +import { emitStringSliceSnapshot } from "./string-slices.js"; +import { + canDeferSplitPiece, + emitSplitCursor, + emitSplitNext, + emitSplitScratch, + emitSplitSnapshot, + emitSplitSpanNext, + loadSplitSnapshot, + storeSplitSnapshot, + stringSplit, +} from "./split-loops.js"; +import { + type StackObjectField, + type StackObjectSource, + emitStackObject, + stackObjectFields, + stackObjectStruct, +} from "./stack-objects.js"; +import { emitStackUnion } from "./stack-unions.js"; +import { emitBorrowedArrayRead, emitLocalArrayRead } from "./local-array-reads.js"; +import { emitStackMapRead } from "./map-read-lifetimes.js"; +import { allocateLocalUnion, storeLocalUnion } from "./local-union-storage.js"; +import { boxAccess, boxNewCall, elemAccess } from "./shapes.js"; +import { dynHoleRead } from "./loop-array-borrows.js"; +import { endsWithJump, matchStringSelfConcat } from "../../ir/analysis.js"; +import { emitDiscardedSplice } from "./expr-containers.js"; +import { InternalCompilerError } from "../../errors.js"; +import { emitDenseArrayGet, emitDenseArrayPop, emitDenseArraySet } from "./dense-array-access.js"; +import { emitBorrowedInput } from "./borrowed-inputs.js"; +import { f64Lit, llvmCommentText, sizeToDouble } from "./common.js"; +import { RESULT_DST_ID, RESULT_ENTRY_PREFIX } from "./record-results.js"; +import { + matchIntegerArrayForLoop, + matchIntegerBytesForLoop, + matchIntegerCountedForLoop, +} from "../../ir/integer-loops.js"; +import { emitByteWindowGuard, matchByteWindow } from "./byte-windows.js"; +import { countedDenseShape, emitDenseLoopGuard, matchDenseArrayLoop } from "./dense-array-loops.js"; +import { emitCountedLoopLimit, emitCountedLoopSafety } from "./counted-loops.js"; +import { emitBytesLoopVersionGuard } from "./bytes-loop-versions.js"; +import { mangleLocal } from "../mangle.js"; +import { LlvmUnsupportedError } from "./unsupported.js"; +import type { LlValue } from "./expr-context.js"; +import { SCALAR_UNION_RESULT } from "./int32-abi.js"; +import { emitLiteralSwitch } from "./switch-dispatch.js"; +import { + SPECULATIVE_RANGE, + emitSafeIntegerCheck, + emitSpeculativeInteger, + planSpeculativeLoop, + speculativeReads, + speculativeWriteTarget, +} from "./speculative-integers.js"; +import { + emitConcatInputs, + emitMixedConcat, + numberPart, + stringParts, +} from "./string-construction.js"; +import { emitStringInputs } from "./string-lifetimes.js"; + +export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void { + const B = emitter.B; + emitter.frames.push([]); + switch (s.kind) { + case "varDecl": { + if (s.init && emitter.int32Locals.has(s.localId)) { + emitter.storeInt32Local(s.localId, emitter.emitExpr(s.init), s.init); + break; + } + const b = emitter.binding(s.localId); + if (emitter.walkBorrows.has(s.localId) && b.kind === "local") { + // A borrowed walk pointer: no retain now, no release at scope exit. + const v = s.init === null ? "null" : emitter.emitReadReceiver(s.init).name; + B.line(`store ptr ${v}, ptr ${b.slot}`); + break; + } + const slice = emitter.scalarStringSlices.get(s.localId); + if (slice) { + const snapshot = emitStringSliceSnapshot(emitter, slice); + emitter.stringSlices.set(s.localId, snapshot); + emitter.scopes[emitter.scopes.length - 1]!.push({ slot: snapshot.source, type: STRING }); + break; + } + const split = emitter.privateSplitLocals.get(s.localId); + if (split) { + const snapshot = storeSplitSnapshot(emitter, emitSplitSnapshot(emitter, split)); + emitter.storedSplits.set(s.localId, snapshot); + emitter.scopes[emitter.scopes.length - 1]!.push( + { slot: snapshot.sourceSlot, type: STRING }, + { slot: snapshot.separatorSlot, type: STRING }, + ); + break; + } + const stackObject = emitter.localStackObjects.get(s.localId); + if (stackObject) { + const value = emitStackObject(emitter, stackObject, (fields) => + emitter.ownScopeFields(fields), + ); + B.line(`store ptr ${value.name}, ptr ${b.slot}`); + break; + } + if (s.init && emitter.reassignedStackObjects.has(s.localId)) { + const slot = emitter.stackObjectSlot(s.localId, s.init.type); + emitStackObject( + emitter, + s.init as StackObjectSource, + (fields) => emitter.ownScopeFields(fields), + slot, + ); + B.line(`store ptr ${slot}, ptr ${b.slot}`); + break; + } + const localUnion = emitter.localStackUnions.get(s.localId); + if (localUnion) { + const union = emitStackUnion(emitter, localUnion); + B.line(`store ptr ${union.value.name}, ptr ${b.slot}`); + if (union.payload) { + emitter.toFrame(union.payload); + emitter.scopes[emitter.scopes.length - 1]!.push({ + slot: union.payloadSlot, + type: union.payload.type, + }); + } + break; + } + const localRead = emitter.localArrayReads.get(s.localId); + if (localRead) { + const owner = emitLocalArrayRead( + emitter, + !localRead.borrow && emitter.unwrittenLocalArrayRead(s, localRead) + ? { ...localRead, borrow: true } + : localRead, + b.slot, + ); + if (owner) emitter.scopes[emitter.scopes.length - 1]!.push(owner); + break; + } + const mapRead = emitter.mapReadLifetimes.locals.get(s.localId); + if (mapRead) { + const result = emitStackMapRead(emitter, mapRead); + B.line(`store ptr ${result.value.name}, ptr ${b.slot}`); + if (result.owner) emitter.scopes[emitter.scopes.length - 1]!.push(result.owner); + break; + } + const localStorage = emitter.localUnionStorageProofs.get(s.localId); + if (localStorage && s.init) { + const storage = allocateLocalUnion(emitter, localStorage); + emitter.localUnionStorage.set(s.localId, storage); + storeLocalUnion(emitter, storage, s.init); + B.line(`store ptr ${storage.box}, ptr ${b.slot}`); + if (storage.owner && storage.ownerType) + emitter.scopes[emitter.scopes.length - 1]!.push({ + slot: storage.owner, + type: storage.ownerType, + }); + break; + } + if (b.kind === "boxed") { + const stack = emitter.stackCaptures.emit(emitter, s.localId, b.type); + if (stack) { + B.line(`store ptr ${stack.box}, ptr ${b.slot}`); + if (stack.owner) emitter.scopes[emitter.scopes.length - 1]!.push(stack.owner); + if (s.init) { + const value = emitter.emitExpr(s.init); + if (isRefCounted(value.type)) emitter.toFrame(value); + emitter.boxSet(stack.box, b.type, value.name); + } + break; + } + // Box FIRST, then evaluate the initializer: a named function + // expression's closure captures this box during init evaluation. + // A SCALAR TDZ box rides an ARR-kind box: the value lives in a + // one-element array cell, so the empty (NULL) slot stays the + // not-yet-initialized sentinel — a raw scalar slot has no spare + // bit pattern to spend on it (stmts.ts's varDecl). + const boxNew = + b.local!.tdz === true && boxAccess(b.type) !== "ref" + ? (emitter.declare(`declare ptr @scr_box_new(i32)`), `call ptr @scr_box_new(i32 3)`) + : boxNewCall(emitter.shapeHost, b.type); + const box = B.tmp(); + B.line(`${box} = ${boxNew} ; let ${b.local!.name} (boxed)`); + B.line(`store ptr ${box}, ptr ${b.slot}`); + emitter.scopes[emitter.scopes.length - 1]!.push({ + slot: b.slot, + type: b.type, + boxed: true, + }); + if (s.init === null) { + // An empty TDZ box whose payload can legitimately be NULL + // starts at its distinct sentinel (tdzEmptySentinel). + const sentinel = b.local!.tdz === true ? emitter.tdzEmptySentinel(b.type) : null; + if (sentinel !== null) B.line(`store ptr ${sentinel}, ptr ${emitter.boxSlot(box)}`); + break; + } + const v = emitter.emitExpr(s.init); + if (isRefCounted(v.type)) emitter.toHeap(v); // the box takes ownership + if (b.local!.tdz === true && boxAccess(b.type) !== "ref") { + emitter.tdzScalarInit(box, b.type, v.name); + } else { + emitter.boxSet(box, b.type, v.name); + } + break; + } + if (s.init === null) { + // Declared, uninitialized (`let x: number;`): reset the slot — + // inside a loop the previous iteration's scope exit released the + // old value and left a stale pointer (NULL-tolerant releases). + if (isRefCounted(b.type)) { + B.line(`store ptr null, ptr ${b.slot}`); + emitter.scopes[emitter.scopes.length - 1]!.push({ slot: b.slot, type: b.type }); + } + break; + } + if (emitter.loopArrayBorrows.has(s) && s.init.kind === "arrayGet") { + emitBorrowedArrayRead(emitter, s.init, b.slot); + break; + } + const holeRead = emitter.loopArrayBorrows.has(s) ? dynHoleRead(s.init) : null; + if (holeRead && s.init.kind === "ternary") { + // A hole reads as the immortal undefined value, which needs no owner. + const present = emitter.emitExpr(s.init.cond); + const filled = B.newLabel("borrow.hole.filled"), + missing = B.newLabel("borrow.hole.missing"), + done = B.newLabel("borrow.hole.done"); + B.condBr(present.name, filled, missing); + B.startBlock(filled); + emitBorrowedArrayRead(emitter, holeRead, b.slot); + B.br(done); + B.startBlock(missing); + emitter.declare("declare ptr @scr_dyn_undefined()"); + const undef = B.tmp(); + B.line(`${undef} = call ptr @scr_dyn_undefined()`); + B.line(`store ptr ${undef}, ptr ${b.slot}`); + B.br(done); + B.startBlock(done); + break; + } + // A stable lexical alias cannot outlive its unchanged local owner. + // Whole-value uses still retain their own copies, so returning or + // storing the alias preserves the ordinary heap representation. + // Boxed captures, reassigned sources and projections need ownership. + if ( + b.kind === "local" && + b.local && + isRefCounted(b.type) && + emitter.stableCallBindings.has(s.localId) && + (emitter.canBorrowCallArgument(s.init) || emitter.unwrittenAlias(s)) + ) { + const v = emitter.emitReadReceiver(s.init); + B.line(`store ptr ${v.name}, ptr ${b.slot}`); + break; + } + const v = emitter.emitExpr(s.init); + if (b.kind === "global") emitter.toHeap(v); + else emitter.toFrame(v); + B.line(`store ${emitter.llType(b.type)} ${v.name}, ptr ${b.slot}`); + emitter.storeIntegerView(s.localId, v, s.init); + if (isRefCounted(b.type)) { + emitter.scopes[emitter.scopes.length - 1]!.push({ slot: b.slot, type: b.type }); + emitter.noteLastUseMove(s, b); + } + break; + } + case "assign": { + if (emitter.int32Locals.has(s.localId)) { + emitter.storeInt32Local(s.localId, emitter.emitExpr(s.value), s.value); + break; + } + const owner = emitter.reboundOwnerSlots.get(s.localId); + if (owner !== undefined && !emitter.walkBorrows.has(s.localId)) { + // A rebound borrowed parameter: the new value moves into the owner + // slot, replacing (and releasing) the previous owned value. This + // precedes the in-place string append, which would release the + // caller's borrow. + const param = emitter.binding(s.localId); + const slot = param.slot; + const v = emitter.emitExpr(s.value); + emitter.toFrame(v); + const old = B.tmp(); + B.line(`${old} = load ptr, ptr ${owner}`); + emitter.releaseFrame(old, param.type); + B.line(`store ptr ${v.name}, ptr ${slot}`); + B.line(`store ptr ${v.name}, ptr ${owner}`); + break; + } + if (emitter.reassignedStackObjects.has(s.localId)) { + // The new value may read the old one: build it apart (its + // reference fields owned by this statement until it completes), + // then copy it over the binding's slot. The binding takes over the + // new fields; the old ones are released after the slot no longer + // holds them. + const struct = stackObjectStruct(s.value.type); + let owned: readonly StackObjectField[] = []; + const value = emitStackObject(emitter, s.value as StackObjectSource, (fields) => { + owned = fields; + for (const field of fields) emitter.ownSlot(field.ptr, field.type); + }); + const target = emitter.stackObjectSlot(s.localId, s.value.type); + const old = stackObjectFields(emitter, s.value.type, target).map((field) => { + const t = B.tmp(); + B.line(`${t} = load ptr, ptr ${field.ptr}`); + return { name: t, type: field.type }; + }); + const bits = B.tmp(); + B.line(`${bits} = load ${struct}, ptr ${value.name}`); + B.line(`store ${struct} ${bits}, ptr ${target}`); + emitter.disownSlots(owned); + for (const field of old) emitter.releaseFrame(field.name, field.type); + break; + } + const localStorage = emitter.localUnionStorage.get(s.localId); + if (localStorage) { + storeLocalUnion(emitter, localStorage, s.value); + break; + } + const self = matchStringSelfConcat(s.localId, s.value); + if (self) { + emitter.emitStringSelfConcatAssign(s.localId, self.left, self.suffix, false); + break; + } + const b = emitter.binding(s.localId); + if (emitter.walkBorrows.has(s.localId) && b.kind === "local") { + // Rebinding a borrowed walk pointer releases nothing. + B.line(`store ptr ${emitter.emitReadReceiver(s.value).name}, ptr ${b.slot}`); + break; + } + const v = emitter.emitExpr(s.value); + if (b.kind === "global" && !s.initializes) emitter.checkGlobalTdz(s.localId); + if (b.kind === "boxed") { + emitter.writeBindingBox(emitter.loadBox(b.slot), b.local!, v.name, s.initializes); + // The RHS remains frame-owned until a possible TDZ throw passes. + if (isRefCounted(v.type)) emitter.toHeap(v); + break; + } + if (b.kind === "global") emitter.toHeap(v); + else emitter.toFrame(v); + if (isRefCounted(b.type)) { + const old = B.tmp(); + B.line(`${old} = load ptr, ptr ${b.slot}`); + if (b.kind === "global") emitter.releaseHeap(old, b.type); + else emitter.releaseFrame(old, b.type); + } + B.line(`store ${emitter.llType(b.type)} ${v.name}, ptr ${b.slot}`); + emitter.storeIntegerView(s.localId, v, s.value); + if (b.kind === "global" && emitter.immortalGlobals.has(s.localId)) { + // An Immutable module constant (contracts/check.ts proved it is + // fresh, publishable and never written): its graph becomes + // immortal, so reads skip reference counting and the collector + // never visits it. + const lines = emitter.publishWalkers.emitPublish(v.name, v.type, s.loc); + for (const line of lines) B.line(line.trimStart()); + // Reads of this global skip retains from here on, which is only + // sound once the graph really is immortal. Node.js never fails + // here, so a refusal means the contract proof was wrong: stop + // rather than keep running with a mortal graph read as immortal. + if (lines.length > 0) { + emitter.declare(`declare zeroext i1 @scr_exc_pending()`); + emitter.declare(`declare void @scr_trap(ptr)`); + const refused = B.tmp(); + B.line(`${refused} = call zeroext i1 @scr_exc_pending()`); + const stop = B.newLabel("immutable.refused"); + const done = B.newLabel("immutable.published"); + B.condBr(refused, stop, done); + B.startBlock(stop); + B.line( + `call void @scr_trap(ptr ${emitter.cstr("scriptc: internal error: an Immutable value could not be published\n")})`, + ); + B.terminate("unreachable"); + B.startBlock(done); + } + } + break; + } + case "exprStmt": + // A statement-position splice never observes its removed elements. + if (!emitDiscardedSplice(emitter, s.expr)) emitter.emitDiscarded(s.expr); + break; + case "arraySet": { + // Evaluation order matches JS: array, index, then value. Ownership + // of a refcounted value moves into the array (the runtime releases + // the replaced element itself). A local receiver that the index and + // value cannot rebind is read in place, as for field stores; the + // released element cannot reach that binding. + if (emitter.emitArraySelfConcat(s)) break; + const arr = emitter.emitStableReceiver(s.arr, [s.index, s.value]); + const idx = emitter.emitExpr(s.index); + const v = emitter.emitExpr(s.value); + if (s.arr.type.kind !== "array") + throw new InternalCompilerError("llvm emitter bug: arraySet on non-array"); + const acc = elemAccess(s.arr.type.elem); + emitter.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name); + if (acc === "ref") emitter.toHeap(v); + emitDenseArraySet( + emitter, + arr.name, + idx, + s.index, + acc, + s.arr.type.elem, + v.name, + emitter.provenDenseIndex(s.arr, s.index), + ); + break; + } + case "arraySetLength": { + // Pop's `length = 0` store on an empty array only matters when the + // array can be frozen (published); its operand is a local. + if ( + s.pop === true && + s.length.kind === "numLit" && + s.length.value === 0 && + emitter.publishedTypes?.containerGuarded(s.arr.type) !== true + ) + break; + const arr = emitBorrowedInput(emitter, s.arr); + const length = emitter.emitExpr(s.length); + if (s.arr.type.kind !== "array") + throw new InternalCompilerError("llvm emitter bug: arraySetLength on non-array"); + emitter.emitPublishedArrayGuard(arr.name, s.arr.type, s.pop === true ? 2 : 3); + emitter.declare(`declare void @scr_arr_set_len(ptr, double)`); + // Assigning the current length (a reused work array reset to where + // it already is) changes nothing: the runtime's validation accepts + // exactly the value equal to the length (-0 included, NaN never), + // and no element is released. Everything else keeps the call. + const lenPtr = B.tmp(), + len = B.tmp(), + lenNumber = B.tmp(), + same = B.tmp(); + const resize = B.newLabel("arr.len.set"), + done = B.newLabel("arr.len.done"); + B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`); + emitter.markMemoryPointer(lenPtr, "array:header"); + B.line( + `${len} = load ${emitter.sizeType}, ptr ${lenPtr}${emitter.fieldAliasAttachment(lenPtr)}${emitter.arrayLengthRange}`, + ); + B.line( + `${lenNumber} = ${sizeToDouble(emitter.sizeType)} ${emitter.sizeType} ${len} to double`, + ); + B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`); + B.condBr(same, done, resize); + B.startBlock(resize); + if (s.pop === true) { + // A discarded pop's `length - 1` on a packed array removes the + // last element inline, then releases it. + const call = B.newLabel("arr.len.call"), + pop = B.newLabel("arr.len.pop"); + const lastNumber = B.tmp(), + dropsLast = B.tmp(); + B.line(`${lastNumber} = fsub double ${lenNumber}, 1.0`); + B.line(`${dropsLast} = fcmp oeq double ${length.name}, ${lastNumber}`); + B.condBr(dropsLast, pop, call); + B.startBlock(pop); + const elem = s.arr.type.elem; + const acc = elemAccess(elem); + const raw = emitDenseArrayPop(emitter, arr.name, acc, call, call); + if (acc === "ref" && isRefCounted(elem)) { + const old = B.tmp(); + B.line(`${old} = inttoptr i64 ${raw} to ptr`); + emitter.releaseHeap(old, elem); + } + B.br(done); + B.startBlock(call); + } + B.line(`call void @scr_arr_set_len(ptr ${arr.name}, double ${length.name})`); + emitter.emitPendingCheck(); + B.br(done); + B.startBlock(done); + break; + } + case "arraySetUndefined": + case "arrayDelete": { + const arr = emitBorrowedInput(emitter, s.arr); + const idx = emitter.emitExpr(s.index); + if (s.arr.type.kind !== "array") + throw new InternalCompilerError(`llvm emitter bug: ${s.kind} on non-array`); + emitter.emitPublishedArrayGuard( + arr.name, + s.arr.type, + s.kind === "arrayDelete" ? 5 : 0, + idx.name, + ); + const fn = s.kind === "arraySetUndefined" ? "scr_arr_set_undefined" : "scr_arr_delete"; + emitter.declare( + `declare ${s.kind === "arrayDelete" ? "zeroext i1" : "void"} @${fn}(ptr, double)`, + ); + if (s.kind === "arrayDelete") + B.line(`call zeroext i1 @${fn}(ptr ${arr.name}, double ${idx.name})`); + else B.line(`call void @${fn}(ptr ${arr.name}, double ${idx.name})`); + break; + } + case "bytesSet": { + // Typed-array element write: same evaluation order as arraySet; + // the value is a scalar (the kind-specific inline path coerces + // JS-exactly), so no ownership moves. Invalid writes are ignored. + // IrBytesElem is static, so never rediscover it through the + // generic runtime switch in a hot loop. + const arr = emitter.emitStableReceiver(s.arr, [s.index, s.value]); + const idx = emitter.emitExpr(s.index); + const v = emitter.emitExpr(s.value); + if (s.arr.type.kind !== "bytes") + throw new InternalCompilerError("llvm emitter bug: bytesSet on non-bytes"); + emitter.emitBytesSet( + s.arr.type.elem, + arr.name, + idx, + v, + s.index, + s.value, + emitter.bytesBounds.has(s), + ); + break; + } + case "fieldSet": + case "recordSet": { + // Evaluation order: obj, then value. New value moved in; the old + // value is released AFTER the field is overwritten (unlink-then- + // release — a release can trigger a cycle collection, which must + // never see a heap edge whose count was already given up). + // Classes and records share the struct layout, so one emission. + const nullable = + s.kind === "fieldSet" ? emitter.nullableFields.get(s.className, s.field) : null; + if (nullable && s.kind === "fieldSet") { + const obj = emitter.emitStableReceiver(s.obj, [s.value]); + // (The guard precedes the value here: the nullable store + // evaluates it itself.) + emitter.emitStoreGuard(obj.name, s); + emitter.emitNullableFieldStore(obj.name, s.className, s.field, nullable, s.value); + break; + } + if (s.kind === "fieldSet" && emitter.int32Slots.isField(s.className, s.field)) { + const obj = emitter.emitStableReceiver(s.obj, [s.value]); + const v = emitter.emitExpr(s.value); + emitter.emitStoreGuard(obj.name, s); + const { ptr } = emitter.classFieldPtr(obj.name, s.className, s.field); + emitter.storeInt32Field(ptr, v, s.value); + break; + } + if (emitter.emitFieldSelfConcat(s)) break; + const obj = emitter.emitStableReceiver(s.obj, [s.value]); + const v = emitter.emitFieldValue( + s.value, + s.kind === "recordSet" + ? emitter.recordFieldInline(s.shapeId, s.field) + : emitter.inlineUnions.classField(s.className, s.field), + ); + if (s.kind === "fieldSet") emitter.emitStoreGuard(obj.name, s); + else if (emitter.publishedTypes?.recordGuarded(s.shapeId) === true) + emitter.emitPublishedFieldGuard(obj.name, null, s.field); + const { ptr, type, inline } = + s.kind === "fieldSet" + ? emitter.classFieldPtr(obj.name, s.className, s.field) + : emitter.recordFieldPtr(obj.name, s.shapeId, s.field); + // A result-slot entry fills zeroed caller storage, each field once. + if ( + s.kind === "recordSet" && + s.obj.kind === "varRef" && + s.obj.localId === RESULT_DST_ID && + emitter.currentFunctionName.startsWith(RESULT_ENTRY_PREFIX) + ) + emitter.initField(ptr, type, inline, v); + else emitter.replaceField(ptr, type, inline, v); + if ( + s.kind === "fieldSet" && + s.field === "name" && + emitter.classMeta.get(s.className)?.root.def.name === "%Error" + ) { + const present = emitter.classFieldPtr(obj.name, s.className, "%namePresent").ptr; + const enumerable = emitter.classFieldPtr(obj.name, s.className, "%nameEnumerable").ptr; + const wasPresent = B.tmp(), + wasEnumerable = B.tmp(), + isPresent = B.tmp(), + nextEnumerable = B.tmp(); + B.line(`${wasPresent} = load i8, ptr ${present}`); + B.line(`${wasEnumerable} = load i8, ptr ${enumerable}`); + B.line(`${isPresent} = icmp ne i8 ${wasPresent}, 0`); + B.line(`${nextEnumerable} = select i1 ${isPresent}, i8 ${wasEnumerable}, i8 1`); + B.line(`store i8 1, ptr ${present}`); + B.line(`store i8 ${nextEnumerable}, ptr ${enumerable}`); + } + break; + } + case "recordKeyDelete": { + // `delete obj[k]` on a pure index-signature shape: a Map delete on + // the overflow (key and value released; absent keys no-op). + const obj = emitter.emitExpr(s.obj); + const key = emitter.emitExpr(s.key); + if (emitter.publishedTypes?.recordGuarded(s.shapeId) === true) { + // Node: "Cannot delete property 'k' of #" (key known at + // run time only; the guard reports the frozen record generically). + emitter.emitPublishedFieldGuard(obj.name, null, "[key]"); + } + const ovf = emitter.recordOvfPtr(obj.name, s.shapeId); + emitter.declare(`declare zeroext i1 @scr_map_delete_str(ptr, ptr)`); + const t = B.tmp(); + B.line(`${t} = call zeroext i1 @scr_map_delete_str(ptr ${ovf}, ptr ${key.name})`); + break; + } + case "recordKeySet": { + // Dynamic-keyed record write (the C per-shape helper, inline): + // declared keys write through (same-typed — dyn-valued shapes, + // whose writes validate and can throw, stay refused), undeclared + // keys insert/replace in the overflow map. Evaluation order: obj, + // key, value; the write OWNS the value (+1 moves in). + const obj = emitter.emitExpr(s.obj); + const key = emitter.emitExpr(s.key); + const v = emitter.emitExpr(s.value); + if (emitter.publishedTypes?.recordGuarded(s.shapeId) === true) + emitter.emitPublishedFieldGuard(obj.name, null, "[key]"); + if (isRefCounted(v.type)) emitter.toHeap(v); + const shape = emitter.recordShape(s.shapeId); + // Signature-free shapes dispatch over their (one-typed) declared + // fields and TRAP on a miss (scr_record_key_miss — JS would add + // the property, which a monomorphic struct cannot); overflow + // shapes keep the map insert tail. + const iv = shape.indexValue ?? shape.fields[0]?.type; + if (!iv) + throw new InternalCompilerError( + `llvm emitter bug: keyed write on field-free non-overflow shape ${s.shapeId}`, + ); + const vAcc = iv.kind === "f64" ? "f64" : iv.kind === "bool" ? "bool" : "ref"; + if (s.overflowOnly === true) { + // A LITERAL key naming no declared field: a plain overflow + // insert — no field chain. + const ovf = emitter.recordOvfPtr(obj.name, s.shapeId); + emitter.mapSet(ovf, "str", vAcc, key.name, v.name); + break; + } + const join = B.newLabel("rks.j"); + if (iv.kind === "dyn" && shape.indexValue) { + // A dyn-valued shape (the C recordKeySetHelper's dyn arm, + // inline): declared keys VALIDATE the dyn value against the + // field's type first (dynCheck — a mismatched write throws the + // catchable TypeError and leaves the field untouched; JS would + // store anything, the documented divergence); undeclared keys + // insert the dyn value into the overflow map as-is. + emitter.declare(`declare void @scr_dyn_release(ptr)`); + for (const f of shape.fields) { + const hit = B.tmp(); + B.line( + `${hit} = call zeroext i1 ${emitter.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`, + ); + const lh = B.newLabel("rks.h"); + const ln = B.newLabel("rks.n"); + B.condBr(hit, lh, ln); + B.startBlock(lh); + const pathSlot = B.slot(); + B.entryAllocas.push(`${pathSlot} = alloca %ScrDynPath`); + const pp = B.tmp(); + const kp = B.tmp(); + const ip = B.tmp(); + B.line(`${pp} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 0`); + B.line(`store ptr null, ptr ${pp}`); + B.line(`${kp} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 1`); + B.line(`store ptr ${emitter.cstr(f.name)}, ptr ${kp}`); + B.line(`${ip} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 2`); + B.line(`store ${emitter.sizeType} 0, ptr ${ip}`); + const helper = emitter.dyn.dynCheckHelper(f.type); + const fty = emitter.llType(f.type); + const nv = B.tmp(); + B.line( + `${nv} = call ${fty === "i1" ? "zeroext i1" : fty} @${helper}(ptr ${v.name}, ptr ${pathSlot})`, + ); + B.line(`call void @scr_dyn_release(ptr ${v.name})`); + // Mismatched write: TypeError pending, field untouched — the + // statement-level check below unwinds. + const pend = B.tmp(); + for (const line of emitter.pendingTestLines(pend)) B.line(line.trimStart()); + const lw = B.newLabel("rks.w"); + B.condBr(pend, join, lw); + B.startBlock(lw); + const { ptr, type, inline } = emitter.recordFieldPtr(obj.name, s.shapeId, f.name); + if (inline) { + // The slot copies the checked value's bits; nv is released below. + emitter.storeField(ptr, type, nv, true); + emitter.releaseFrame(nv, type); + } else if (isRefCounted(type)) { + const old = B.tmp(); + B.line(`${old} = load ptr, ptr ${ptr}`); + emitter.storeField(ptr, type, nv, false); + emitter.releaseHeap(old, type); + } else { + emitter.storeField(ptr, type, nv, false); + } + B.br(join); + B.startBlock(ln); + } + const ovf = emitter.recordOvfPtr(obj.name, s.shapeId); + emitter.mapSet(ovf, "str", vAcc, key.name, v.name); + B.br(join); + B.startBlock(join); + // MAY THROW exactly when a dyn value can validate against a + // declared field (stmts.ts's condition). + if (shape.fields.length > 0) emitter.emitPendingCheck(); + break; + } + for (const f of shape.fields) { + // typeEquals(f.type, iv) — the frontend fences everything else. + const hit = B.tmp(); + B.line( + `${hit} = call zeroext i1 ${emitter.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`, + ); + const lh = B.newLabel("rks.h"); + const ln = B.newLabel("rks.n"); + B.condBr(hit, lh, ln); + B.startBlock(lh); + const { ptr, type, inline } = emitter.recordFieldPtr(obj.name, s.shapeId, f.name); + if (inline) { + // The write owns the moved-in value; the slot copies its bits. + emitter.storeField(ptr, type, v.name, true); + emitter.releaseHeap(v.name, type); + } else if (isRefCounted(type)) { + const old = B.tmp(); + B.line(`${old} = load ptr, ptr ${ptr}`); + emitter.storeField(ptr, type, v.name, false); + emitter.releaseHeap(old, type); + } else { + emitter.storeField(ptr, type, v.name, false); + } + B.br(join); + B.startBlock(ln); + } + if (!shape.indexValue) { + // The MISS on a fixed shape: release the moved-in value, throw + // the catchable TypeError naming the key (scr_record_key_miss — + // JS would add the property, the documented divergence). + if (isRefCounted(iv)) emitter.releaseHeap(v.name, iv); + emitter.declare(`declare void @scr_record_key_miss(ptr)`); + B.line(`call void @scr_record_key_miss(ptr ${key.name})`); + B.br(join); + B.startBlock(join); + emitter.emitPendingCheck(); + break; + } + const ovf = emitter.recordOvfPtr(obj.name, s.shapeId); + emitter.mapSet(ovf, "str", vAcc, key.name, v.name); + B.br(join); + B.startBlock(join); + break; + } + case "block": { + if (s.labels === undefined) { + emitter.emitBlock(s.body); + break; + } + // A labeled block: `break lbl` inside branches to the end label. + const le = B.newLabel("blk.e"); + emitter.jumpTargets.push({ + kind: "block", + brkLabel: le, + contLabel: null, + labels: s.labels, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + finallyDepth: emitter.finallyStack.length, + }); + emitter.emitBlock(s.body); + emitter.jumpTargets.pop(); + B.br(le); + B.startBlock(le); + break; + } + case "if": { + const cond = emitter.emitCondition(s.cond); + const trueLabel = B.newLabel("if.t"); + const joinLabel = B.newLabel("if.j"); + const falseLabel = s.else_ ? B.newLabel("if.f") : joinLabel; + B.condBr(cond, trueLabel, falseLabel); + B.startBlock(trueLabel); + emitter.emitBlock(s.then); + B.br(joinLabel); + if (s.else_) { + B.startBlock(falseLabel); + emitter.emitBlock(s.else_); + B.br(joinLabel); + } + B.startBlock(joinLabel); + break; + } + case "while": { + const workerBudget = emitter.workerLoopBudget(s); + const le = B.newLabel("loop.e"); + emitter.emitLoopCopies(s, true, le, (exit) => { + const lc = B.newLabel("loop.c"); + const lb = B.newLabel("loop.b"); + const lp = workerBudget !== null ? B.newLabel("loop.p") : lc; + B.br(lc); + B.startBlock(lc); + B.condBr(emitter.emitCondition(s.cond), lb, exit); + B.startBlock(lb); + emitter.jumpTargets.push({ + kind: "loop", + brkLabel: exit, + contLabel: lp, + labels: s.labels, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + finallyDepth: emitter.finallyStack.length, + }); + emitter.emitBlock(s.body); + emitter.jumpTargets.pop(); + B.br(lp); + if (lp !== lc) { + B.startBlock(lp); + emitter.emitWorkerLoopCheck(workerBudget); + B.br(lc); + } + }); + B.startBlock(le); + break; + } + case "doWhile": { + const workerBudget = emitter.workerLoopBudget(s); + // Body first (runs at least once); continue jumps to the CONDITION. + const le = B.newLabel("loop.e"); + emitter.emitLoopCopies(s, true, le, (exit) => { + const lb = B.newLabel("loop.b"); + const lc = B.newLabel("loop.c"); + B.br(lb); + B.startBlock(lb); + emitter.jumpTargets.push({ + kind: "loop", + brkLabel: exit, + contLabel: lc, + labels: s.labels, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + finallyDepth: emitter.finallyStack.length, + }); + emitter.emitBlock(s.body); + emitter.jumpTargets.pop(); + B.br(lc); + B.startBlock(lc); + emitter.emitWorkerLoopCheck(workerBudget); + B.condBr(emitter.emitCondition(s.cond), lb, exit); + }); + B.startBlock(le); + break; + } + case "for": { + // The init's scope wraps the whole loop (break/continue must NOT + // release it — scopeDepth captured after the push, C parity). + const integerArrayLoop = + emitter.debug === null + ? matchIntegerArrayForLoop(s, emitter.currentLocals, emitter.integerArrayBindings) + : null; + const integerLoop = + integerArrayLoop ?? + (emitter.debug === null ? matchIntegerBytesForLoop(s, emitter.numericLocals) : null); + const countedLoop = + !integerLoop && emitter.countedLoopsEnabled + ? matchIntegerCountedForLoop( + s, + emitter.numericLocals, + emitter.integerRanges, + emitter.unaliasedArrays, + ) + : null; + const window = + countedLoop && emitter.byteWindowsEnabled + ? matchByteWindow( + s, + countedLoop, + emitter.numericLocals, + emitter.captureIds, + emitter.integerRanges, + emitter.byteWindowEntries.get(s) ?? new Set(), + ) + : null; + const denseShape = !emitter.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, + emitter.numericLocals, + emitter.unaliasedArrays, + emitter.denseLoopEntries.get(s) ?? new Set(), + ) + : null; + if (dense) { + const valid = emitDenseLoopGuard(emitter, 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); + emitter.denseLoopsEnabled = false; + try { + B.startBlock(slow); + emitter.emitStmt(s); + B.br(done); + B.startBlock(fast); + emitter.denseLoop = dense; + emitter.emitStmt(s); + B.br(done); + } finally { + emitter.denseLoop = null; + emitter.denseLoopsEnabled = true; + } + B.startBlock(done); + break; + } + if (window) { + const valid = emitByteWindowGuard(emitter, window); + const fast = B.newLabel("bytes.window.fast"), + slow = B.newLabel("bytes.window.slow"), + done = B.newLabel("bytes.window.done"); + B.condBr(valid, fast, slow); + const originalRanges = emitter.integerRanges, + originalBounds = emitter.bytesBounds; + emitter.byteWindowsEnabled = false; + try { + B.startBlock(slow); + emitter.emitStmt(s); + B.br(done); + B.startBlock(fast); + emitter.integerRanges = window.ranges; + emitter.bytesBounds = new Set([...originalBounds, ...window.bounds]); + emitter.emitStmt(s); + B.br(done); + } finally { + emitter.integerRanges = originalRanges; + emitter.bytesBounds = originalBounds; + emitter.byteWindowsEnabled = true; + } + B.startBlock(done); + break; + } + const version = + emitter.bytesLoopVersionsEnabled && + emitter.bytesLoopVersioning.size < MAX_BYTES_LOOP_VERSION_DEPTH && + !emitter.bytesLoopVersioning.has(s) + ? emitter.matchBytesLoopVersion(s, countedLoop) + : null; + if (version) { + const fast = B.newLabel("bytes.version.fast"), + slow = B.newLabel("bytes.version.slow"), + done = B.newLabel("bytes.version.done"); + emitBytesLoopVersionGuard(emitter, version, fast, slow); + const originalBounds = emitter.bytesBounds, + originalRanges = emitter.integerRanges; + emitter.bytesLoopVersioning.add(s); + try { + B.startBlock(slow); + emitter.emitStmt(s); + B.br(done); + B.startBlock(fast); + emitter.bytesBounds = new Set([...originalBounds, ...version.bounds]); + if (version.ranges.size > 0) + emitter.integerRanges = new Map([...originalRanges, ...version.ranges]); + if (version.counted?.guarded) emitter.provenCountedGuards.add(s); + emitter.emitStmt(s); + B.br(done); + } finally { + emitter.bytesBounds = originalBounds; + emitter.integerRanges = originalRanges; + emitter.provenCountedGuards.delete(s); + emitter.bytesLoopVersioning.delete(s); + } + B.startBlock(done); + break; + } + let countedJoin: string | null = null; + if (countedLoop?.guarded && !emitter.provenCountedGuards.has(s)) { + const safe = emitCountedLoopSafety(emitter, countedLoop); + const fast = B.newLabel("counted.fast"); + const slow = B.newLabel("counted.slow"); + countedJoin = B.newLabel("counted.done"); + B.condBr(safe, fast, slow); + B.startBlock(slow); + emitter.countedLoopsEnabled = false; + emitter.emitStmt(s); + emitter.countedLoopsEnabled = true; + B.br(countedJoin); + B.startBlock(fast); + } + emitter.scopes.push([]); + let integerSlot: string | null = null; + let countedLimit: string | null = null; + if (integerLoop) { + integerSlot = B.slot(); + B.entryAllocas.push( + `${integerSlot} = alloca ${emitter.sizeType} ; integer induction ${emitter.currentLocals.get(integerLoop.localId)!.name}`, + ); + let start = "0"; + if (integerArrayLoop && s.init?.kind === "varDecl" && s.init.init) { + const value = emitter.emitExpr(s.init.init); + start = B.tmp(); + // The start is 0 or an enclosing array index plus one (at most + // 2^32 - 1): on 64-bit targets the signed conversion is exact + // and avoids x86-64's unsigned 64-bit conversion sequence. + const conversion = emitter.sizeType === "i64" ? "fptosi" : "fptoui"; + B.line(`${start} = ${conversion} double ${value.name} to ${emitter.sizeType}`); + emitter.integerArrayBindings.add(integerLoop.localId); + } + B.line(`store ${emitter.sizeType} ${start}, ptr ${integerSlot}`); + emitter.integerLoopBindings.set(integerLoop.localId, { + slot: integerSlot, + type: emitter.sizeType, + range: + emitter.denseLoop?.counter === integerLoop.localId + ? emitter.denseLoop.range + : { min: 0, max: integerArrayLoop ? 4294967294 : Number.MAX_SAFE_INTEGER - 1 }, + }); + } else if (countedLoop) { + integerSlot = B.slot(); + B.entryAllocas.push( + `${integerSlot} = alloca i64 ; integer induction ${emitter.currentLocals.get(countedLoop.localId)!.name}`, + ); + const startNumber = emitter.emitExpr(countedLoop.start); + const start = B.tmp(); + B.line(`${start} = fptosi double ${startNumber.name} to i64`); + B.line(`store i64 ${start}, ptr ${integerSlot}`); + emitter.integerLoopBindings.set(countedLoop.localId, { + slot: integerSlot, + type: "i64", + signed: true, + range: + emitter.denseLoop?.counter === countedLoop.localId + ? emitter.denseLoop.range + : countedLoop.range, + }); + countedLimit = emitCountedLoopLimit(emitter, countedLoop); + } else if (s.init) { + // A multi-declarator head shares the loop's scope. Emitting its + // IR block as an ordinary block would release captured/ref locals + // before the first condition. + const initializers = s.init.kind === "block" ? s.init.body : [s.init]; + for (const initializer of initializers) emitter.emitStmt(initializer); + } + const le = B.newLabel("loop.e"); + // JS `for (let i ...)`: each iteration gets a FRESH binding holding + // a copy of the previous one (closures made in iteration k keep + // seeing iteration k's value) — only observable, and only emitted, + // when a let variable is captured (boxed). Freshen before the first + // condition and again in the continue-target block before updating. + const initializers = s.init?.kind === "block" ? s.init.body : s.init ? [s.init] : []; + const capturedLets = initializers.flatMap((init) => { + const local = init.kind === "varDecl" ? emitter.currentLocals.get(init.localId) : undefined; + return local?.boxed && local.mutable ? [local] : []; + }); + const freshenBindings = (): void => { + for (const local of capturedLets) { + const slot = `%${mangleLocal(local.id)}`; + const fresh = B.tmp(); + const old = B.tmp(); + B.line( + `${fresh} = ${boxNewCall(emitter.shapeHost, local.type)} ; per-iteration ${local.name}`, + ); + B.line(`${old} = load ptr, ptr ${slot}`); + const val = emitter.boxGet(old, local.type); + emitter.boxSet(fresh, local.type, val); + emitter.declare(`declare void @scr_box_release(ptr)`); + B.line(`call void @scr_box_release(ptr ${old})`); + B.line(`store ptr ${fresh}, ptr ${slot}`); + } + }; + const workerBudget = emitter.workerLoopBudget(s); + freshenBindings(); + emitter.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => { + const lc = B.newLabel("loop.c"); + const lb = B.newLabel("loop.b"); + const lu = + s.update || capturedLets.length > 0 || workerBudget !== null ? B.newLabel("loop.u") : lc; + B.br(lc); + B.startBlock(lc); + if (integerLoop && integerSlot) { + const receiver = emitter.emitStableReceiver(integerLoop.limitReceiver, []); + const lenPtr = B.tmp(); + const len = B.tmp(); + const index = B.tmp(); + const inBounds = B.tmp(); + B.line( + `${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`, + ); + emitter.markMemoryPointer(lenPtr, integerArrayLoop ? "array:header" : "bytes:header"); + B.line( + `${len} = load ${emitter.sizeType}, ptr ${lenPtr}${emitter.fieldAliasAttachment(lenPtr)}`, + ); + B.line(`${index} = load ${emitter.sizeType}, ptr ${integerSlot}`); + B.line(`${inBounds} = icmp ult ${emitter.sizeType} ${index}, ${len}`); + B.condBr(inBounds, lb, exit); + } else if (countedLoop && integerSlot && countedLimit) { + const index = B.tmp(); + const inBounds = B.tmp(); + B.line(`${index} = load i64, ptr ${integerSlot}`); + B.line( + `${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`, + ); + B.condBr(inBounds, lb, exit); + } else if (s.cond) B.condBr(emitter.emitCondition(s.cond), lb, exit); + else B.br(lb); + B.startBlock(lb); + emitter.jumpTargets.push({ + kind: "loop", + brkLabel: exit, + contLabel: lu, + labels: s.labels, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + finallyDepth: emitter.finallyStack.length, + }); + emitter.emitBlock(s.body); + emitter.jumpTargets.pop(); + B.br(lu); + if (lu !== lc) { + B.startBlock(lu); + emitter.emitWorkerLoopCheck(workerBudget); + freshenBindings(); + if (integerLoop && integerSlot) { + const old = B.tmp(); + const next = B.tmp(); + B.line(`${old} = load ${emitter.sizeType}, ptr ${integerSlot}`); + B.line(`${next} = add nuw ${emitter.sizeType} ${old}, 1`); + B.line(`store ${emitter.sizeType} ${next}, ptr ${integerSlot}`); + } else if (countedLoop && integerSlot) { + const old = B.tmp(); + const next = B.tmp(); + B.line(`${old} = load i64, ptr ${integerSlot}`); + B.line(`${next} = add nsw i64 ${old}, ${countedLoop.step}`); + B.line(`store i64 ${next}, ptr ${integerSlot}`); + } else if (s.update) emitter.emitStmt(s.update); + B.br(lc); + } + }); + B.startBlock(le); + emitter.releaseScope(emitter.scopes.pop()!); + if (integerLoop) emitter.integerLoopBindings.delete(integerLoop.localId); + if (countedLoop) emitter.integerLoopBindings.delete(countedLoop.localId); + if (integerArrayLoop) emitter.integerArrayBindings.delete(integerArrayLoop.localId); + if (countedJoin) { + B.br(countedJoin); + B.startBlock(countedJoin); + } + break; + } + case "forOf": { + // Ascending index loop; the length is re-read every iteration + // (JS-exact — pushes inside the body extend the iteration). The + // iterable temp lives in this statement's frame, so it is released + // when the whole loop ends (and by `return`'s frame sweep). + if (s.iterable.type.kind !== "array") + throw new LlvmUnsupportedError(`forOf:${s.iterable.type.kind}`, s.loc); + const elem = s.iterable.type.elem; + const stored = + s.iterable.kind === "varRef" ? emitter.storedSplits.get(s.iterable.localId) : undefined; + const direct = emitter.streamingSplitsEnabled ? stringSplit(s.iterable) : null; + const snapshot = stored + ? loadSplitSnapshot(emitter, stored) + : direct + ? emitSplitSnapshot(emitter, direct) + : null; + const arr = snapshot ? null : emitter.emitExpr(s.iterable); + const cursor = snapshot ? emitSplitCursor(emitter, snapshot) : null; + const scratch = snapshot ? emitSplitScratch(emitter) : null; + const deferred = snapshot && canDeferSplitPiece(s, emitter.currentLocals.get(s.localId)); + const spanLength = deferred ? B.slot() : null; + if (spanLength) B.entryAllocas.push(`${spanLength} = alloca ${emitter.sizeType}`); + const idxSlot = snapshot ? null : B.slot(); + if (idxSlot) { + B.entryAllocas.push(`${idxSlot} = alloca double`); + B.line(`store double ${f64Lit(0)}, ptr ${idxSlot}`); + } + const lc = B.newLabel("fof.c"); + const lb = B.newLabel("fof.b"); + const lu = B.newLabel("fof.u"); + const le = B.newLabel("fof.e"); + const workerBudget = emitter.workerLoopBudget(s); + B.br(lc); + B.startBlock(lc); + const inBounds = B.tmp(); + let cur: string; + if (snapshot && cursor && scratch) { + cur = spanLength + ? emitSplitSpanNext(emitter, snapshot, cursor, spanLength) + : emitSplitNext(emitter, snapshot, cursor, scratch); + B.line(`${inBounds} = icmp ne ptr ${cur}, null`); + } else { + const i = B.tmp(), + lenPtr = B.tmp(), + rawLen = B.tmp(), + len = B.tmp(); + B.line(`${i} = load double, ptr ${idxSlot}`); + // The inline form of scr_arr_len, like the `length` intrinsic. + B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr!.name}, i32 0, i32 1`); + emitter.markMemoryPointer(lenPtr, "array:header"); + B.line( + `${rawLen} = load ${emitter.sizeType}, ptr ${lenPtr}${emitter.fieldAliasAttachment(lenPtr)}${emitter.arrayLengthRange}`, + ); + B.line( + `${len} = ${sizeToDouble(emitter.sizeType)} ${emitter.sizeType} ${rawLen} to double`, + ); + B.line(`${inBounds} = fcmp olt double ${i}, ${len}`); + cur = i; + } + B.condBr(inBounds, lb, le); + B.startBlock(lb); + emitter.jumpTargets.push({ + kind: "loop", + brkLabel: le, + contLabel: lu, + labels: s.labels, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + finallyDepth: emitter.finallyStack.length, + }); + // The loop variable is a fresh const per iteration: its scope opens + // here, holds the (for ref elements: owned +1) current element, and + // releases it at the end of each iteration. + emitter.scopes.push([]); + const localInfo = emitter.currentLocals.get(s.localId); + const slot = `%${mangleLocal(s.localId)}`; + const borrowedElement = !snapshot && emitter.loopArrayBorrows.has(s); + if (!snapshot) { + const acc = elemAccess(elem); + const getter = borrowedElement ? "scr_arr_borrow_ref" : `scr_arr_get_${acc}`; + cur = emitDenseArrayGet( + emitter, + arr!.name, + { name: cur, type: { kind: "f64" } }, + undefined, + acc, + elem, + getter, + !borrowedElement, + ); + } + if (spanLength) { + const length = B.tmp(); + B.line(`${length} = load ${emitter.sizeType}, ptr ${spanLength}`); + emitter.splitSpans.set(s.localId, { bytes: cur, length, scratch: scratch! }); + cur = "null"; + } + if (localInfo?.boxed) { + // Captured loop variable: a fresh box per iteration, matching the + // fresh const binding. The box takes ownership of a ref element's + // +1 and is released with the iteration's scope. + const box = B.tmp(); + B.line(`${box} = ${boxNewCall(emitter.shapeHost, elem)} ; per-iteration ${localInfo.name}`); + emitter.boxSet(box, elem, cur); + B.line(`store ptr ${box}, ptr ${slot}`); + emitter.scopes[emitter.scopes.length - 1]!.push({ slot, type: elem, boxed: true }); + } else { + B.line(`store ${emitter.llType(elem)} ${cur}, ptr ${slot}`); + if (isRefCounted(elem) && !borrowedElement) + emitter.scopes[emitter.scopes.length - 1]!.push({ slot, type: elem }); + } + emitter.emitStmts(s.body); + if (spanLength) emitter.splitSpans.delete(s.localId); + const endedWithJump = endsWithJump(s.body); + const scope = emitter.scopes.pop()!; + if (!endedWithJump) emitter.releaseScope(scope); + emitter.jumpTargets.pop(); + B.br(lu); + B.startBlock(lu); + emitter.emitWorkerLoopCheck(workerBudget); + if (idxSlot) { + const i2 = B.tmp(), + i3 = B.tmp(); + B.line(`${i2} = load double, ptr ${idxSlot}`); + B.line(`${i3} = fadd double ${i2}, ${f64Lit(1)}`); + B.line(`store double ${i3}, ptr ${idxSlot}`); + } + B.br(lc); + B.startBlock(le); + break; + } + case "switch": + emitter.emitSwitch(s); + break; + case "break": { + // Unlabeled: the innermost loop OR switch (labeled blocks are + // skipped); labeled: the entry carrying the label. + let target: (typeof emitter.jumpTargets)[number] | undefined; + for (let i = emitter.jumpTargets.length - 1; i >= 0; i--) { + const t = emitter.jumpTargets[i]!; + if (s.label !== undefined ? t.labels?.includes(s.label) : t.kind !== "block") { + target = t; + break; + } + } + if (!target) throw new InternalCompilerError("llvm emitter bug: break target not found"); + emitter.emitFinallysForJump(target.finallyDepth, target.frameDepth, target.scopeDepth); + if (!B.isTerminated()) B.terminate(`br label %${target.brkLabel}`); + break; + } + case "continue": { + // Unlabeled: the innermost loop; labeled: the loop carrying the + // label (tsc + the validator guarantee it IS a loop). + let target: (typeof emitter.jumpTargets)[number] | undefined; + for (let i = emitter.jumpTargets.length - 1; i >= 0; i--) { + const t = emitter.jumpTargets[i]!; + if (t.kind === "loop" && (s.label === undefined || t.labels?.includes(s.label))) { + target = t; + break; + } + } + if (!target || target.contLabel === null) + throw new InternalCompilerError("llvm emitter bug: continue target not found"); + emitter.emitFinallysForJump(target.finallyDepth, target.frameDepth, target.scopeDepth); + if (!B.isTerminated()) B.terminate(`br label %${target.contLabel}`); + break; + } + case "return": { + // The value computes FIRST (an SSA temp — finally mutations of + // returned locals cannot change it, Node-exact), then every + // crossed finally runs innermost-first with the frames/scopes/ + // tryStack it sees truncated to its region (its releases already + // ran; a throw inside a copy propagates OUT of the completing + // try, past its own catch), then the function-level releases and + // the actual ret. The parked value owns a synthetic slot-backed + // scope entry during each copy so a throwing finally releases it. + const adopted = + emitter.finallyStack.length > 0 || emitter.tryStack.length > 0 + ? adoptingAwaitOf(s.value) + : null; + if (adopted !== null) { + emitter.emitAdoptingReturn(s, adopted); + break; + } + let v: LlValue | null = null; + const moved = + emitter.finallyStack.length === 0 && !emitter.currentUnionResult + ? emitter.movableReturn(s.value) + : null; + if (s.value !== null && emitter.currentUnionResult) { + // The tag and payload bits leave in registers; a heap box the + // value needed is released with its frame below. + const box = emitter.emitUnionProjection(s.value); + v = { name: emitter.scalarUnionPair(box.name, s.value.type), type: s.value.type }; + } else if (moved !== null) { + // Returning an owned local hands its reference to the caller: no + // retain now and no release of the local below. + const t = B.tmp(); + B.line(`${t} = load ptr, ptr ${moved.entry.slot}`); + v = { name: t, type: s.value!.type }; + moved.scope.splice(moved.scope.indexOf(moved.entry), 1); + emitter.releaseForJump(0, 0); + moved.scope.splice(moved.index, 0, moved.entry); + } else if (s.value !== null && emitter.currentBorrowedReturn) { + // A borrowed-return body hands back its walk without a reference + // (it has no try statements, so no finally can intervene). An + // optional element read lends the element its array keeps alive. + const read = emitter.optionalArrayReads.get(s.value); + if (read) { + const slot = B.slot(); + B.entryAllocas.push(`${slot} = alloca ptr`); + emitLocalArrayRead(emitter, { ...read, borrow: true }, slot); + const t = B.tmp(); + B.line(`${t} = load ptr, ptr ${slot}`); + v = { name: t, type: s.value.type }; + } else v = emitter.emitReadReceiver(s.value); + } else if (s.value !== null) { + v = emitter.emitExpr(s.value); + if (emitter.currentCoro !== null) emitter.toHeap(v); + else emitter.toFrame(v); + } + // An async generator's `return v` awaits v before any crossed + // finally runs. Past finally regions that turn is taken here and + // the request settles at completion; otherwise the completion's + // own turn is the await (nothing runs in between). + const genSettles = + emitter.currentAsyncGenerator && (s.awaited === true || emitter.finallyStack.length > 0); + // A completion turn that awaits x replaces a return() request's + // already-taken await. + if (emitter.currentAsyncGenerator && !genSettles) { + emitter.declare(`declare void @scr_async_gen_return_awaits()`); + B.line(`call void @scr_async_gen_return_awaits()`); + } + if (emitter.currentAsyncGenerator && s.awaited !== true && emitter.finallyStack.length > 0) { + if (emitter.wasi) emitter.emitWasiSuspend(null); + else { + emitter.declare(`declare void @scr_await_hop()`); + B.line(`call void @scr_await_hop()`); + } + } + if (emitter.finallyStack.length > 0) { + let pretSlot: string | null = null; + if (v !== null && isRefCounted(v.type) && !emitter.currentUnionResult) { + pretSlot = B.slot(); + B.entryAllocas.push(`${pretSlot} = alloca ptr ; pending return (through finally)`); + B.line(`store ptr ${v.name}, ptr ${pretSlot}`); + } + const savedFrames = emitter.frames; + const savedScopes = emitter.scopes; + const savedFinally = emitter.finallyStack; + const savedTry = emitter.tryStack; + for (let i = savedFinally.length - 1; i >= 0 && !B.isTerminated(); i--) { + const fin = savedFinally[i]!; + emitter.releaseForJump(fin.frameDepth, fin.scopeDepth); + emitter.frames = emitter.frames.slice(0, fin.frameDepth); + emitter.scopes = emitter.scopes.slice(0, fin.scopeDepth); + emitter.finallyStack = savedFinally.slice(0, i); + emitter.tryStack = savedTry.slice(0, fin.tryDepth); + if (pretSlot !== null) emitter.scopes.push([{ slot: pretSlot, type: v!.type }]); + emitter.emitBlock(fin.body); + if (pretSlot !== null) emitter.scopes.pop(); + } + if (!B.isTerminated()) emitter.releaseForJump(0, 0); + emitter.frames = savedFrames; + emitter.scopes = savedScopes; + emitter.finallyStack = savedFinally; + emitter.tryStack = savedTry; + } else if (moved === null) { + emitter.releaseForJump(0, 0); + } + if (!B.isTerminated()) { + if (genSettles) emitter.emitAsyncGenSettled(); + if (emitter.currentCoro !== null) { + emitter.emitWasiFulfill(v); + B.terminate(`br label %${emitter.currentCoro.finalLabel}`); + } else if (v === null) B.terminate("ret void"); + else if (emitter.currentInt32Result) + B.terminate(`ret i32 ${emitter.int32Argument(v, s.value!)}`); + else if (emitter.currentUnionResult) B.terminate(`ret ${SCALAR_UNION_RESULT} ${v.name}`); + else B.terminate(`ret ${emitter.llType(s.value!.type)} ${v.name}`); + } + break; + } + case "throw": { + // Evaluate, move ownership into the runtime's exception cell, then + // unwind unconditionally (the innermost try handler, or out of the + // function) — the same release path as return/break/continue. + const v = emitter.emitExpr(s.value); + if (isRefCounted(s.value.type)) emitter.toHeap(v); // the cell takes ownership + emitter.emitThrowValue({ name: v.name, type: s.value.type }); + emitter.emitUnwind(); + break; + } + case "rethrow": { + // Re-raise the saved snapshot (payload retained — the binding + // local releases with its scope) and unwind like `throw`. + const c = emitter.emitExpr({ kind: "varRef", localId: s.localId, type: CAUGHT, loc: s.loc }); + emitter.declare(`declare void @scr_rethrow(ptr)`); + B.line(`call void @scr_rethrow(ptr ${c.name})`); + emitter.emitUnwind(); + break; + } + case "runtimeFence": { + // The deferred JS compile fence: throw a catchable Error naming + // the construct (message) with the SC code stamped on `code`, + // then unwind exactly like `throw`. SCR_ERR_ERROR = 0. + const bytes = Buffer.byteLength(s.message, "utf8"); + emitter.declare(`declare void @scr_throw_error_msg_code(i32, ptr, ${emitter.sizeType}, ptr)`); + B.line( + `call void @scr_throw_error_msg_code(i32 0, ptr ${emitter.cstr(s.message)}, ${emitter.sizeType} ${bytes}, ptr ${emitter.cstr(s.code)})`, + ); + emitter.emitUnwind(); + break; + } + case "tryCatch": + emitter.emitTryCatch(s); + break; + default: { + // Statement coverage is now total (bytesSet closed the set) — + // keep throwing LlvmUnsupportedError for any future IR statement kind. + const rest: never = s; + const k = (rest as IrStmt).kind; + throw new LlvmUnsupportedError(`stmt:${k}`, (rest as IrStmt).loc); + } + } + const frame = emitter.frames.pop()!; + // return/throw already released their frames on the jump path; the + // fall-through releases after them would be dead double-release code. + if ( + s.kind !== "return" && + s.kind !== "throw" && + s.kind !== "rethrow" && + s.kind !== "runtimeFence" + ) { + emitter.releaseTemps(frame); + } +} + +/** try/catch/finally via pending-flag unwinding — stmts.ts's + * emitTryCatch, block-flavored. Entering a try emits NO code: the try + * context is compile-time state (tryStack) redirecting unwinds inside + * the region to a label here. Shape: + * + * { try body } unwinds inside release frames/scopes down to + * this statement's depths, then br the handler + * br after (normal completion skips the handler) + * try.c: (emitted only when some unwind targets it) + * binding = scr_exc_take() (or scr_exc_clear() when bindingless) + * { catch body } + * after: { finally body } normal path + * br try.e + * try.fx: exception path: the pending exception is + * stash = scr_exc_take() STASHED across the finally body so the + * { finally body } body's own pending checks answer for + * scr_rethrow(stash) themselves; a throw inside REPLACES the + * stash (it unwinds through the synthetic + * try.e: scope entry) — JS's semantics exactly + * + * Abrupt completions inside tryBody/catchBody ride the finallyStack: + * returns inline copies at the return site, while break/continue use the + * shared region walk before branching to their resolved target. */ +export function emitTryCatch(emitter: LlEmitter, s: IrStmt & { kind: "tryCatch" }): void { + const B = emitter.B; + const hasCatch = s.catchBody !== null; + const hasFinally = s.finallyBody !== null; + const catchLabel = B.newLabel("try.c"); + const finExcLabel = B.newLabel("try.fx"); + const endLabel = B.newLabel("try.e"); + const afterTryLabel = hasFinally ? B.newLabel("try.f") : endLabel; + + const handler = { + label: hasCatch ? catchLabel : finExcLabel, + used: false, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + }; + if (hasFinally) { + emitter.finallyStack.push({ + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + tryDepth: emitter.tryStack.length, + body: s.finallyBody!, + }); + } + emitter.tryStack.push(handler); + emitter.emitBlock(s.tryBody); + emitter.tryStack.pop(); + B.br(afterTryLabel); // no-op when the try body already terminated + + // Exceptions raised in the CATCH body unwind to the exception-path + // finally (pending stays set through it) when one exists. + const excHandler = { + label: finExcLabel, + used: !hasCatch && handler.used, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + }; + + if (hasCatch && handler.used) { + B.startBlock(catchLabel); + if (hasFinally) emitter.tryStack.push(excHandler); + if (emitter.currentGenerator !== null) { + // Generator bodies: a pending GENRET sentinel (.return(v) + // injected at a yield) is a RETURN completion, not a throw — + // catch must not take it. Re-unwind past this handler (finally + // still runs — the unwind targets the exception-path finally or + // the enclosing context; the depths here equal the handler's). + emitter.declare(`declare zeroext i1 @scr_exc_genret_pending()`); + const gr = B.tmp(); + B.line(`${gr} = call zeroext i1 @scr_exc_genret_pending()`); + const lg = B.newLabel("try.gr"); + const lk = B.newLabel("try.gk"); + B.condBr(gr, lg, lk); + B.startBlock(lg); + emitter.emitUnwind(); + B.startBlock(lk); + } + if (s.catchLocalId !== null) { + // catch (e): the exception MOVES into the binding's snapshot box, + // owned by the catch body's scope (released on every exit — + // normal fall-through, jumps out, and unwinds from the body). + const slot = `%${mangleLocal(s.catchLocalId)}`; + emitter.declare(`declare ptr @scr_exc_take()`); + emitter.emitBlock(s.catchBody!, (scope) => { + const c = B.tmp(); + B.line(`${c} = call ptr @scr_exc_take() ; catch binding`); + if (emitter.currentLocals.get(s.catchLocalId!)?.boxed) { + const box = B.tmp(); + B.line(`${box} = ${boxNewCall(emitter.shapeHost, CAUGHT)} ; captured exception`); + emitter.boxSet(box, CAUGHT, c); + B.line(`store ptr ${box}, ptr ${slot}`); + scope.push({ slot, type: CAUGHT, boxed: true }); + } else { + B.line(`store ptr ${c}, ptr ${slot}`); + scope.push({ slot, type: CAUGHT }); + } + }); + } else { + emitter.declare(`declare void @scr_exc_clear()`); + B.line(`call void @scr_exc_clear() ; catch takes the exception`); + emitter.emitBlock(s.catchBody!); + } + if (hasFinally) emitter.tryStack.pop(); + B.br(afterTryLabel); // the catch's normal completion + } + if (hasFinally) emitter.finallyStack.pop(); + + if (hasFinally) { + B.startBlock(afterTryLabel); + emitter.emitBlock(s.finallyBody!); // normal path + B.br(endLabel); + if (excHandler.used) { + // The pending exception is STASHED across the finally body (a + // ScrCaught snapshot, re-raised after) so the body runs with a + // CLEAN cell — see stmts.ts's exception-path copy. The + // stash rides an alloca slot so a throw inside the body unwinds + // through the synthetic scope entry (replace semantics). + B.startBlock(finExcLabel); + emitter.declare(`declare ptr @scr_exc_take()`); + emitter.declare(`declare void @scr_rethrow(ptr)`); + emitter.declare(`declare void @scr_caught_release(ptr)`); + const stash = B.tmp(); + const stashSlot = B.slot(); + B.entryAllocas.push(`${stashSlot} = alloca ptr ; finally exception stash`); + B.line(`${stash} = call ptr @scr_exc_take() ; stash across finally`); + B.line(`store ptr ${stash}, ptr ${stashSlot}`); + emitter.scopes.push([{ slot: stashSlot, type: CAUGHT }]); + const suppressHandler = + s.suppressFinallyErrors || s.dropFinallyErrors + ? { + label: B.newLabel("try.fs"), + used: false, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + } + : null; + if (suppressHandler) emitter.tryStack.push(suppressHandler); + emitter.emitBlock(s.finallyBody!); + if (suppressHandler) emitter.tryStack.pop(); + emitter.scopes.pop(); // normal completion keeps the stash for the re-raise + B.line(`call void @scr_rethrow(ptr ${stash})`); + B.line(`call void @scr_caught_release(ptr ${stash})`); + emitter.emitUnwind(); + if (suppressHandler?.used) { + B.startBlock(suppressHandler.label); + const cleanup = s.dropFinallyErrors ? "scr_exc_keep_first" : "scr_exc_suppress"; + emitter.declare(`declare void @${cleanup}(ptr)`); + B.line(`call void @${cleanup}(ptr ${stash}) ; consumes stash`); + emitter.emitUnwind(); + } + } + B.startBlock(endLabel); + } else { + B.startBlock(endLabel); + } +} + +/** JS-exact switch: lazily evaluated, arbitrary-expression case tests in + * source order, bodies falling through in source order until a break — + * Literal-only tests can use native dispatch; other tests keep a chain + * of conditional branches. All case bodies + * share ONE scope; because dispatch can jump PAST a varDecl into a later + * case, refcounted/boxed case-body locals are NULL-reset up front and + * the scope-exit releases rely on NULL tolerance. */ +export function emitSwitch(emitter: LlEmitter, s: IrStmt & { kind: "switch" }): void { + const unitSwitch = charAtUnitSwitch(s); + if (unitSwitch !== null) return emitSwitch(emitter, unitSwitch); + const B = emitter.B; + const discKind = s.disc.type.kind; + if (discKind !== "f64" && discKind !== "string" && discKind !== "bool") { + throw new LlvmUnsupportedError(`switch:${discKind}`, s.loc); + } + // The disc temp lives in the whole statement's frame: for a string + // discriminant it stays alive across every test and body, released + // when the switch statement ends (break lands past this statement's + // frame release — releaseForJump keeps the target's own frame). + const disc = emitter.emitExpr(s.disc); + for (const c of s.cases) { + for (const stmt of c.body) { + if (stmt.kind !== "varDecl") continue; + const local = emitter.currentLocals.get(stmt.localId)!; + if (local.boxed || isRefCounted(local.type)) { + B.line(`store ptr null, ptr %${mangleLocal(local.id)} ; case-scoped ${local.name}`); + } + } + } + const end = B.newLabel("sw.e"); + const caseLabels = s.cases.map(() => B.newLabel("sw.c")); + const defaultIdx = s.cases.findIndex((c) => c.test === null); + const fallback = defaultIdx >= 0 ? caseLabels[defaultIdx]! : end; + const nullableString = nullableStringSwitch(emitter, s); + if (nullableString !== null) { + // `undefined` matches no string literal; any other value is the + // string itself and dispatches as a string switch. + const value = emitter.emitExpr(nullableString.value); + const present = B.tmp(); + const dispatch = B.newLabel("sw.string"); + B.line(`${present} = icmp ne ptr ${value.name}, null`); + B.condBr(present, dispatch, fallback); + B.startBlock(dispatch); + const text: LlValue = { name: value.name, type: STRING }; + if (!emitLiteralSwitch(emitter, text, undefined, nullableString.cases, caseLabels, fallback)) { + nullableString.cases.forEach((c, i) => { + if (c.test?.kind !== "strLit") return; + const hit = B.tmp(); + B.line( + `${hit} = call zeroext i1 ${emitter.literalEquals(c.test.value)}(ptr ${value.name})`, + ); + const next = B.newLabel("sw.t"); + B.condBr(hit, caseLabels[i]!, next); + B.startBlock(next); + }); + B.br(fallback); + } + } else if (!emitLiteralSwitch(emitter, disc, s.disc, s.cases, caseLabels, fallback)) { + s.cases.forEach((c, i) => { + if (c.test === null) return; + // Lazy source-order test evaluation (a test after the match never + // runs). Each test's temps release right after its comparison. + emitter.frames.push([]); + const t = emitter.emitExpr(c.test); + const hit = B.tmp(); + if (c.test.kind === "strLit") { + B.line(`${hit} = call zeroext i1 ${emitter.literalEquals(c.test.value)}(ptr ${disc.name})`); + } else if (c.test.type.kind === "string") { + emitter.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`); + B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${disc.name}, ptr ${t.name})`); + } else if (c.test.type.kind === "bool") { + B.line(`${hit} = icmp eq i1 ${disc.name}, ${t.name}`); + } else { + B.line(`${hit} = fcmp oeq double ${disc.name}, ${t.name}`); + } + emitter.releaseTemps(emitter.frames.pop()!); + const next = B.newLabel("sw.t"); + B.condBr(hit, caseLabels[i]!, next); + B.startBlock(next); + }); + B.br(fallback); + } + + emitter.jumpTargets.push({ + kind: "switch", + brkLabel: end, + contLabel: null, + labels: s.labels, + frameDepth: emitter.frames.length, + scopeDepth: emitter.scopes.length, + finallyDepth: emitter.finallyStack.length, + }); + const scope: LlScopeEntry[] = []; + emitter.scopes.push(scope); + s.cases.forEach((c, i) => { + B.br(caseLabels[i]!); // the previous body's natural fall-through + B.startBlock(caseLabels[i]!); + emitter.emitStmts(c.body); + }); + emitter.jumpTargets.pop(); + emitter.scopes.pop(); + // Natural fall-off of the last body releases the shared scope; a jump + // already released it before jumping. + const lastBody = s.cases[s.cases.length - 1]?.body; + if (!lastBody || !endsWithJump(lastBody)) emitter.releaseScope(scope); + B.br(end); + B.startBlock(end); +} + +/** Emit a loop's condition, body and update through `emitCopy`, twice + * when speculative-integers.ts plans a fast integer copy. Both copies + * share the loop's setup and exit; the ordinary copy comes first so the + * fast copy is only emitted when every resume point exists. */ +export function emitLoopCopies( + emitter: LlEmitter, + loop: Extract, + allowed: boolean, + exit: string, + emitCopy: (exit: string) => void, +): void { + const plan = + allowed && + emitter.speculativeLoopsEnabled && + emitter.speculation === null && + emitter.speculationResume === null + ? planSpeculativeLoop( + loop, + (id) => emitter.speculativeLocal(id), + (id) => emitter.integerLoopBindings.has(id) || emitter.int32Locals.has(id), + (e) => { + const range = emitter.integerRanges.get(e); + return ( + range !== undefined && + range !== null && + range.min >= -Number.MAX_SAFE_INTEGER && + range.max <= Number.MAX_SAFE_INTEGER + ); + }, + (id) => emitter.speculativeLocal(id) || emitter.speculativeInvariantGlobal(id), + ) + : null; + if (!plan) { + emitCopy(exit); + return; + } + const B = emitter.B; + const entries = new Map(); + let ok = "true"; + for (const [id, live] of plan.locals) { + if (!live) continue; + const value = B.tmp(); + B.line(`${value} = load double, ptr ${emitter.binding(id).slot}`); + const [integer, exact] = emitSafeIntegerCheck(emitter, value); + entries.set(id, integer); + if (ok === "true") ok = exact; + else { + const both = B.tmp(); + B.line(`${both} = and i1 ${ok}, ${exact}`); + ok = both; + } + } + const fast = B.newLabel("spec.fast"), + slow = B.newLabel("spec.slow"); + B.condBr(ok, fast, slow); + const resume = new Map(); + for (const s of plan.writes) resume.set(s, { label: B.newLabel("spec.resume"), placed: false }); + B.startBlock(slow); + emitter.speculationResume = resume; + try { + emitCopy(exit); + } finally { + emitter.speculationResume = null; + } + B.startBlock(fast); + if (![...resume.values()].every((r) => r.placed)) { + B.br(slow); + return; + } + for (const id of plan.locals.keys()) { + const shadow = B.slot(); + B.entryAllocas.push( + `${shadow} = alloca i64 ; speculative integer ${emitter.currentLocals.get(id)?.name ?? id}`, + ); + const entry = entries.get(id); + if (entry !== undefined) { + // The same double, now visibly an integer conversion: the double + // phis of the fast copy fold into integer phis. + const exact = B.tmp(); + B.line(`store i64 ${entry}, ptr ${shadow}`); + B.line(`${exact} = sitofp i64 ${entry} to double`); + B.line(`store double ${exact}, ptr ${emitter.binding(id).slot}`); + } else B.line(`store i64 0, ptr ${shadow}`); + emitter.integerLoopBindings.set(id, { + slot: shadow, + type: "i64", + signed: true, + range: SPECULATIVE_RANGE, + }); + } + // A global's store stays in the loop (calls may read it). When nothing + // in the loop can observe it, its double is written once on leaving the + // copy instead, so the loop keeps it in a register as the double copy + // would. + const deferred = new Set(); + // Worker loops also leave through their cancellation poll. + if (plan.observerFree && !emitter.mod.workers) + for (const s of plan.writes) { + const id = speculativeWriteTarget(s)!.localId; + if (!emitter.currentLocals.has(id)) deferred.add(id); + } + emitter.speculation = { + writes: plan.writes, + resume: new Map([...resume].map(([s, r]) => [s, r.label])), + deferred, + }; + try { + if (deferred.size === 0) emitCopy(exit); + else { + const leave = B.newLabel("spec.leave"); + emitCopy(leave); + B.startBlock(leave); + emitter.writeBackSpeculativeGlobals(deferred); + B.br(exit); + } + } finally { + emitter.speculation = null; + for (const id of plan.locals.keys()) emitter.integerLoopBindings.delete(id); + } +} + +/** A speculated write in the fast copy: compute the value as a safe + * integer, store it to the shadow and its exact double to the local. + * Any failed check re-executes the statement in the ordinary copy. */ +export function emitSpeculativeWrite(emitter: LlEmitter, s: IrStmt, resume: string): void { + const B = emitter.B; + const target = speculativeWriteTarget(s)!; + const binding = emitter.integerLoopBindings.get(target.localId)!; + const failed = B.newLabel("spec.bail"); + let bails = false; + emitter.declare("declare i1 @llvm.expect.i1(i1, i1)"); + const bail = (condition: string): void => { + if (condition === "false") return; + bails = true; + const expected = B.tmp(); + B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${condition}, i1 false)`); + const next = B.newLabel("spec.ok"); + B.condBr(expected, failed, next); + B.startBlock(next); + }; + // Heap reads come first, before any check, and release their temporaries + // at once: a bailing path then owns nothing, and reads have no effect + // whose reordering before the arithmetic could be observed. + const reads = new Map(); + if (target.value !== null) { + emitter.frames.push([]); + for (const read of speculativeReads(target.value)) reads.set(read, emitter.emitExpr(read)); + emitter.releaseTemps(emitter.frames.pop()!); + } + emitter.frames.push([]); + let result: string; + if (target.value === null) { + const old = B.tmp(); + result = B.tmp(); + B.line(`${old} = load i64, ptr ${binding.slot}`); + B.line(`${result} = ${target.op === "+" ? "add" : "sub"} nsw i64 ${old}, 1`); + const biased = B.tmp(), + outside = B.tmp(); + B.line(`${biased} = add i64 ${result}, ${Number.MAX_SAFE_INTEGER}`); + B.line(`${outside} = icmp ugt i64 ${biased}, ${2 * Number.MAX_SAFE_INTEGER}`); + bail(outside); + } else result = emitSpeculativeInteger(emitter, target.value, bail, reads); + // Speculable values read only numbers past this point. + if (emitter.frames.pop()!.length > 0) + throw new InternalCompilerError("llvm emitter bug: speculative write owns a temporary"); + B.line(`store i64 ${result}, ptr ${binding.slot}`); + const deferred = emitter.speculation!.deferred; + if (!deferred.has(target.localId)) { + const number = B.tmp(); + B.line(`${number} = sitofp i64 ${result} to double`); + B.line(`store double ${number}, ptr ${emitter.binding(target.localId).slot}`); + } + if (!bails) return; + const done = B.newLabel("spec.done"); + B.br(done); + B.startBlock(failed); + emitter.writeBackSpeculativeGlobals(deferred); + B.br(resume); + B.startBlock(done); +} + +/** + * Lower canonical `target = target + suffix` after evaluating the old + * left side and suffix in JavaScript order. The snapshot stays owned by + * the statement frame while the destination relinquishes its CURRENT + * value, making the snapshot unique unless a real observable alias exists. + */ +export function emitStringSelfConcatAssign( + emitter: LlEmitter, + localId: string, + left: IrExpr, + suffix: IrExpr, + retainForYield: boolean, +): LlValue { + const moved = emitter.emitMovedSelfConcat(localId, left, suffix, retainForYield); + if (moved) return moved; + const snapshot = emitter.emitExpr(left); + // A suffix of several parts, or a number, appends each operand to the + // snapshot directly (in place once the binding lets go of it) instead + // of first building the suffix as a separate string. + const parts = stringParts(suffix); + const mixed = parts.length > 1 || numberPart(parts[0]!) !== null; + const charCode = mixed ? null : emitter.emitSingleCharCode(suffix); + const inputs = mixed ? emitConcatInputs(emitter, parts) : []; + // The concat only reads the suffix; relinquishing the binding below + // runs no code that could rebind a borrowed local. + const right = mixed || charCode !== null ? null : emitStringInputs(emitter, [suffix])[0]!; + const b = emitter.binding(localId); + const B = emitter.B; + if (b.kind === "boxed") { + // set_ref(NULL) unlinks then releases the binding's post-suffix value. + emitter.boxSet(emitter.loadBox(b.slot), b.type, "null"); + } else { + const old = B.tmp(); + B.line(`${old} = load ptr, ptr ${b.slot}`); + B.line(`store ptr null, ptr ${b.slot}`); + if (b.kind === "global") emitter.releaseHeap(old, b.type); + else emitter.releaseFrame(old, b.type); + } + let raw: string; + if (charCode !== null) { + // The snapshot moves into the consuming character append. + emitter.toFrame(snapshot); + emitter.declare(`declare ptr @scr_str_concat_move_char_code(ptr, double)`); + raw = B.tmp(); + B.line( + `${raw} = call ptr @scr_str_concat_move_char_code(ptr ${snapshot.name}, double ${charCode.name})`, + ); + } else if (right === null) { + raw = emitMixedConcat(emitter, snapshot.name, inputs); + } else { + emitter.declare(`declare ptr @scr_str_concat(ptr, ptr)`); + raw = B.tmp(); + B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`); + } + const result = emitter.own({ name: raw, type: left.type }); + if (retainForYield) { + const stored = + b.kind === "local" + ? emitter.retainFrame(result.name, result.type) + : emitter.retainHeap(result.name, result.type); + if (b.kind === "boxed") emitter.boxSet(emitter.loadBox(b.slot), b.type, stored); + else B.line(`store ptr ${stored}, ptr ${b.slot}`); + } else { + if (b.kind === "local") emitter.toFrame(result); + else emitter.toHeap(result); + if (b.kind === "boxed") emitter.boxSet(emitter.loadBox(b.slot), b.type, result.name); + else B.line(`store ptr ${result.name}, ptr ${b.slot}`); + } + return result; +} + +/** `switch (x)` on a `string | undefined` value whose case tests are all + * string literals: the frontend's `switch (true)` over `x === "lit"` union + * equalities against one switch local. The local and the cases with plain + * string literal tests. */ +function nullableStringSwitch( + emitter: LlEmitter, + s: IrStmt & { kind: "switch" }, +): { value: IrExpr; cases: (IrStmt & { kind: "switch" })["cases"] } | null { + if (s.disc.kind !== "boolLit" || !s.disc.value) return null; + let value: (IrExpr & { kind: "varRef" }) | null = null; + const cases: (IrStmt & { kind: "switch" })["cases"] = []; + for (const c of s.cases) { + const test = c.test; + if (test === null) { + cases.push(c); + continue; + } + if ( + test.kind !== "unionEq" || + test.negated || + test.sameValue || + test.left.kind !== "varRef" || + test.right.kind !== "unionWrap" || + test.right.value.kind !== "strLit" + ) + return null; + const nullable = emitter.nullableUnions.get(test.unionId); + if ( + nullable === null || + nullable.repr !== "single" || + nullable.nullTag !== -1 || + nullable.arm.kind !== "string" || + test.right.tag !== nullable.refTag + ) + return null; + if (value !== null && value.localId !== test.left.localId) return null; + value = test.left; + cases.push({ ...c, test: test.right.value }); + } + return value === null ? null : { value, cases }; +} + +/** `switch (s.charAt(i))` (or `switch (s[i])`) whose case tests are all + * one-unit string literals, as a switch on `s.charCodeAt(i)` with the units + * as cases: no character string is built. An index out of range answers + * NaN, which matches no case, as "" matches no one-unit literal. */ +function charAtUnitSwitch(s: IrStmt & { kind: "switch" }): (IrStmt & { kind: "switch" }) | null { + const disc = s.disc; + if (disc.kind !== "strIntrinsic" || disc.method !== "charAt" || disc.args.length !== 1) + return null; + if (!s.cases.some((c) => c.test !== null)) return null; + if ( + !s.cases.every( + (c) => c.test === null || (c.test.kind === "strLit" && c.test.value.length === 1), + ) + ) + return null; + return { + ...s, + disc: { ...disc, method: "charCodeAt", type: F64 }, + cases: s.cases.map((c) => + c.test?.kind === "strLit" + ? { + ...c, + test: { kind: "numLit", value: c.test.value.charCodeAt(0), type: F64, loc: c.test.loc }, + } + : c, + ), + }; +} diff --git a/packages/compiler/src/backend/llvm/emitter.ts b/packages/compiler/src/backend/llvm/emitter.ts index e86b447d9..3c0ab2378 100644 --- a/packages/compiler/src/backend/llvm/emitter.ts +++ b/packages/compiler/src/backend/llvm/emitter.ts @@ -1,9 +1,8 @@ +import { wasmBoundsChecks } from "./wasm-modules.js"; import { jsTruthy } from "./jsval.js"; -import { type ArraySearchKind, emitArraySearchHelpers } from "./array-search.js"; +import { type ArraySearchKind } from "./array-search.js"; import { emitStringInputs } from "./string-lifetimes.js"; -import { emitLlvmLayouts } from "./layouts.js"; -import { emitThreadSlotsIntrinsic, threadSlotsBlockDefinitions } from "./thread-slots.js"; -import { compileRegexMatcher, type RegexMatcherCode } from "./regex-matchers.js"; +import { emitThreadSlotsIntrinsic } from "./thread-slots.js"; import { CYC_BUF, CYC_HDR, @@ -13,18 +12,16 @@ import { WEAK_KEYS_GATE, } from "./alloc.js"; import { youngNurseryRequested } from "../young-nursery.js"; -import { hasLoneSurrogate, llvmBytes as llBytes, wtf8Bytes } from "../literals.js"; +import { llvmBytes as llBytes, wtf8Bytes } from "../literals.js"; import { InternalCompilerError } from "../../errors.js"; import { createHash } from "node:crypto"; import { endsWithJump, matchFieldSelfConcat, - matchStringSelfConcat, readsArraySlot, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js"; -import { emitLibraryIdentityLines } from "../library-identity-markers.js"; import { loopTestsEveryIteration } from "../loop-polls.js"; import type { IrBytesElem, @@ -42,10 +39,7 @@ import type { SrcLoc, } from "../../ir/ir.js"; import { - CAUGHT, - ffiCallbackType, isDynTypedRefType, - isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, @@ -56,77 +50,41 @@ import { RUNTIME_STREAM_CLASSES, typeEquals, typeKey, - STRING, VOID, classJsName, } from "../../ir/ir.js"; -import { moduleRuntimeFeatures, moduleUsesMockTimers } from "../../ir/runtime-features.js"; +import { + moduleRuntimeFeatures, + moduleUsesDynBuiltinStream, + moduleUsesMockTimers, +} from "../../ir/runtime-features.js"; import { captureFreeFunctions, observesStacks } from "../stack-observations.js"; +import { type DenseArrayLoop } from "./dense-array-loops.js"; +import { findInitializedByteLoopBindings } from "./byte-windows.js"; +import { matchBytesLoopVersion } from "./bytes-loop-versions.js"; +import { globalSlotAlias } from "./bytes-alias.js"; +import type { IntegerCountedForLoop } from "../../ir/integer-loops.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, - emitSpeculativeInteger, - planSpeculativeLoop, - speculativeReads, - speculativeWriteTarget, - SPECULATIVE_RANGE, -} from "./speculative-integers.js"; -import { emitLiteralSwitch } from "./switch-dispatch.js"; -import { findBytesBounds } from "./bytes-bounds.js"; -import { - emitByteWindowGuard, - findInitializedByteLoopBindings, - matchByteWindow, -} from "./byte-windows.js"; -import { - canDeferSplitPiece, - emitSplitSpanNext, materializeSplitPiece, type SplitSpan, - emitSplitCursor, - emitSplitNext, - emitSplitScratch, - emitSplitSnapshot, - findPrivateSplitLocals, - loadSplitSnapshot, - storeSplitSnapshot, - stringSplit, type StoredSplitSnapshot, type StringSplit, } from "./split-loops.js"; import { scalarizeNumericRecords } from "../../ir/scalar-records.js"; -import { RESULT_DST_ID, RESULT_ENTRY_PREFIX, addRecordResultEntries } from "./record-results.js"; +import { addRecordResultEntries } from "./record-results.js"; import { specializeCallbackCalls } from "../../ir/callback-specialization.js"; import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js"; import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js"; -import { - analyzeIntegerRanges, - INT32_RANGE, - type IntegerRange, - type IntegerRanges, -} from "../../ir/integer-ranges.js"; +import { INT32_RANGE, type IntegerRange, type IntegerRanges } from "../../ir/integer-ranges.js"; import { unshareNodes } from "../../ir/unshare.js"; import { copyStableCaptures } from "../../ir/capture-values.js"; import { resolveSelfCaptures } from "../../ir/self-captures.js"; import { analyzeInt32Slots, dynamicFieldStores, type Int32Slots } from "../../ir/int32-slots.js"; -import { analyzeFinalFields, constructorClass, type FinalFields } from "../../ir/final-fields.js"; +import { analyzeFinalFields, type FinalFields } from "../../ir/final-fields.js"; import { foldConstantGlobals } from "../../ir/constant-globals.js"; import { dropUnwrittenPropertyBags } from "../../ir/property-bags.js"; import { freezeInitOnlyGlobals } from "../../ir/init-only-globals.js"; import { hoistFinalFieldReads } from "../../ir/field-values.js"; -import { findIntegerViews } from "./integer-views.js"; import { findInt32Abis, INT32_ABI_SUFFIX, @@ -134,22 +92,10 @@ import { SCALAR_UNION_RESULT, type Int32Abi, } from "./int32-abi.js"; -import { findInt32Locals, type Int32Local } from "./int32-locals.js"; -import { - emitIntegerClone, - emitIntegerDispatcher, - findIntegerClones, - NO_INTEGER_CLONES, - type IntegerClones, -} from "./integer-clones.js"; +import { type Int32Local } from "./int32-locals.js"; +import { findIntegerClones, NO_INTEGER_CLONES, type IntegerClones } from "./integer-clones.js"; import { exactInteger, narrowInteger } from "./integer-values.js"; -import { - conditionalLoopWrites, - findInitializerBindings, - initializerLocalCandidates, - withInitializerBindings, - withInitializerLocals, -} from "../../ir/initializer-bindings.js"; +import { findInitializerBindings } from "../../ir/initializer-bindings.js"; import { findConstantNumericTables, type ConstantNumericTable } from "../../ir/constant-tables.js"; import { allocateFfiCallbackAdapters, @@ -157,47 +103,28 @@ import { hasRetainedFfiCallback, type FfiCallbackAdapter, } from "../ffi-callbacks.js"; -import { RUNTIME_ABI_MARKER } from "../runtime-abi.js"; import { computeMayThrow } from "../may-throw.js"; import { - mangleArgPack, - mangleAsyncAdoptCall, - mangleAsyncAwaitCall, mangleAsyncInlineAwaitCall, mangleAsyncSpawn, mangleBorrowedFunction, mangleClassObj, - mangleClassStruct, mangleFnClosure, mangleFunction, - mangleGenDrop, - mangleGenSourceClose, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordStruct, - mangleTrampoline, mangleWrapper, mangleBorrowingWrapper, - mangleVtInstance, } from "../mangle.js"; import { analyzeCallLifetimes, type CallLifetimes } from "./call-lifetimes.js"; -import { canStackUnion, emitStackUnion, findLocalStackUnions } from "./stack-unions.js"; import { - emitStackObject, - findLocalStackObjects, - findReassignedStackObjects, - stackObjectFields, stackObjectStruct, type StackObjectField, type StackObjectSource, } from "./stack-objects.js"; import { - allocateLocalUnion, - emitScalarOptional, - findLocalUnionStorage, - isScalarOptionalSource, - storeLocalUnion, type LocalUnionStorageProof, type LocalUnionStorage, type UnionSources, @@ -212,63 +139,30 @@ import { storeInlineUnion, unionBoxBits, } from "./inline-unions.js"; -import { - emitLocalArrayRead, - emitProjectedArrayRead, - emitBorrowedArrayRead, - findLocalArrayReads, - findCallArrayReads, - OptionalArrayReads, - type LocalArrayRead, -} from "./local-array-reads.js"; -import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js"; -import { - emitDenseArrayGet, - emitDenseArrayPop, - emitDenseArraySet, - emitDenseSlotUnlink, -} from "./dense-array-access.js"; -import { - emitStackMapRead, - findMapReadLifetimes, - findReadNumberMapWrites, - matchMapRead, - type MapReadLifetimes, -} from "./map-read-lifetimes.js"; -import { findMapEntryFusions, type MapEntryFusion } from "./map-entry-fusion.js"; +import { OptionalArrayReads, type LocalArrayRead } from "./local-array-reads.js"; +import { CheckedNarrows } from "./checked-narrows.js"; +import { emitDenseArraySet, emitDenseSlotUnlink } from "./dense-array-access.js"; +import { matchMapRead, type MapReadLifetimes } from "./map-read-lifetimes.js"; +import { type MapEntryFusion } from "./map-entry-fusion.js"; import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js"; -import { borrowableInputs, emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js"; -import { analyzeWalks, findWalkBorrows, type WalkBorrowHost } from "./walk-borrows.js"; +import { emitBorrowedInputs } from "./borrowed-inputs.js"; +import { findWalkBorrows, type WalkBorrowHost } from "./walk-borrows.js"; import { lastUseRead } from "./local-extents.js"; import { findReboundParameters } from "./rebound-parameters.js"; import { ReferenceEffects } from "./reference-effects.js"; import { ReferenceWrites, type Writes } from "./reference-writes.js"; import { LlvmDebugInfo } from "./debug-info.js"; import { StackCallbacks } from "./stack-callbacks.js"; -import { dynHoleRead, findLoopArrayBorrows } from "./loop-array-borrows.js"; 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"; import { StackCaptures } from "./stack-captures.js"; -import { - ABSENT_FIELD_PAYLOAD, - emitFieldAbsentTest, - exactIndexLines, - f64Lit, - ffiNativeTypeLl, - ffiNativeParamLl, - ffiNativeReturnLl, - llvmCommentText, - sizeToDouble, -} from "./common.js"; +import { emitFieldAbsentTest, f64Lit } from "./common.js"; import { ffiExtendsNarrowIntegers } from "../targets.js"; import { emitLiteralExpr, @@ -278,7 +172,7 @@ import { emitRecordExpr, } from "./expr-primitives.js"; import { emitControlExpr } from "./expr-control.js"; -import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js"; +import { emitCallExpr } from "./expr-calls.js"; import { emitDynamicExpr } from "./expr-dynamic.js"; import { emitIntrinsicExpr, emitSerializationExpr, emitAsyncExpr } from "./expr-async.js"; import { PublishWalkers } from "./publish.js"; @@ -292,6 +186,7 @@ import { genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, + childDataDynThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, @@ -321,7 +216,6 @@ import { emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, - emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, @@ -371,8 +265,6 @@ import { import { buildClassGraph, classMembershipIntervals, - emitClassMembershipHelper, - classEnvironmentIndex, classFieldIndex, classStructSym, programLifetimeClass, @@ -385,13 +277,6 @@ import { LlvmUnsupportedError } from "./unsupported.js"; import { LlWalkers } from "./walkers.js"; import { isObjectArm, NULLABLE_ABSENT, NULLABLE_NULL, NullableUnions } from "./nullable-unions.js"; import { emitNullablePresent, emitUnionPeek, emitUnionTag } from "./union-repr.js"; -import { literalCapWord, literalCapWord32 } from "./string-key-hash.js"; -import { - MAP_ITER_DECL, - NUMBER_MAP_ENTRY_DECL, - STRING_MAP_ENTRY_DECL, -} from "./map-number-lookup.js"; -import { STRING_READ_HELPERS, stringScanDecl } from "./string-reads.js"; import { emitConcatInputs, emitMixedConcat, @@ -404,30 +289,60 @@ import { boxNewCall, boxReleaseSym, computeTraced, - elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, emitShapeUnionHelpers, FN_ATTRS, inlineRcDecls, type InlineRcMode, - limitInlineDynReleases, llFieldType, mapFlagsOffset, type MapKeyAccess, releaseSym, retainSym, traceArg, - typedRefConstructor, vAdapters, } from "./shapes.js"; import type { ExprOf, LibCallExpr, LlStreamTypedRefAdapter, LlValue } from "./expr-context.js"; -import { INLINE_LITERAL_BYTES, literalEqualityDefinition } from "./string-literal-equality.js"; -import { emitDowncastGuard, emitUnionNarrowGuard } from "./narrow-guards.js"; +import { INLINE_LITERAL_BYTES } from "./string-literal-equality.js"; +import { + emitFfiCallbackDefs, + emitLibDefs, + emitModuleParts, + emitParts, + helperDefs, +} from "./emit-module.js"; +import { + emitLoopCopies, + emitSpeculativeWrite, + emitStmtBody, + emitStringSelfConcatAssign, + emitSwitch, + emitTryCatch, +} from "./emit-statements.js"; +import { + emitArgPackAndTrampolinePrologue, + emitAsyncAwaitCall, + emitAsyncScaffolding, + emitFunction, + emitGenScaffolding, + emitWasiFulfill, + fiberlessAsyncFunctions, + parkingFunctions, +} from "./emit-function.js"; +import { + analyzeBorrowedReturns, + canBorrowCallArgument, + canBorrowReceiver, + canStackReceiver, + emitReadReceiver, + fieldAliasMetadata, + isStackUnionSource, +} from "./receivers.js"; export { LlvmUnsupportedError } from "./unsupported.js"; -interface LlArgPackAndTrampolinePrologue { +export interface LlArgPackAndTrampolinePrologue { definitions: string[]; pack: string; lifted: boolean; @@ -438,7 +353,7 @@ interface LlArgPackAndTrampolinePrologue { argPackLines: string[]; } -function ffiCallbackDummyLl(callback: IrFfiCallbackParam["callback"]): string { +export function ffiCallbackDummyLl(callback: IrFfiCallbackParam["callback"]): string { switch (callback.returns) { case "i64": case "u64": @@ -464,7 +379,7 @@ function ffiCallbackDummyLl(callback: IrFfiCallbackParam["callback"]): string { } } -interface LlScopeEntry { +export interface LlScopeEntry { slot: string; type: IrType; boxed?: boolean; @@ -504,6 +419,9 @@ export interface LlvmTargetOptions { * teardown after `process.exit` to a helper process (`--detached-exit`). * Executables only; the runtime's weak default keeps it off. */ detachedExit?: boolean; + /** Translated WebAssembly memories check bounds explicitly instead of + * relying on guard pages (wasm-modules.ts wasmBoundsChecks). */ + wasmBoundsChecks?: boolean; } export function emitLlvmModule(input: IrModule, options: LlvmTargetOptions = {}): string { @@ -568,7 +486,7 @@ export function emitLlvmModuleSource( return parts.flatMap((part, index) => (index === 0 ? [part] : ["\n", part])); } -function llStrBytes(text: string): string { +export function llStrBytes(text: string): string { return llBytes(wtf8Bytes(text)); } @@ -576,7 +494,7 @@ const NO_BORROWED: ReadonlySet = new Set(); /** Runtime entry points (and await-call entries) that may park the running * execution context, as they appear in emitted function text. */ -const SUSPENDS = +export const SUSPENDS = /@(?:scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_)|sc_a[wd]_)/; /** The suspensions of a fiber body's own awaits, which its stackless * coroutine form suspends itself at instead. */ @@ -587,7 +505,7 @@ const COROUTINE_SUSPENDS = /@(?:scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_(?!self\b|promise\b))|sc_a[wd]_)/; /** Captured bindings (boxes a body declares or receives) from which a * function compiles without LLVM optimization (see boxSlotCount). */ -const UNOPTIMIZED_BOX_SLOTS = 512; +export const UNOPTIMIZED_BOX_SLOTS = 512; /** ScrExcKind tags of a generator slot holding a number or a boolean. */ const SCR_EXC_F64 = 1; const SCR_EXC_BOOL = 2; @@ -605,7 +523,7 @@ const SCR_EXC_BOOL = 2; * optnone bodies, as the native helper's does. Each use reloads its box * pointer and code generation stays linear. Semantics, boxes and * environments are unchanged; only that body's machine code is slower. */ -function boxSlotCount(fn: IrFunction): number { +export function boxSlotCount(fn: IrFunction): number { const captured = new Set(); for (const capture of fn.captures ?? []) captured.add(capture.localId); for (const capture of fn.classCaptures ?? []) captured.add(capture.localId); @@ -616,6 +534,9 @@ function boxSlotCount(fn: IrFunction): number { /** Whether any node of `e` names the local (reads, writes, declarations, * captures). */ +/** Typed-array loop versions nest at most this deep (each doubles a body). */ +export const MAX_BYTES_LOOP_VERSION_DEPTH = 3; + function mentionsLocal(e: IrExpr, localId: string): boolean { const exprNames = (node: IrExpr): boolean => { if (node.kind === "varRef" || node.kind === "incDec" || node.kind === "assignExpr") @@ -649,26 +570,26 @@ function mentionsLocal(e: IrExpr, localId: string): boolean { export class LlEmitter { localArrayReads = new Map(); - private loopArrayBorrows: ReadonlySet = new Set(); - private scalarStringSlices = new Map(); + loopArrayBorrows: ReadonlySet = new Set(); + 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(); + lentStringParameters: LentStringParameters = new Map(); + localStackUnions = new Map(); + localStackObjects = new Map(); + reassignedStackObjects = new Set(); /** The binding slot of each reassigned stack-object local. */ - private stackObjectSlots = new Map(); + stackObjectSlots = new Map(); /** Entry-block pointers to the reference fields of each frame object * slot (stack-objects.ts stackObjectFields). */ stackObjectFieldPtrs = new Map(); - private localUnionStorageProofs = new Map(); - private localUnionStorage = new Map(); + localUnionStorageProofs = new Map(); + localUnionStorage = new Map(); integerArrayBindings = new Set(); - private readonly fieldAliasTags = new Map(); - private readonly fieldPointerTags = new Map(); + readonly fieldAliasTags = new Map(); + readonly fieldPointerTags = new Map(); /** Native class fields with different property names cannot occupy the * same slot of one object. Inherited views of the same property share a @@ -726,22 +647,13 @@ export class LlEmitter { const tag = this.fieldPointerTags.get(ptr); return tag === undefined ? "" : `, !tbaa !${tag}`; } - - private fieldAliasMetadata(): string[] { - if (this.fieldAliasTags.size === 0) return []; - const lines = ['!0 = !{!"scriptc native fields"}']; - for (const tag of this.fieldAliasTags.values()) { - lines.push( - `!${tag - 1} = !{!"field.${tag}", !0, i64 0}`, - `!${tag} = !{!${tag - 1}, !${tag - 1}, i64 0}`, - ); - } - return lines; + fieldAliasMetadata(): string[] { + return fieldAliasMetadata(this); } - private readonly debug: LlvmDebugInfo | null; + readonly debug: LlvmDebugInfo | null; /** Projection-only scalar objects may live in frame storage (stack-objects.ts). */ readonly stackObjects: boolean; - private debugScope: string | null = null; + debugScope: string | null = null; readonly sizeType: "i32" | "i64"; readonly ffiExtendNarrowIntegers: boolean; readonly cycleBufOffset: number; @@ -760,48 +672,49 @@ export class LlEmitter { * generators' conditions; library and Windows builds keep fibers. */ private stacklessAsyncAllowed = false; /** The async functions selectStacklessAsync lowered as coroutines. */ - private readonly stacklessAsync = new Set(); + readonly stacklessAsync = new Set(); /** Some function in the module is lowered as a coroutine. */ - private usesCoroutines = false; + usesCoroutines = false; /** The target lacks a rounding instruction at its baseline CPU (x86-64 * before SSE4.1), so `llvm.trunc` would lower to a libm call: emit * truncation inline through an integer round trip instead. */ readonly inlineTrunc: boolean; /** ELF worker executables give thread-locals the executable TLS models. */ - private readonly executableTls: boolean; + readonly executableTls: boolean; /** Source loops enclosing the statement being emitted in the current * function body. */ - private loopDepth = 0; + loopDepth = 0; /** Darwin worker executables read the stack guard from a pthread * thread-specific-data slot off the thread pointer (scr_runtime.h's * SCR_STACK_GUARD_TSD): the architecture names the access sequence. */ private readonly stackGuardTsd: "aarch64" | "x86_64" | null; - private readonly emitLibraryIdentity: boolean; - private readonly runtimeAbiMarker: boolean; - private readonly detachedExit: boolean; + readonly emitLibraryIdentity: boolean; + readonly runtimeAbiMarker: boolean; + readonly detachedExit: boolean; + readonly wasmBoundsChecks: boolean; /** Interned string literals: UTF-8 text → { symbol, byte length } — * first-use order, the runtime ABI’s determinism discipline. */ - private readonly literals = new Map(); + readonly literals = new Map(); /** Literal equality helpers: literal text → helper symbol (literalEquals). */ - private readonly literalEqualities = new Map(); + readonly literalEqualities = new Map(); /** Interned bigint literals: hex magnitude with sign → { symbol, value }. */ - private readonly bigintLiterals = new Map(); + readonly bigintLiterals = new Map(); /** Symbols of interned constant data, named by content (contentSymbol). */ private readonly contentSymbols = new Set(); /** Interned unit-armed union instances: "unionId:tag" → symbol — one * immortal (rc == SIZE_MAX) static per (union, unit tag). * RC entry points and the collector skip immortals. */ - private readonly unitInstances = new Map(); + readonly unitInstances = new Map(); /** unionId → symbol of the union's immortal ABSENT field-slot instance. */ - private readonly absentInstances = new Map(); + readonly absentInstances = new Map(); /** The zeroed box an inline store reads for a NULL union dummy. */ - private needsUnionNone = false; + needsUnionNone = false; /** unionId → symbol of a flag set once a checked-dynamic cast caches one * of the union's boxes. A cached box is refreshed in place, possibly with * a tag that another union numbers apart, so it is never shared. */ - private readonly retypeGuards = new Map(); + readonly retypeGuards = new Map(); /** The nullable-union ABSENT sentinel was referenced (absentInstanceRef). */ - private needsNullableAbsent = false; + needsNullableAbsent = false; private readonly immortalValues = new Set(); /** Regex literal templates: "/" → { symbol, interned * source/flags literal refs } — one immortal ScrRegex template per distinct @@ -819,9 +732,9 @@ export class LlEmitter { readonly templateStringsInstances = new Map(); /** Interned NUL-terminated C-string constants (scr_jb_puts labels, the * stringify indent text): UTF-8 text → { symbol, byte length }. */ - private readonly cstrs = new Map(); + readonly cstrs = new Map(); /** Interned ScrMapOps descriptors (scr_map.h): initializer → symbol. */ - private readonly mapOpsConsts = new Map(); + readonly mapOpsConsts = new Map(); /** Type-directed walker functions (JSON serializers, the indent * rewriter, union ToString/join) — interned per typeKey/unionId, defs * flushed with the shape helpers. */ @@ -832,17 +745,17 @@ export class LlEmitter { /** Explicit callback boundary for standalone layout and walker stages. */ readonly shapeHost: DynHost; /** External declarations, in first-use order. */ - private readonly decls = new Set(); + readonly decls = new Set(); /** Declared functions referenced as values: each needs an env-signature * wrapper + an interned immortal closure (so `f === f` holds). */ readonly fnValues = new Set(); - private needsOom = false; + needsOom = false; needsBadTag = false; needsBadKey = false; - private needsRetainBox = false; + needsRetainBox = false; /** Inline RC fast-path helpers requested by call sites (shapes.ts); null * when inline RC is off. */ - private readonly rcHelpers: Set | null; + readonly rcHelpers: Set | null; private readonly rcMode: InlineRcMode | undefined; private readonly rcHelpersNoinline: boolean; @@ -851,11 +764,11 @@ export class LlEmitter { appendedArrayLiterals: ReadonlySet = new Set(); /** Emitted LLVM text of every IR function body, by function name: the * fiberless-async analysis scans it for suspension entry points. */ - private readonly fnDefText = new Map(); + readonly fnDefText = new Map(); readonly referenceEffects: ReferenceEffects; /** Field-sensitive reference writes (null in debug builds). */ referenceWrites: ReferenceWrites | null = null; - private readonly borrowedReturnKeyCache = new Map(); + readonly borrowedReturnKeyCache = new Map(); /** The statement list being emitted and the current index, for blocks * whose bindings end with the list (emitBlock and function bodies). */ private stmtCursor: { stmts: IrStmt[]; index: number } | null = null; @@ -873,63 +786,63 @@ export class LlEmitter { currentConstantCallbacks: ReadonlyMap = new Map(); readonly stackCallbacks: StackCallbacks; readonly receiverReaders: AmbientReceiverReaders; - private readonly stackCaptures: StackCaptures; - private readonly lazyCaptures: LazyCaptures; + readonly stackCaptures: StackCaptures; + readonly lazyCaptures: LazyCaptures; /** Unchanged captured parameters of the current function: each maps to * the slot caching its box, NULL until the first closure needs it. */ - private readonly lazyCaptureBoxes = new Map(); - private readonly lazyStackBoxes = new Map(); - private borrowedParameters = new Set(); + readonly lazyCaptureBoxes = new Map(); + readonly lazyStackBoxes = new Map(); + borrowedParameters = new Set(); /** Captures of the current closure body that its environment holds * unchanged for the closure's whole life (LazyCaptures.stableCaptures). */ - private stableCaptureIds: ReadonlySet = new Set(); + stableCaptureIds: ReadonlySet = new Set(); /** Parameters proven projection-only: callers may pass stack boxes. */ - private projectedParameters = new Set(); - private stableCallBindings: ReadonlySet = new Set(); + projectedParameters = new Set(); + stableCallBindings: ReadonlySet = new Set(); /** Locals and rebound parameters holding borrowed pointers for their * whole lifetime (walk-borrows.ts), per function and for the current one. */ - private readonly walkBorrowsByFunction = new Map>(); - private walkBorrows: ReadonlySet = new Set(); + readonly walkBorrowsByFunction = new Map>(); + walkBorrows: ReadonlySet = new Set(); /** Functions whose borrowing body returns its result without a reference * (analyzeBorrowedReturns), and whether the current body is one. */ readonly borrowedReturns = new Set(); /** Rebound parameters that keep the borrowed convention with an owner * slot (rebound-parameters.ts), per function, and the current owner slots. */ - private readonly reboundByFunction = new Map>(); - private readonly reboundOwnerSlots = new Map(); - private currentBorrowedReturn = false; + readonly reboundByFunction = new Map>(); + readonly reboundOwnerSlots = new Map(); + currentBorrowedReturn = false; /** Manifest-bound native imports, used by ffiCall emission. */ readonly ffiByName = new Map(); /** C-ABI callback trampolines and (for raw/no-userdata callbacks) their * distinct call-scoped TLS closure slots. */ - private readonly ffiCallbackAdapters: Map; + readonly ffiCallbackAdapters: Map; /** Module-level constant consulted per ffiCall: with a retained * descriptor anywhere in the manifest, every native call is a * pending-exception checkpoint (may-throw derives the same fact from * the same helper). */ readonly ffiHasRetainedCallback: boolean; - private readonly ffiHasForeignCallback: boolean; + readonly ffiHasForeignCallback: boolean; private readonly globalTypes = new Map(); /** Module constants that keep one value once initialized * (immutability.ts stableGlobals). */ - private stableGlobals: ReadonlySet = new Set(); + stableGlobals: ReadonlySet = new Set(); private readonly tdzGlobals = new Map(); readonly constantNumericTables: ReadonlyMap; /** Constant-key reads on checked-dynamic objects, each with its own * zero-initialized ScrDynReadIc (scr_runtime.h). */ - private dynReadIcSites = 0; + dynReadIcSites = 0; /** ThreadSlots blocks by family name (thread-slots.ts). */ - private readonly threadSlotsBlocks = new Map(); + readonly threadSlotsBlocks = new Map(); /** May-throw analysis (computeMayThrow): pending * checks are emitted only after calls that can actually raise. */ readonly mayThrow: Set; readonly indirectMayThrow: boolean; /** Worker programs: the functions that poll for termination on entry * (workerTerminationPolls); null outside worker programs. */ - private readonly workerEntryPolls: ReadonlySet | null; + readonly workerEntryPolls: ReadonlySet | null; /** Functions that check the native stack guard on entry * (stackCheckedFunctions); null where the runtime has no guard. */ - private readonly stackChecks: ReadonlySet | null; + readonly stackChecks: ReadonlySet | null; /** Static types whose values can be published (@scriptc/threads): their * stores carry the frozen-object guard. Null without publish calls. */ readonly publishedTypes: PublishedTypes | null; @@ -938,8 +851,8 @@ export class LlEmitter { private readonly immutableFields: ImmutableFields | null; /** Constructor function names: their bodies may still write their own * `this`'s construction-only fields. */ - private readonly constructorFunctions: ReadonlySet; - private inConstructor = false; + readonly constructorFunctions: ReadonlySet; + inConstructor = false; /** Method names with at least one may-throw implementation — the * virtualCall pending check's key. */ readonly mayThrowMethods = new Set(); @@ -993,6 +906,11 @@ export class LlEmitter { /** Identity-preserving static→dyn capsules for program classes and Web * APIs whose values remain directly observable. */ readonly liveDynRefAdapters = new Map(); + private dynBuiltinStreamUse: boolean | undefined; + /** See moduleUsesDynBuiltinStream. */ + get usesDynBuiltinStream(): boolean { + return (this.dynBuiltinStreamUse ??= moduleUsesDynBuiltinStream(this.mod)); + } /** Runtime-arm dispatchers for live union values. */ readonly liveDynUnionRefAdapters = new Map(); readonly dynPromiseAdapters = new Map(); @@ -1015,30 +933,30 @@ export class LlEmitter { /** Class objects (classes as first-class values): className → the * interned .name literal ref — registered during body emission, the * statics and construct thunks assemble around the bodies. */ - private readonly classObjs = new Map(); + readonly classObjs = new Map(); private readonly unionFieldReadGroups = new Map(); /** Preorder intervals of the runtime error classes under THIS module's * class-forest numbering (main() stamps scr_error_vts with them, exactly * like the runtime ABI’s errorVtStampLines). */ - private readonly errorIntervals: { kind: number; pre: number; post: number; lib: string }[] = []; + readonly errorIntervals: { kind: number; pre: number; post: number; lib: string }[] = []; /** The runtime emitter vtable's preorder interval, when the program * touches node:events (the class def rides the module exactly then) — * main() stamps scr_emitter_vt with it (emitterVtStampLines, ported). */ - private emitterInterval: { pre: number; post: number } | null = null; + emitterInterval: { pre: number; post: number } | null = null; /** The runtime stream vtables' preorder intervals (streamVtStampLines, * ported): the defs ride every emitter-touching module (the frontend * collects the whole emitter-rooted tree), and scr_stream.c links on * the same predicate, so the stamps always have their globals. */ - private readonly streamIntervals: { vt: string; pre: number; post: number; lib: string }[] = []; + readonly streamIntervals: { vt: string; pre: number; post: number; lib: string }[] = []; // ── per-function state (reset in emitFunction) ───────────────────────── B = new BlockBuilder(); frames: LlValue[][] = []; - private scopes: LlScopeEntry[][] = []; + scopes: LlScopeEntry[][] = []; /** Scope slots dominate every throw site. Share their exceptional cleanup * for identical live bindings and handlers; statement temporaries stay at * their own throw sites because their SSA values are path-dependent. */ - private readonly unwindCleanups = new Map< + readonly unwindCleanups = new Map< string, { label: string; entries: LlScopeEntry[]; terminator: string } >(); @@ -1049,7 +967,7 @@ export class LlEmitter { * continue binds to the innermost LOOP; a labeled jump binds to the * entry whose `labels` contains its label. `contLabel` is null exactly * for blocks and switches. */ - private jumpTargets: { + jumpTargets: { kind: "loop" | "block" | "switch"; brkLabel: string; contLabel: string | null; @@ -1058,12 +976,12 @@ export class LlEmitter { scopeDepth: number; finallyDepth: number; }[] = []; - private currentLocals = new Map(); - private privateSplitLocals = new Map(); + currentLocals = new Map(); + privateSplitLocals = new Map(); readonly splitSpans = new Map(); - private storedSplits = new Map(); - private streamingSplitsEnabled = false; - private readonly initializerBindings: ReturnType; + storedSplits = new Map(); + streamingSplitsEnabled = false; + readonly initializerBindings: ReturnType; /** Whole-program int32 storage and parameter/result facts. */ readonly int32Slots: Int32Slots; /** The program has no runtime store into a checked-dynamic value @@ -1087,56 +1005,67 @@ export class LlEmitter { /** ProgramLifetime hierarchy roots (contract types): their instances are * immortal from construction (classes.ts programLifetimeClass). */ readonly programLifetime: ReadonlySet; - private numericLocals = new Map(); + 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(); + unaliasedArrays: ReadonlySet = new Set(); /** Dense array loops (dense-array-loops.ts) run in this function. */ - private denseLoopsEnabled = false; - private denseLoopEntries: ReturnType = new Map(); + denseLoopsEnabled = false; + denseLoopEntries: ReturnType = new Map(); /** The fast copy of a dense array loop being emitted. */ - private denseLoop: DenseArrayLoop | null = null; - private captureIds = new Set(); + denseLoop: DenseArrayLoop | null = null; + captureIds = new Set(); /** Active induction bindings retain their width independently of size_t. */ integerLoopBindings = new Map< string, { slot: string; type: "i32" | "i64"; signed?: boolean; range?: { min: number; max: number } } >(); - private countedLoopsEnabled = false; + countedLoopsEnabled = false; /** Speculative integer loops (speculative-integers.ts) run in this function. */ - private speculativeLoopsEnabled = false; + speculativeLoopsEnabled = false; /** The fast copy of a speculative integer loop being emitted: its * integer-computed writes and where each resumes in the ordinary copy. */ - private speculation: { + speculation: { writes: Set; resume: Map; /** Globals whose double is written back only when the copy is left. */ deferred: Set; } | null = null; /** The ordinary copy being emitted: resume labels still to place. */ - private speculationResume: Map | null = null; + speculationResume: Map | null = null; /** The function being emitted, for speculative globals. */ - private speculativeFunction = ""; + speculativeFunction = ""; /** Every function that writes each number global (computed on demand). */ private globalWriters: Map> | null = null; /** Whole-program final reference fields (ir/final-fields.ts). */ - private finalFields!: FinalFields; + finalFields!: FinalFields; /** The current function may borrow final fields of borrowable objects: * not a constructor (where they change), not async or a generator. */ - private finalFieldBorrows = false; + finalFieldBorrows = false; integerRanges: IntegerRanges = new Map(); /** Number arrays of the current function whose every present element is * an int32 (Int32[] contracts), or null when the module has none. */ int32Elements: ((arr: IrExpr) => boolean) | null = null; /** Sums whose result only feeds 32-bit conversions (wrapping-sums.ts). */ wrappingSums = new Set(); - private byteWindowsEnabled = true; - private byteWindowEntries: ReturnType = new Map(); + byteWindowsEnabled = true; + byteWindowEntries: ReturnType = new Map(); + /** Typed-array loop versioning (bytes-loop-versions.ts) for the current + * function: off in debug and suspending functions and inside a copy. */ + bytesLoopVersionsEnabled = false; + /** Loops being versioned around the statement being emitted: a loop is + * never versioned inside its own copies, and versions nest boundedly. */ + readonly bytesLoopVersioning = new Set(); + bytesLoopEntries: ReturnType | null = null; + bytesLoopFunction: IrFunction | null = null; + /** Counted loops whose integer-form safety test an enclosing version + * guard already passed. */ + readonly provenCountedGuards = new Set(); bytesBounds: ReadonlySet = new Set(); integerViews = new Map(); /** Module globals the current function owns outright (initializer * bindings): their function-local slot, which stands in for the global. */ - private privateGlobalSlots = new Map(); + privateGlobalSlots = new Map(); /** Number locals of the current function stored only as an i32 * (int32-locals.ts); their slots are `alloca i32`. */ int32Locals = new Map(); @@ -1151,7 +1080,7 @@ export class LlEmitter { * (past its own catch), so the copies emit under the truncated stack. * break/continue use the same region snapshots through * emitFinallysForJump. */ - private finallyStack: { + finallyStack: { frameDepth: number; scopeDepth: number; tryDepth: number; @@ -1162,15 +1091,15 @@ export class LlEmitter { * releases frames/scopes down to the recorded depths and branches to * `label` (the catch, or the exception-path finally) instead of * returning out of the function. Entering a try emits no code. */ - private tryStack: { label: string; used: boolean; frameDepth: number; scopeDepth: number }[] = []; + tryStack: { label: string; used: boolean; frameDepth: number; scopeDepth: number }[] = []; /** Return type of the function being emitted — the unwind path returns * a dummy of this type (never read: callers check the flag first). */ currentReturnType: IrType = VOID; /** The current body returns its number result as i32. */ - private currentInt32Result = false; + currentInt32Result = false; /** The function's i32 entry returns its scalar union result as tag and * payload bits (Int32Abi.union). */ - private currentUnionResult = false; + currentUnionResult = false; /** Active only while emitting a body lowered with LLVM's * switched-coroutine intrinsics (wasm32 async bodies and generators, * native synchronous generators; see coroLowered). */ @@ -1195,13 +1124,13 @@ export class LlEmitter { * catch prologue emits the GENRET sentinel re-unwind exactly here. */ currentGenerator: { yieldT: IrType; nextT: IrType } | null = null; /** The function being emitted is an async generator body. */ - private currentAsyncGenerator = false; + currentAsyncGenerator = false; /** Active optional-chain bind slots, by chain id (chainRecv reads). */ readonly chainSlots = new Map(); logArgSlots = 0; - private readonly stackTraces: boolean; + readonly stackTraces: boolean; /** Stack-trace mode: functions no capture can include (no frame push). */ - private readonly unframedFunctions: ReadonlySet; + readonly unframedFunctions: ReadonlySet; constructor( readonly mod: IrModule, @@ -1261,6 +1190,13 @@ export class LlEmitter { this.emitLibraryIdentity = options.emitLibraryIdentity !== false; this.runtimeAbiMarker = options.runtimeAbiMarker === true; this.detachedExit = options.detachedExit === true; + this.wasmBoundsChecks = + options.wasmBoundsChecks === true || + wasmBoundsChecks({ + sanitize: false, + pointerBits: options.pointerBits ?? 64, + targetTriple: options.targetTriple ?? "", + }); this.rcHelpers = options.inlineRc !== undefined ? new Set() : null; this.rcMode = options.inlineRc; this.rcHelpersNoinline = options.rcHelpersNoinline === true; @@ -1478,6 +1414,7 @@ export class LlEmitter { fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId), absentInstanceRef: (unionId) => this.absentInstanceRef(unionId), liveDynRefAdapter: (type) => this.liveDynRefAdapter(type), + usesDynBuiltinStream: () => this.usesDynBuiltinStream, liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type), dynPromiseAdapter: (type) => this.dynPromiseAdapter(type), isErrorClass: (name) => this.classMeta.get(name)?.root.def.name === "%Error", @@ -1594,7 +1531,7 @@ export class LlEmitter { return this.virtualBorrows.entry(implFn); } - private implSlotBorrowed(implFn: string): ReadonlySet { + implSlotBorrowed(implFn: string): ReadonlySet { return this.virtualBorrows.implSlotBorrowed(implFn); } @@ -1631,398 +1568,22 @@ export class LlEmitter { throw new InternalCompilerError(`llvm emitter bug: no callback adapter for ${binding}:${id}`); return adapter; } - - /** C-callable scalar callback trampolines. A callback with an explicit - * context entry receives the closure at that exact ABI position. A raw - * callback loads it from a call-scoped TLS slot installed around the - * outer native call. */ - private emitFfiCallbackDefs(): { globals: string[]; defs: string[] } { - const globals: string[] = []; - const defs: string[] = []; - if (this.ffiCallbackAdapters.size === 0) return { globals, defs }; - this.declare(`declare void @scr_trap(ptr)`); - const expired = this.cstr( - "scriptc: native callback invoked outside its call-scoped lifetime\n", - ); - const released = this.cstr("scriptc: native callback invoked outside its retained lifetime\n"); - for (const adapter of this.ffiCallbackAdapters.values()) { - const cb = adapter.callback; - if (adapter.tls !== null) - globals.push(`@${adapter.tls} = internal thread_local global ptr null`); - if (adapter.global !== null) - globals.push(`@${adapter.global} = internal thread_local global ptr null`); - if (adapter.table !== null) { - globals.push( - `@${adapter.table} = internal ${this.mod.workers ? "thread_local " : ""}global %ScrFfiTable zeroinitializer`, - ); - } - const params = cb.params.flatMap((param, i): string[] => { - if (isFfiContextParam(param)) return [`ptr %ctx`]; - if (param === "string" || param === "bytes") { - return [`ptr %a${i}`, `${this.sizeType} %a${i}_len`]; - } - return [`${ffiNativeParamLl(param, this.ffiExtendNarrowIntegers)} %a${i}`]; - }); - if (cb.invoke === "foreign") { - if ( - adapter.table === null || - !cb.params.some(isFfiContextParam) || - cb.returns !== "void" || - cb.params.some((param) => param === "i64" || param === "u64" || param === "pointer") - ) { - throw new InternalCompilerError( - "llvm emitter bug: invalid foreign FFI callback descriptor", - ); - } - const dispatch = `${adapter.symbol}_dispatch`; - const scriptArgs: string[] = []; - const dispatchBody: string[] = [ - `define internal void @${dispatch}(ptr %cb, ptr %call) ${FN_ATTRS} {`, - `entry:`, - ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, - ` %fn = load ptr, ptr %fnp`, - ]; - for (let i = 0; i < cb.params.length; i++) { - const param = cb.params[i]!; - if (isFfiContextParam(param)) continue; - switch (param) { - case "f64": - case "f32": - case "i8": - case "u16": - case "i16": - this.declare(`declare double @scr_ffi_call_get_f64(ptr, ${this.sizeType})`); - dispatchBody.push( - ` %s${i} = call double @scr_ffi_call_get_f64(ptr %call, ${this.sizeType} ${i})`, - ); - scriptArgs.push(`double %s${i}`); - break; - case "bool": - this.declare(`declare zeroext i1 @scr_ffi_call_get_bool(ptr, ${this.sizeType})`); - dispatchBody.push( - ` %s${i} = call zeroext i1 @scr_ffi_call_get_bool(ptr %call, ${this.sizeType} ${i})`, - ); - scriptArgs.push(`i1 %s${i}`); - break; - case "u8": - case "u32": - case "i32": - this.declare(`declare double @scr_ffi_call_get_${param}(ptr, ${this.sizeType})`); - dispatchBody.push( - ` %s${i} = call double @scr_ffi_call_get_${param}(ptr %call, ${this.sizeType} ${i})`, - ); - scriptArgs.push(`double %s${i}`); - break; - case "cstring": - case "string": - case "bytes": { - this.declare(`declare ptr @scr_ffi_call_get_data(ptr, ${this.sizeType})`); - this.declare(`declare ${this.sizeType} @scr_ffi_call_get_len(ptr, ${this.sizeType})`); - dispatchBody.push( - ` %data${i} = call ptr @scr_ffi_call_get_data(ptr %call, ${this.sizeType} ${i})`, - ` %len${i} = call ${this.sizeType} @scr_ffi_call_get_len(ptr %call, ${this.sizeType} ${i})`, - ); - if (param === "bytes") { - this.declare(`declare ptr @scr_bytes_from_data(ptr, ${this.sizeType})`); - dispatchBody.push( - ` %s${i} = call ptr @scr_bytes_from_data(ptr %data${i}, ${this.sizeType} %len${i})`, - ); - } else { - this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`); - dispatchBody.push( - ` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %data${i}, ${this.sizeType} %len${i})`, - ); - } - scriptArgs.push(`ptr %s${i}`); - break; - } - } - } - dispatchBody.push( - ` call void %fn(${[`ptr %cb`, ...scriptArgs].join(", ")})`, - ` ret void`, - `}`, - ``, - ); - for (const line of dispatchBody) defs.push(line); - - this.declare(`declare ptr @scr_ffi_call_new(ptr, ptr, ptr, ${this.sizeType})`); - this.declare(`declare void @scr_ffi_post(ptr)`); - defs.push( - `define internal void @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, - `entry:`, - ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, - ` %call = call ptr @scr_ffi_call_new(ptr @${adapter.table}, ptr %cb, ptr @${dispatch}, ${this.sizeType} ${cb.params.length})`, - ); - for (let i = 0; i < cb.params.length; i++) { - const param = cb.params[i]!; - if (isFfiContextParam(param)) continue; - if (param === "cstring") { - this.declare(`declare void @scr_ffi_call_copy_cstring(ptr, ${this.sizeType}, ptr)`); - defs.push( - ` call void @scr_ffi_call_copy_cstring(ptr %call, ${this.sizeType} ${i}, ptr %a${i})`, - ); - } else if (param === "string" || param === "bytes") { - this.declare( - `declare void @scr_ffi_call_copy_${param}(ptr, ${this.sizeType}, ptr, ${this.sizeType})`, - ); - defs.push( - ` call void @scr_ffi_call_copy_${param}(ptr %call, ${this.sizeType} ${i}, ptr %a${i}, ${this.sizeType} %a${i}_len)`, - ); - } else if (param === "f32" || param === "i8" || param === "u16" || param === "i16") { - const nativeTy = ffiNativeTypeLl(param); - const op = param === "f32" ? "fpext" : param === "u16" ? "uitofp" : "sitofp"; - this.declare(`declare void @scr_ffi_call_set_f64(ptr, ${this.sizeType}, double)`); - defs.push( - ` %wide${i} = ${op} ${nativeTy} %a${i} to double`, - ` call void @scr_ffi_call_set_f64(ptr %call, ${this.sizeType} ${i}, double %wide${i})`, - ); - } else { - const nativeTy = ffiNativeTypeLl(param); - this.declare( - `declare void @scr_ffi_call_set_${param}(ptr, ${this.sizeType}, ${nativeTy})`, - ); - defs.push( - ` call void @scr_ffi_call_set_${param}(ptr %call, ${this.sizeType} ${i}, ${nativeTy} %a${i})`, - ); - } - } - defs.push(` call void @scr_ffi_post(ptr %call)`, ` ret void`, `}`, ``); - continue; - } - defs.push( - `define internal ${ffiNativeReturnLl(cb.returns, this.ffiExtendNarrowIntegers)} @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, - `entry:`, - adapter.tls !== null - ? ` %cb = load ptr, ptr @${adapter.tls}` - : adapter.global !== null - ? ` %cb = load ptr, ptr @${adapter.global}` - : ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, - ` %missing = icmp eq ptr %cb, null`, - ` br i1 %missing, label %expired, label %ready`, - `expired:`, - ` call void @scr_trap(ptr ${adapter.callback.lifetime === "call" ? expired : released})`, - ` unreachable`, - `ready:`, - ...this.pendingTestLines("%pending"), - ` br i1 %pending, label %skip, label %invoke`, - `skip:`, - ` ret ${ffiCallbackDummyLl(cb)}`, - `invoke:`, - ); - // Validate every native pointer before allocating any copy-in value. - // A later bad slot therefore cannot leak an earlier materialization. - for (let i = 0; i < cb.params.length; i++) { - const param = cb.params[i]!; - if (param !== "cstring" && param !== "string" && param !== "bytes") continue; - const invalid = `%invalid${i}`; - defs.push(` %null${i} = icmp eq ptr %a${i}, null`); - if (param === "cstring") { - defs.push(` ${invalid} = or i1 %null${i}, false`); - } else { - defs.push( - ` %nonempty${i} = icmp ne ${this.sizeType} %a${i}_len, 0`, - ` ${invalid} = and i1 %null${i}, %nonempty${i}`, - ); - } - const message = - param === "cstring" - ? "scriptc: native callback passed a NULL cstring\n" - : `scriptc: native callback passed a NULL ${param} span with nonzero length\n`; - defs.push( - ` br i1 ${invalid}, label %invalid_param${i}, label %param_ok${i}`, - `invalid_param${i}:`, - ` call void @scr_trap(ptr ${this.cstr(message)})`, - ` unreachable`, - `param_ok${i}:`, - ); - } - defs.push( - ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, - ` %fn = load ptr, ptr %fnp`, - ); - const scriptArgs: string[] = []; - for (let i = 0; i < cb.params.length; i++) { - const param = cb.params[i]!; - if (isFfiContextParam(param)) continue; - switch (param) { - case "i64": - case "u64": - case "pointer": { - const ty = ffiNativeTypeLl(param); - this.declare(`declare ptr @scr_bigint_from_${param}(${ty})`); - defs.push(` %s${i} = call ptr @scr_bigint_from_${param}(${ty} %a${i})`); - scriptArgs.push(`ptr %s${i}`); - break; - } - case "f64": - scriptArgs.push(`double %a${i}`); - break; - case "f32": - case "i8": - case "u16": - case "i16": { - const nativeTy = ffiNativeTypeLl(param); - const op = param === "f32" ? "fpext" : param === "u16" ? "uitofp" : "sitofp"; - defs.push(` %s${i} = ${op} ${nativeTy} %a${i} to double`); - scriptArgs.push(`double %s${i}`); - break; - } - case "bool": - defs.push(` %s${i} = icmp ne i8 %a${i}, 0`); - scriptArgs.push(`i1 %s${i}`); - break; - case "u8": - defs.push(` %s${i} = uitofp i8 %a${i} to double`); - scriptArgs.push(`double %s${i}`); - break; - case "u32": - defs.push(` %s${i} = uitofp i32 %a${i} to double`); - scriptArgs.push(`double %s${i}`); - break; - case "i32": - defs.push(` %s${i} = sitofp i32 %a${i} to double`); - scriptArgs.push(`double %s${i}`); - break; - case "cstring": - this.declare(`declare ${this.sizeType} @strlen(ptr)`); - this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`); - defs.push( - ` %len${i} = call ${this.sizeType} @strlen(ptr %a${i})`, - ` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %a${i}, ${this.sizeType} %len${i})`, - ); - scriptArgs.push(`ptr %s${i}`); - break; - case "string": - this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`); - defs.push( - ` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %a${i}, ${this.sizeType} %a${i}_len)`, - ); - scriptArgs.push(`ptr %s${i}`); - break; - case "bytes": - this.declare(`declare ptr @scr_bytes_from_data(ptr, ${this.sizeType})`); - defs.push( - ` %s${i} = call ptr @scr_bytes_from_data(ptr %a${i}, ${this.sizeType} %a${i}_len)`, - ); - scriptArgs.push(`ptr %s${i}`); - break; - } - } - const ft = ffiCallbackType(cb); - const internalRet = this.llType(ft.ret); - const callArgs = [`ptr %cb`, ...scriptArgs].join(", "); - if (cb.lifetime === "retained") { - this.declare(`declare ptr @scr_closure_retain_v(ptr)`); - this.declare(`declare void @scr_closure_release_v(ptr)`); - defs.push(` %invoke_pin = call ptr @scr_closure_retain_v(ptr %cb)`); - } - if (cb.returns === "void") { - defs.push( - ` call void %fn(${callArgs})`, - ...(cb.lifetime === "retained" - ? [` call void @scr_closure_release_v(ptr %invoke_pin)`] - : []), - ` ret void`, - `}`, - ``, - ); - continue; - } - defs.push(` %result = call ${internalRet} %fn(${callArgs})`); - if (cb.lifetime === "retained") { - defs.push(` call void @scr_closure_release_v(ptr %invoke_pin)`); - } - switch (cb.returns) { - case "i64": - case "u64": - case "pointer": { - const ty = ffiNativeTypeLl(cb.returns); - this.declare(`declare ${ty} @scr_bigint_to_${cb.returns}(ptr)`); - this.declare(`declare void @scr_bigint_release(ptr)`); - defs.push( - ` %out = call ${ty} @scr_bigint_to_${cb.returns}(ptr %result)`, - ` call void @scr_bigint_release(ptr %result)`, - ` ret ${ty} %out`, - ); - break; - } - case "f64": - defs.push(` ret double %result`); - break; - case "f32": - defs.push(` %out = fptrunc double %result to float`, ` ret float %out`); - break; - case "bool": - defs.push(` %out = zext i1 %result to i8`, ` ret i8 %out`); - break; - case "u8": - case "i8": - case "u16": - case "i16": - case "u32": - this.declare(`declare double @scr_bit_ushr(double, double)`); - defs.push( - ` %coerced = call double @scr_bit_ushr(double %result, double ${f64Lit(0)})`, - ` %wide = fptoui double %coerced to i32`, - ); - if (cb.returns !== "u32") { - const nativeTy = ffiNativeTypeLl(cb.returns); - defs.push(` %out = trunc i32 %wide to ${nativeTy}`, ` ret ${nativeTy} %out`); - } else { - defs.push(` ret i32 %wide`); - } - break; - case "i32": - this.declare(`declare double @scr_bit_or(double, double)`); - defs.push( - ` %coerced = call double @scr_bit_or(double %result, double ${f64Lit(0)})`, - ` %out = fptosi double %coerced to i32`, - ` ret i32 %out`, - ); - break; - } - defs.push(`}`, ``); - } - return { globals, defs }; + emitFfiCallbackDefs(): { globals: string[]; defs: string[] } { + return emitFfiCallbackDefs(this); } emit(): string { return this.emitParts().join("\n"); } - emitParts(): string[] { - 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 - // ones, because the runtime is always linked statically into the - // executable. So on ELF they can all use the local-exec model: LLVM's - // default general-dynamic sequence (relaxed by the linker to `mov %fs:0` - // plus an add), and even initial-exec (relaxed to `mov $offset` plus an - // indexed access), become one %fs-relative operand LLVM can fold into - // loads and compares. Darwin's thread-local variables have one model. - return parts.map((part) => - part - .replace( - /^(@[-$._A-Za-z0-9]+ = internal )thread_local global /gm, - "$1thread_local(localexec) global ", - ) - .replace( - /^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, - "$1thread_local(localexec) global ", - ), - ); + return emitParts(this); } /** 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[] { + disposeObserverGates(): string[] { const weak = [...this.decls].some( (d) => d.includes("@scr_weak_map_new(") || d.includes("@scr_weak_set_new("), ); @@ -2032,1812 +1593,25 @@ export class LlEmitter { ``, ]; } - - 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. - const fnDefs: string[] = []; - for (const fn of this.mod.functions) { - const def = this.emitFunction(fn); - fnDefs.push(def); - this.fnDefText.set(fn.name, def); - } - this.selectStacklessAsync(fnDefs); - const errorViews: string[] = []; - for (const property of ["name", "message"]) - if (this.mod.functions.some((fn) => fn.name === `%error.${property}.read`)) { - this.declare(`declare void @scr_error_install_${property}_reader(ptr)`); - errorViews.push( - ` call void @scr_error_install_${property}_reader(ptr @${mangleFunction(`%error.${property}.read`)})`, - ); - } - for (const meta of this.classMeta.values()) { - if (meta.def.runtime || meta.root.def.name !== "%Error") continue; - const type: IrType = { kind: "object", className: meta.def.name }; - const adapter = this.liveDynRefAdapter(type); - const rc = vAdapters(this.shapeHost, type); - const key = typeKey(type); - const name = `sc_error_view_${errorViews.length}`; - this.resolveThunkDefs.push( - `define internal ptr @${name}(ptr %v) ${FN_ATTRS} {`, - `entry:`, - ` %d = call ptr ${typedRefConstructor(this.shapeHost, type)}(ptr %v, ptr ${rc.retain}, ptr ${rc.release}, ptr ${this.cstr(key)}, ${this.sizeType} ${Buffer.byteLength(key, "utf8")}, ptr @${adapter.snapshot}, ptr ${adapter.commit})`, - ` ret ptr %d`, - `}`, - ``, - ); - this.declare(`declare void @scr_error_register_dyn(ptr, ptr)`); - errorViews.push( - ` call void @scr_error_register_dyn(ptr @${mangleVtInstance(meta.def.name)}, ptr @${name})`, - ); - } - const layouts = emitLlvmLayouts( - this.shapeHost, - this.mod, - this.classMeta, - this.classObjs, - this.fnByName, - (t) => this.llType(t), - this.nullableFields, - this.int32Slots, - ); - const shapes = layouts.records; - const classShapes = layouts.classes; - const classObjDefs = layouts.classObjects; - const wrappers = this.emitFnValueDefs(); - const asyncDefs = this.emitAsyncScaffolding(); - const ffiCallbacks = this.emitFfiCallbackDefs(); - const hasNoInlineRecordClone = [...this.recordCloneShapes].some( - (shapeId) => (this.recordsById.get(shapeId)?.fields.length ?? 0) >= 16, - ); - - // Module globals, FIRST OCCURRENCE per id: a class-expression static - // instantiated through several mixin applications registers one global - // id several times (2041-mixin-values) — C's tentative definitions - // accept the duplicates without an error, LLVM rejects redefinition, so the - // storage AND the release emit once per id here. - const seenGlobalIds = new Set(); - const globals = (this.mod.globals ?? []).filter((g) => { - if (seenGlobalIds.has(g.id)) return false; - seenGlobalIds.add(g.id); - return true; - }); - // Refcounted globals release before main returns — the runtime ABI’s - // sc_release_globals, keeping the RC audit's live count exact. Built - // before the declaration table flushes (it adds the release symbols). - // Two spellings with distinct temp names: the normal exit and the - // uncaught-exception exit are separate blocks of the same function. - // Interned function-value closures are IMMORTAL (rc == SIZE_MAX), so - // an own-property table Object.defineProperties hung on one would - // outlive the RC audit — release it with the globals (the runtime ABI’s - // sc_release_globals tail). Only when the dispatch unit is even - // linked (defineProps is the only writer). - const runtimeFeatures = moduleRuntimeFeatures(this.mod); - const fnValueProps = [...this.fnValues]; - if (fnValueProps.length > 0) this.declare(`declare void @scr_box_release(ptr)`); - if (this.classObjs.size > 0) this.declare(`declare void @scr_classobj_clear_properties(ptr)`); - const globalReleaseLines = (prefix: string): string[] => { - const lines: string[] = []; - globals.forEach((g, i) => { - if (!isRefCounted(g.type)) return; - lines.push( - ` %${prefix}${i} = load ptr, ptr @${mangleGlobal(g.id)}`, - ` call void ${releaseSym(this.shapeHost, g.type)}(ptr %${prefix}${i}) ; ${g.name}`, - ); - }); - fnValueProps.forEach((name, i) => { - // props sits at %ScrClosure field 3; release is NULL-tolerant — - // cleared so a second release path stays idempotent. - lines.push( - ` %${prefix}fp${i} = load ptr, ptr getelementptr inbounds (%ScrClosure, ptr @${mangleFnClosure(name)}, i64 0, i32 3)`, - ` call void @scr_box_release(ptr %${prefix}fp${i}) ; ${name}.props`, - ` store ptr null, ptr getelementptr inbounds (%ScrClosure, ptr @${mangleFnClosure(name)}, i64 0, i32 3)`, - ); - }); - for (const name of this.classObjs.keys()) - lines.push(` call void @scr_classobj_clear_properties(ptr @${mangleClassObj(name)})`); - return lines; - }; - // Exit listeners can read MODULE GLOBALS directly, so they must run - // BEFORE the global releases (the runtime ABI’s runExitListeners - // ordering — the atexit half becomes an idempotent no-op). - const usesEvents = runtimeFeatures.processEvents; - const usesChildProcess = runtimeFeatures.childProcess; - const usesFsWatch = runtimeFeatures.fsWatch; - // Stream-surface programs fill the loop's stream hook (the deferred - // next-tick emissions) and the emitter's post-registration flow kick - // before %main — scr_stream.c links only when the line is emitted - // (native/executable.ts gates on the same predicate). - const usesStream = runtimeFeatures.stream; - // Net-surface programs fill the loop's net hooks (and the netSocket - // handle-dispatch ops for the checked-dynamic boundary); http-surface - // programs additionally stamp the httpReq/httpRes ops — the C main's - // install lines, gated on the same predicates native/executable.ts links by. - const usesNet = runtimeFeatures.net; - const usesDgram = runtimeFeatures.dgram; - const usesHttp2 = runtimeFeatures.http2; - const usesHttp = runtimeFeatures.http; - // Fetch-referencing programs register native fetch before main. - // Runtime selection compiles scr_fetch.c on the same predicate. - const usesFetch = runtimeFeatures.fetch; - const snapshotsTlsCa = runtimeFeatures.tls || runtimeFeatures.tlsCa; - // The process verdict has the same precedence as the C reference - // emitter: node:test owns the final status when present; otherwise an - // embedded process.exitCode owns it; ordinary programs return zero. - const usesNodeTest = runtimeFeatures.nodeTest; - // Declared NOW — the extern block flushes before main assembles. - if (usesEvents) this.declare(`declare void @scr_events_install()`); - if (usesChildProcess) this.declare(`declare void @scr_child_dyn_install()`); - const childStreamBoxes = - usesChildProcess && - usesStream && - this.classMeta.has("%Readable") && - this.classMeta.has("%Writable") - ? [ - this.dyn.toDynHelper({ kind: "object", className: "%Readable" }), - this.dyn.toDynHelper({ kind: "object", className: "%Writable" }), - ] - : null; - // worker.stdout and worker.stderr box the stream unit's Readable the - // same way, and worker.stdin its Writable when the program has one - // (scr_worker_stdio_install, scr_worker_events.c). - const workerStdioBox = - this.mod.workers === true && usesStream && this.classMeta.has("%Readable") - ? (childStreamBoxes?.[0] ?? - this.dyn.toDynHelper({ kind: "object", className: "%Readable" })) - : null; - const workerStdinBox = - this.mod.workers === true && usesStream && this.classMeta.has("%Writable") - ? (childStreamBoxes?.[1] ?? - this.dyn.toDynHelper({ kind: "object", className: "%Writable" })) - : null; - if (childStreamBoxes || workerStdioBox || workerStdinBox) { - this.declare( - `@scr_stream_child_ops = external constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }`, - ); - } - if (childStreamBoxes) - this.declare(`declare void @scr_child_dyn_streams_install(ptr, ptr, ptr)`); - if (workerStdioBox || workerStdinBox) - this.declare(`declare void @scr_worker_stdio_install(ptr, ptr, ptr)`); - if (usesFsWatch) this.declare(`declare void @scr_watch_install()`); - if (this.ffiHasForeignCallback) this.declare(`declare void @scr_ffi_install()`); - if (usesStream) this.declare(`declare void @scr_stream_install()`); - if (usesNet) { - this.declare(`declare void @scr_net_install()`); - this.declare(`declare void @scr_net_dyn_install()`); - } - if (usesDgram) this.declare(`declare void @scr_dgram_install()`); - if (usesHttp2) this.declare(`declare void @scr_http2_dyn_install()`); - if (usesHttp) this.declare(`declare void @scr_http_dyn_install()`); - if (usesFetch) this.declare(`declare void @scr_fetch_install()`); - - if (usesNodeTest) { - this.declare(`declare i32 @scr_test_exit_code()`); - this.declare(`declare void @scr_test_init()`); - } - if (snapshotsTlsCa) { - this.declare(`declare void @scr_tls_ca_install()`); - } - // Inline exit listeners run when something they must beat exists: - // the refcounted-global releases, or the retained-FFI atexit ledger - // sweep (a listener may legitimately release or pump a registration, - // and only the inline call orders ahead of every atexit handler — - // the runtime ABI’s runExitListeners stance). Plain event programs - // with neither keep the atexit path, so their listener timing is - // unchanged. - const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0; - const inlineExitListeners = - usesEvents && (this.mod.workers || hasRefGlobals || this.ffiHasRetainedCallback); - this.declare(`declare i32 @scr_exit_code_hint_get()`); - if (inlineExitListeners) { - this.declare(`declare void @scr_run_exit_listeners(double)`); - this.declare(`declare i32 @scr_exit_code_hint_get()`); - } - const exitListenerLines = (prefix: string): string[] => { - if (!inlineExitListeners) return []; - return [ - ` %${prefix}h = call i32 @scr_exit_code_hint_get()`, - ` %${prefix}hd = sitofp i32 %${prefix}h to double`, - ` call void @scr_run_exit_listeners(double %${prefix}hd)`, - ]; - }; - // The entry's exits release the module globals only where the thread - // frees its heap when its script ends (scr_exit_frees: workers, and - // builds that check for leaks); the main thread of other builds leaves - // it to the operating system. - const exitReleaseLines = (prefix: string): string[] => { - const releases = globalReleaseLines(prefix); - if (releases.length === 0) return releases; - this.declare(`declare zeroext i1 @scr_exit_frees()`); - return [ - ` %${prefix}.frees = call zeroext i1 @scr_exit_frees()`, - ` br i1 %${prefix}.frees, label %${prefix}.release, label %${prefix}.kept`, - `${prefix}.release:`, - ...releases, - ` br label %${prefix}.kept`, - `${prefix}.kept:`, - ]; - }; - const globalReleases = exitReleaseLines("g"); - const asyncEntry = this.fnByName.get(this.mod.entry)?.async === true; - const entryMayThrow = this.mayThrow.has(this.mod.entry); - // The event loop runs when timers appeared OR any async/generator - // function exists (the C main's hasAsync || hasGenerators || - // usesTimers gate). Generator programs run - // the loop too: its exit accounting notes still-suspended generator - // fibers as abandoned, so the RC audit downgrades exactly like the - // async loop-exhaustion story. - const runsLoop = - this.usesTimers || - this.ffiHasForeignCallback || - this.mod.functions.some((f) => f.async === true || f.generator !== undefined); - const uncaughtReleases = entryMayThrow && !asyncEntry ? exitReleaseLines("gu") : []; - const loopReleasesU = runsLoop ? exitReleaseLines("gl") : []; - const loopReleasesR = runsLoop ? exitReleaseLines("gr") : []; - const topRejectReleases = asyncEntry ? exitReleaseLines("gt") : []; - const topPendingReleases = asyncEntry ? exitReleaseLines("gp") : []; - const loopReportedReleases = runsLoop ? exitReleaseLines("gq") : []; - // main's epilogues read the flag / the loop entry points — declared - // HERE, before the extern block flushes. They run once, so they keep - // the out-of-line scr_exc_pending call; per-call checks inline the - // test (pendingTestLines). - if (entryMayThrow || runsLoop) this.declare(`declare zeroext i1 @scr_exc_pending()`); - if (runsLoop) { - this.declare(`declare zeroext i1 @scr_loop_run(ptr)`); - if (this.mod.commonJsEntry === true) this.declare(`declare void @scr_loop_commonjs_entry()`); - this.declare(`declare zeroext i1 @scr_report_unhandled_rejections()`); - this.declare(`declare void @scr_discard_unhandled_rejections()`); - } - if (asyncEntry) { - this.declare(`declare i32 @scr_promise_finish_top_level(ptr)`); - this.declare(`declare void @scr_promise_rethrow_top_level(ptr)`); - this.declare(`declare void @scr_promise_release(ptr)`); - this.declare(`declare void @scr_exit_code_note(i32)`); - } - const topPendingExitLines = (): string[] => { - if (!asyncEntry) return []; - const lines: string[] = []; - // finish_top_level noted 13 unless the program set process.exitCode. - // A failed node:test run outranks either before exit listeners run. - if (usesNodeTest) { - lines.push( - ` %tla_program_exit = call i32 @scr_test_exit_code()`, - ` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`, - ` %tla_hint = call i32 @scr_exit_code_hint_get()`, - ` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_hint, i32 %tla_program_exit`, - ` call void @scr_exit_code_note(i32 %tla_exit_status)`, - ); - } - for (const line of exitListenerLines("xp")) lines.push(line); - for (const line of topPendingReleases) lines.push(line); - // An exit listener may set process.exitCode again, as in Node. - lines.push( - ` %tla_exit_code = call i32 @scr_exit_code_hint_get()`, - ` ret i32 %tla_exit_code`, - ); - return lines; - }; - // LIBRARY mode: the runtime entry points the generated library - // symbols delegate to — declared before the extern block flushes. - if (this.mod.lib !== undefined) { - if (this.wasi) { - this.declare(`declare ptr @malloc(${this.sizeType})`); - this.declare(`declare void @free(ptr)`); - } - this.declare(`declare void @scr_library_entry(i1 zeroext, ptr)`); - this.declare(`declare void @scr_library_reset()`); - this.declare(`declare void @scr_library_check_exc()`); - this.declare(`declare void @scr_library_set_sink(ptr, ptr)`); - this.declare(`declare void @scr_library_callback_entry_guard(ptr)`); - this.declare(`declare void @scr_library_arena_reset()`); - this.declare(`declare void @scr_library_collect()`); - if ((this.mod.lib.callbacks?.length ?? 0) > 0) { - // Host-callback channels: the registration define's dispatch - // (strcmp over the declared names + the runtime slot store). - // The call-site fetch pair (scr_library_cb_require/_ctx) is - // declared at ffiCall emission like every body-driven runtime - // symbol. - this.declare(`declare void @scr_library_cb_set(${this.sizeType}, ptr, ptr)`); - this.declare(`declare i32 @strcmp(ptr, ptr)`); - } - if (this.mod.lib.exports.some((e) => e.params.includes("string"))) { - this.declare(`declare ptr @scr_library_str_in(ptr, ${this.sizeType})`); - } - if (this.mod.lib.exports.some((e) => e.params.includes("bytes"))) { - this.declare(`declare ptr @scr_library_bytes_in(ptr, ${this.sizeType}, ptr)`); - } - if (this.mod.lib.exports.some((e) => e.params.includes("i64"))) { - this.declare(`declare double @scr_library_i64_in(i64, ptr)`); - } - if (this.mod.lib.exports.some((e) => e.params.includes("u64"))) { - this.declare(`declare double @scr_library_u64_in(i64, ptr)`); - } - if (this.mod.lib.exports.some((e) => e.returns === "string")) { - this.declare(`declare void @scr_library_str_out(ptr, ptr, ptr)`); - } - if (this.mod.lib.exports.some((e) => e.returns === "bytes")) { - this.declare(`declare void @scr_library_bytes_out(ptr, ptr, ptr)`); - } - if (this.mod.lib.exports.some((e) => e.params.includes("cstring"))) { - this.declare(`declare ptr @scr_library_cstr_in(ptr, ptr)`); - } - if (this.mod.lib.exports.some((e) => e.returns === "cstring")) { - this.declare(`declare ptr @scr_library_cstr_out(ptr)`); - } - } - // Helpers assemble BEFORE the declaration table flushes (they add - // write/abort declarations). - const helpers = [ - ...this.helperDefs(), - ...emitArraySearchHelpers(this.arraySearchKinds, this.sizeType, (line) => this.declare(line)), - ...emitClassMembershipHelper(this.classMeta, this.sizeType), - ...(this.publishWalkersCache?.defs() ?? []), - ...this.publishedClassNamesDefs(), - ...(this.regions?.moduleDefs() ?? []), - ]; - if (this.constantNumericTables.size > 0) - this.declare(`declare double @scr_arr_get_number(ptr, double)`); - // Native matchers of the regex literals inside the compiled subset. - const regexMatchers = new Map(); - for (const [key, re] of this.regexInstances) { - const code = compileRegexMatcher( - `${re.sym}_match`, - re.pattern, - re.flags, - this.sizeType, - !this.wasi, - ); - if (code === null) continue; - regexMatchers.set(key, code); - for (const decl of code.decls) this.declare(decl); - } - - const out: string[] = [ - `; Generated by scriptc (LLVM backend) from ${this.mod.sourceFile}. Do not edit.`, - ``, - // Type shapes shared with the runtime's C ABI. ScrStr is the header - // prefix only (the flexible-array tail is concrete per literal); - // ScrLogArg is { i32 tag; 8-byte union } — i64 at offset 8 matches - // the C layout (4 bytes padding after the tag). ScrUnion/ScrClosure - // mirror scr_runtime.h field-for-field (tag reads, slot peeks, the - // fn pointer, and the caps[] tail all address through them). - `%ScrStr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType} }`, - `%ScrLogArg = type { i32, i64 }`, - `%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`, - `%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`, - `%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr, ptr }`, - `%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`, - `%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr, ptr }`, - // ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem, - // cyc, spare, filled, ops, data, present, ext } (field numbers in - // SCR_ARR_FIELD), where filled bounds the hole-free prefix, ops points at the element type's constant RC descriptor - // (%ScrArrOps, SCR_ELEM_REF only) and ext at the out-of-line sparse, - // property and metadata storage (NULL until first used). Live dynamic - // stream commits swap its dense, presence and out-of-line storage while - // preserving the target object's identity. - `%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`, - `%ScrArrOps = type { ptr, ptr, ptr }`, - // The ScrMap prefix { rc, key_kind, val_kind, traced } — the inline - // RC fast paths test the traced byte (shapes.ts). - ...(this.rcHelpers === null ? [] : [`%ScrMapRc = type { ${this.sizeType}, i8, i8, i8 }`]), - // The complete ScrMap and its { key, val, hash } entry, read by the - // inline number-key lookup (sc_map_entry_f64) and the inline - // iteration steps (sc_map_iter_entry): field 8 is the i32 entry - // count, field 10 the i64 absent-key filter, entries is field 11, - // nbuckets and buckets fields 13 and 14, the direct index and its i32 - // length fields 15 and 16. - ...(this.decls.has(NUMBER_MAP_ENTRY_DECL) || - this.decls.has(STRING_MAP_ENTRY_DECL) || - this.decls.has(MAP_ITER_DECL) - ? [ - `%ScrMapIx = type { ${this.sizeType}, i8, i8, i8, i8, i32, ptr, ptr, i32, i32, i64, ptr, i32, i32, ptr, ptr, i32, i32 }`, - ] - : []), - // String-keyed reference reads load the value slot of the entry - // scr_map_entry_str returns (map-number-lookup.ts). - ...(this.decls.has(NUMBER_MAP_ENTRY_DECL) || - this.decls.has(STRING_MAP_ENTRY_DECL) || - this.decls.has(MAP_ITER_DECL) - ? [`%ScrMapEntry = type { i64, i64, i64 }`] - : []), - // The runtime error prefix { rc, vt, name, message, code, cause } and the - // class-object shape { rc, pre, post, ctor, name } — field reads on - // builtin errors and classval loads GEP through these. - `%ScrError = type { ${this.sizeType}, ptr, ptr, ptr, ptr, ptr, i8, i8, i8, i8, i8, ptr, ptr }`, - `%ScrClassObj = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`, - // The runtime emitter prefix { rc, vt, reg, cls } — user subclasses - // embed it (classes.ts), and bare-emitter GEPs address through it. - `%ScrEmitter = type { ${this.sizeType}, ptr, ptr, ptr }`, - // The runtime stream layout { rc, vt, reg, cls, st } — one struct - // for all five stream classes; stream subclasses embed it. - `%ScrStream = type { ${this.sizeType}, ptr, ptr, ptr, ptr }`, - // The EventTarget and Event subclass prefixes { rc, vt, state, cls }. - ...(this.classMeta.has(RUNTIME_EVENT_TARGET_CLASS) - ? [`%ScrEventTargetObj = type { ${this.sizeType}, ptr, ptr, ptr }`] - : []), - ...(this.classMeta.has(RUNTIME_EVENT_CLASS) - ? [`%ScrEventObj = type { ${this.sizeType}, ptr, ptr, ptr }`] - : []), - // The catch-binding snapshot box { rc, kind, f64, b, payload, - // retain_fn, release_fn, trace_fn } — caughtTest kind reads and - // caughtNarrow payload extraction GEP through it (offsets match - // scr_runtime.h's natural alignment: kind at 8, f64 at 16, b at 24, - // payload at 32). - `%ScrCaught = type { ${this.sizeType}, i32, double, i8, ptr, ptr, ptr, ptr }`, - // ScrBytes { rc, len, elem(i32+pad), data, backing, brands, shared }. Indexed - // typed-array access GEPs through this directly: the IR type already - // fixes elem, so the hot path needs neither a runtime kind load nor - // the generic scr_bytes_get/set call. - `%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, i8, ptr }`, - // The capture box { rc, kind, obj_retain, obj_release, obj_trace, - // slot } — TDZ reads peek the payload slot (offset 40) directly. - `%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`, - // The stack buffer of the emitted JSON serializers { data, len, cap }. - `%ScrJsonBuf = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, ${this.sizeType}, ${this.sizeType} }`, - // The dynCheck error-path spine { parent, key, index } — the emitted - // builders stack-allocate one per recursion level (dyn.ts). - `%ScrDynPath = type { ptr, ptr, ${this.sizeType} }`, - `%ScrIslandModule = type { ptr, ptr, ${this.sizeType}, ${this.sizeType}, i32, ptr, ${this.sizeType}, ${this.sizeType} }`, - `%ScrIslandEdge = type { ptr, ptr, ptr, i32 }`, - // A generator's prefix { rc, receiver, fiber, state, out } and its - // value slot { kind, f64, b, payload, release_fn } (scr_async.c pins - // both): number and boolean yields and drives address OUT directly. - ...(this.usesGenSlots - ? [ - `%ScrGenSlot = type { i32, double, i8, ptr, ptr }`, - `%ScrGenHead = type { ${this.sizeType}, ptr, ptr, i32, %ScrGenSlot }`, - ] - : []), - ]; - for (const line of shapes.typeDefs) out.push(line); - for (const line of classShapes.typeDefs) out.push(line); - // Thread-instanced library state (abi.instance_per_thread): the - // program TU's mutable globals — module globals, run-once guards, the - // lazily-compiled regex literal caches — and the runtime globals its - // init stamps live in thread-local storage, matching the runtime - // objects compiled with -DSCR_THREAD_INSTANCES. Immutable interned - // data (string literals, unit arms, template arrays, vtables) stays - // shared. - const tl = - this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""; - out.push( - ``, - `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, - `declare void @scr_init()`, - `declare void @scr_lib_init(i32, ptr)`, - ...(this.mod.workers ? [`declare void @scr_runtime_workers_v8()`] : []), - ...(this.stackChecks?.size ? [`declare void @scr_stack_guard_init()`] : []), - ...(this.runtimeAbiMarker && this.mod.lib === undefined - ? [`declare void @${RUNTIME_ABI_MARKER}()`] - : []), - // Overrides the runtime's weak default (scr_lib.c). - ...(this.detachedExit && this.mod.lib === undefined - ? [`@scr_detached_exit = constant i8 1`] - : []), - ); - for (const d of this.decls) out.push(d); - out.push(``); - const flushedDecls = new Set(this.decls); - { - const tl = - this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""; - out.push(...threadSlotsBlockDefinitions(this.threadSlotsBlocks, tl !== "")); - for (let site = 0; site < this.dynReadIcSites; site++) - out.push( - `@sc_dyn_read_ic_${site} = internal ${tl}global { ptr, ${this.sizeType}, ${this.sizeType}, ptr } zeroinitializer`, - ); - } - for (const table of this.constantNumericTables.values()) { - const n = table.values.length; - let tableTmp = 0; - const tableIndex = exactIndexLines("%i", String(n), "i64", () => `%ix${tableTmp++}`); - out.push( - `@${table.symbol} = private constant [${n} x double] [${table.values.map((v) => `double ${f64Lit(v)}`).join(", ")}]`, - `define internal double @${table.symbol}_get(ptr %a, double %i) alwaysinline #0 {`, - `entry:`, - ` %initialized = icmp ne ptr %a, null`, - ...tableIndex.lines.map((line) => ` ${line}`), - ` %safe = and i1 %initialized, ${tableIndex.ok}`, - ` br i1 %safe, label %read, label %fallback`, - `read:`, - ` %slot = getelementptr inbounds [${n} x double], ptr @${table.symbol}, i64 0, i64 ${tableIndex.wide}`, - ` %value = load double, ptr %slot`, - ` ret double %value`, - `fallback:`, - ` %generic = call double @scr_arr_get_number(ptr %a, double %i)`, - ` ret double %generic`, - `}`, - ``, - ); - } - for (const [text, lit] of this.literals) { - // Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } — - // the runtime ABI’s static table, retain/release skip rc == SIZE_MAX. - // 64-bit (little-endian) targets split the capacity word: the high - // half carries the precomputed Map key hash (SCR_STR_HASH_CACHE). - // Bit 31 marks all-ASCII literals (SCR_STR_ASCII_BIT) on every target. - const capWord = - this.sizeType === "i64" - ? literalCapWord(wtf8Bytes(text)) - : literalCapWord32(wtf8Bytes(text)); - out.push( - `@${lit.sym} = internal global { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, [${lit.len + 1} x i8] } ` + - `{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${capWord}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`, - ); - } - if (this.literals.size > 0) out.push(``); - for (const lit of this.bigintLiterals.values()) { - // Immortal ScrBigInt: { rc = SIZE_MAX, len, sign, cap, limbs } with - // pointer-sized little-endian limbs (scr_bigint.c); zero keeps one - // zero limb so limb 0 is always readable. - const size = this.sizeType; - const limbBits = size === "i64" ? 64n : 32n; - const mask = (1n << limbBits) - 1n; - const limbs: string[] = []; - for (let m = lit.value < 0n ? -lit.value : lit.value; m > 0n; m = m >> limbBits) { - limbs.push(`${size} ${m & mask}`); - } - const len = limbs.length; - if (len === 0) limbs.push(`${size} 0`); - const sign = lit.value < 0n ? -1 : lit.value > 0n ? 1 : 0; - out.push( - `@${lit.sym} = internal global { ${size}, ${size}, i32, i32, [${limbs.length} x ${size}] } ` + - `{ ${size} -1, ${size} ${len}, i32 ${sign}, i32 ${limbs.length}, [${limbs.length} x ${size}] [${limbs.join(", ")}] }`, - ); - } - if (this.bigintLiterals.size > 0) out.push(``); - for (const [key, sym] of this.unitInstances) { - // One immortal instance per unit-armed (union, tag): tag set, payload - // slot and RC entry points zero — retain/release/collector all skip - // rc == SIZE_MAX, so these never join the RC audit or a trace walk. - const [unionId, tag] = key.split(":"); - out.push( - `@${sym} = internal global %ScrUnion { ${this.sizeType} -1, i32 ${tag}, ptr null, ptr null, ptr null, i64 0 } ; ${unionId} unit arm`, - ); - } - if (this.unitInstances.size > 0) out.push(``); - for (const [unionId, sym] of this.retypeGuards) { - // Thread-local like module globals: a thread's cast caches hold only - // its own boxes. Never written, the flag folds to false. - out.push(`@${sym} = internal ${tl}global i8 0 ; ${unionId} boxes in a cast cache`); - } - if (this.retypeGuards.size > 0) out.push(``); - for (const [unionId, sym] of this.absentInstances) { - // One immortal ABSENT field-slot instance per undefined-armed union: - // the undefined arm's tag with payload 1 (ordinary unit instances - // carry 0). Every tag reader sees undefined; recordHas and the - // field reads compare tag and payload, never the address, so the - // per-object copies of separately compiled units agree. - const tag = undefinedArmTag({ kind: "union", unionId }, this.unionsById); - out.push( - `@${sym} = internal global %ScrUnion { ${this.sizeType} -1, i32 ${tag}, ptr null, ptr null, ptr null, i64 ${ABSENT_FIELD_PAYLOAD} } ; ${unionId} absent field`, - ); - } - if (this.absentInstances.size > 0) out.push(``); - if (this.needsUnionNone) - out.push( - `@sc_union_none = internal constant %ScrUnion zeroinitializer ; NULL union dummy read by inline stores`, - ``, - ); - if (this.nullableUnions.usesNullSentinel) { - // `null` in every `C | null | undefined` union (NULL is undefined): - // one module-scope immortal object like the ABSENT sentinel. - const S = this.sizeType; - out.push( - `${NULLABLE_NULL} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable null arm`, - ``, - ); - } - if (this.needsNullableAbsent) { - // The ABSENT field-slot state of every nullable-pointer union: one - // module-scope immortal object (rc == SIZE_MAX), distinct from NULL - // (undefined) and from every instance. RC entry points and the - // collector skip it like any immortal; field reads compare its address. - const S = this.sizeType; - out.push( - `${NULLABLE_ABSENT} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable absent field`, - ``, - ); - } - for (const [key, re] of this.regexInstances) { - // One immortal ScrRegex per (pattern, flags) literal, pointing at - // the interned source/flags strings. The bc and translated-program - // slots start null (lazy compile, cached by the runtime) — a mutable - // global, not constant. The last slot is the build-time matcher. - const matcher = regexMatchers.has(key) ? `@${re.sym}_match` : "null"; - out.push( - `@${re.sym} = internal ${tl}global %ScrRegex { ${this.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null, double 0.0, ptr null, ptr null, ptr ${matcher} } ; ${key.replace(/\n/g, "\\n")}`, - ); - } - if (this.regexInstances.size > 0) out.push(``); - for (const code of regexMatchers.values()) out.push(...code.lines, ``); - for (const [, inst] of this.templateStringsInstances) { - // One immortal ScrArr per tagged-template site: a [N x ptr] data - // global of interned cooked-string literals, and the ScrArr header - // over it (rc == SIZE_MAX, len == cap == filled, SCR_ELEM_STR = 2, - // no REF entry points). Every dense slot is present; reads retain - // immortal strings — a no-op. - const n = inst.slots.length; - const present = - n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`; - out.push( - `@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, - `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, - `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n}, i8 2, i8 0, i16 0, i32 ${n}, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null }`, - ); - } - if (this.templateStringsInstances.size > 0) out.push(``); - for (const [text, c] of this.cstrs) { - // NUL-terminated byte-array constants for scr_jb_puts and indentation. - out.push(`@${c.sym} = internal constant [${c.len + 1} x i8] c"${llStrBytes(text)}"`); - } - if (this.cstrs.size > 0) out.push(``); - for (const [body, sym] of this.mapOpsConsts) { - out.push(`@${sym} = internal constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr } { ${body} }`); - } - if (this.mapOpsConsts.size > 0) out.push(``); - - for (const line of ffiCallbacks.globals) out.push(line); - if (ffiCallbacks.globals.length > 0) out.push(``); - for (const g of globals) { - const ty = this.llType(g.type); - const zero = ty === "double" ? f64Lit(0) : ty === "ptr" ? "null" : "false"; - const debug = this.debug?.global(g); - out.push( - `@${mangleGlobal(g.id)} = internal ${tl}global ${ty} ${zero}${debug ? `, !dbg ${debug}` : ""} ; ${g.name}`, - ); - } - if (globals.length > 0) out.push(``); - for (const line of helpers) out.push(line); - for (const line of ffiCallbacks.defs) out.push(line); - for (const line of shapes.defs) out.push(line); - for (const line of classShapes.defs) out.push(line); - for (const line of classObjDefs) out.push(line); - for (const line of this.walkers.defs) out.push(line); - for (const line of this.dyn.defs) out.push(line); - for (const line of wrappers) out.push(line); - for (const line of asyncDefs) out.push(line); - for (const line of this.resolveThunkDefs) out.push(line); - out.push(fnDefs[0] ?? ""); - for (let i = 1; i < fnDefs.length; i++) out.push("", fnDefs[i]!); - out.push(""); - - // main(): scr_init, the program-dependent error-vt interval stamps, - // scr_lib_init(argc, argv), then the entry function. An uncaught - // exception escaping top-level code prints and exits 1 (Node). - // The exception epilogue is emitted when the entry may throw. - const stamps: string[] = [...errorViews]; - for (const iv of this.errorIntervals) { - const fields: [number, number][] = [ - [0, iv.pre], - [1, iv.post], - ]; - for (const [field, value] of fields) { - stamps.push( - ` store ${this.sizeType} ${value}, ptr getelementptr inbounds ([${RUNTIME_ERROR_CLASSES.size} x %ScrVt], ptr @scr_error_vts, i64 0, i64 ${iv.kind}, i32 ${field})${field === 1 ? ` ; ${iv.lib}` : ""}`, - ); - } - } - if (this.emitterInterval !== null) { - // The runtime emitter vtable's interval (emitterVtStampLines): bare - // EventEmitter instances answer instanceof and dispatch dynamic - // teardown under THIS module's preorder numbering. - out.push(`@scr_emitter_vt = external ${tl}global %ScrVt`, ``); - stamps.push( - ` store ${this.sizeType} ${this.emitterInterval.pre}, ptr getelementptr inbounds (%ScrVt, ptr @scr_emitter_vt, i64 0, i32 0)`, - ` store ${this.sizeType} ${this.emitterInterval.post}, ptr getelementptr inbounds (%ScrVt, ptr @scr_emitter_vt, i64 0, i32 1) ; EventEmitter`, - ); - } - for (const iv of this.streamIntervals) { - // The runtime stream vtables' intervals (streamVtStampLines) — the - // emitter story: instanceof and dynamic teardown dispatch through - // them. - out.push(`@${iv.vt} = external ${tl}global %ScrVt`); - stamps.push( - ` store ${this.sizeType} ${iv.pre}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 0)`, - ` store ${this.sizeType} ${iv.post}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 1) ; ${iv.lib}`, - ); - } - if (this.streamIntervals.length > 0) out.push(``); - if ([...this.literals.keys(), ...this.cstrs.keys()].some(hasLoneSurrogate)) { - // The runtime skips surrogate work until a lone surrogate may exist - // (scr_wtf8_any); its own producers note one, and literals do here. - out.push(`declare void @scr_wtf8_note()`, ``); - stamps.push(` call void @scr_wtf8_note()`); - } - if (this.errorIntervals.length > 0 && this.tracedShapes.has("object:%Error")) { - // The cycle fixpoint marked the Error hierarchy (a user subclass - // holds cycle-capable fields — capability is hierarchy-uniform), so - // the runtime's own error allocations need collector headers too. - // Declared inline: the extern block already flushed (LLVM is - // order-free, the helper-defs precedent). - out.push(`declare void @scr_error_set_traced()`, ``); - stamps.push(` call void @scr_error_set_traced()`); - } - if ((entryMayThrow || runsLoop) && this.mod.lib === undefined) { - // Declared inline: the extern block already flushed (LLVM is - // order-free — the scr_error_set_traced precedent). Only the - // printer emits here (nothing else declares it); scr_exc_pending - // and the loop entry points rode the Set before the flush. - out.push( - `declare void @scr_exc_print_uncaught()`, - `declare zeroext i1 @scr_exc_handle_uncaught(i1 zeroext)`, - ``, - ); - } - if (this.mod.lib !== undefined) { - // LIBRARY mode: no @main — the profile-declared external - // symbols specified by the library IR instead. - for (const line of this.emitLibDefs(globals, globalReleaseLines, stamps)) out.push(line); - out.push(...this.inlineRcTail(flushedDecls)); - out.push(`attributes #0 = { sanitize_address }`); - if (this.usesCoroutines) out.push(`attributes #1 = { sanitize_address presplitcoroutine }`); - if (hasNoInlineRecordClone) out.push(`attributes #2 = { noinline sanitize_address }`); - out.push(``); - out.push(...this.fieldAliasMetadata()); - if (this.debug !== null) out.push(this.debug.render()); - return out; - } - out.push( - `define i32 @${this.mod.workers ? "sc_context_entry" : this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`, - `entry:`, - ...(this.runtimeAbiMarker ? [` call void @${RUNTIME_ABI_MARKER}()`] : []), - ...(this.mod.workers ? [` call void @scr_runtime_workers_v8()`] : []), - ` call void @scr_init()`, - // Each context (the main thread, every worker) installs its stack - // guard before running compiled code that checks it. - ...(this.stackChecks?.size ? [` call void @scr_stack_guard_init()`] : []), - ...stamps, - // Event-surface programs (signal/exit listeners) fill the loop's - // nullable event hooks before %main — scr_events.c links only when - // this line is emitted (native/executable.ts gates on the same predicate). - ...(usesEvents ? [` call void @scr_events_install()`] : []), - ...(usesChildProcess ? [` call void @scr_child_dyn_install()`] : []), - ...(childStreamBoxes - ? [ - ` call void @scr_child_dyn_streams_install(ptr @scr_stream_child_ops, ptr @${childStreamBoxes[0]}, ptr @${childStreamBoxes[1]})`, - ] - : []), - ...(workerStdioBox || workerStdinBox - ? [ - ` call void @scr_worker_stdio_install(ptr @scr_stream_child_ops, ptr ${workerStdioBox ? `@${workerStdioBox}` : "null"}, ptr ${workerStdinBox ? `@${workerStdinBox}` : "null"})`, - ] - : []), - // fs.watch programs fill the loop's watch hooks the same way — - // scr_watch.c links only when this line is emitted. - ...(usesFsWatch ? [` call void @scr_watch_install()`] : []), - ...(this.ffiHasForeignCallback ? [` call void @scr_ffi_install()`] : []), - ...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []), - ...(usesFetch ? [` call void @scr_fetch_install()`] : []), - ...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []), - ...(usesHttp ? [` call void @scr_http_dyn_install()`] : []), - ...(usesDgram ? [` call void @scr_dgram_install()`] : []), - ...(usesHttp2 ? [` call void @scr_http2_dyn_install()`] : []), - ...(usesStream ? [` call void @scr_stream_install()`] : []), - ` call void @scr_lib_init(i32 %argc, ptr %argv)`, - // node:test takes Node's --test-update-snapshots out of argv first. - ...(usesNodeTest ? [` call void @scr_test_init()`] : []), - ...(asyncEntry - ? [` %top = call ptr @${mangleAsyncSpawn(this.mod.entry)}()`] - : [` call void @${mangleFunction(this.mod.entry)}()`]), - // Uncaught exception from top-level code: Node exits 1. - ...(entryMayThrow && !asyncEntry - ? [ - ` %exc = call zeroext i1 @scr_exc_pending()`, - ` br i1 %exc, label %uncaught, label %ok`, - `uncaught:`, - ` %handled = call zeroext i1 @scr_exc_handle_uncaught(i1 false)`, - ` br i1 %handled, label %ok, label %fatal`, - `fatal:`, - ` call void @scr_exc_print_uncaught()`, - ...exitListenerLines("xu"), - ...uncaughtReleases, - ` %failure_code_0 = call i32 @scr_exit_code_hint_get()`, - ` ret i32 %failure_code_0`, - `ok:`, - ] - : []), - // The event loop runs to exhaustion (microtasks before timers). A - // throw escaping a timer callback and unhandled promise rejections - // both exit 1, like Node — the C main's loop block exactly. - ...(runsLoop - ? [ - ...(this.mod.commonJsEntry === true ? [` call void @scr_loop_commonjs_entry()`] : []), - ` %loop_rejection = call zeroext i1 @scr_loop_run(ptr ${asyncEntry ? "%top" : "null"})`, - ` %lexc = call zeroext i1 @scr_exc_pending()`, - ` br i1 %lexc, label %luncaught, label %lok`, - `luncaught:`, - ` call void @scr_exc_print_uncaught()`, - ...(asyncEntry ? [` call void @scr_promise_release(ptr %top)`] : []), - ...exitListenerLines("xl"), - ...loopReleasesU, - ` %failure_code_1 = call i32 @scr_exit_code_hint_get()`, - ` ret i32 %failure_code_1`, - `lok:`, - ` br i1 %loop_rejection, label %lreported, label %lclean`, - `lreported:`, - ` call void @scr_discard_unhandled_rejections()`, - ...(asyncEntry ? [` call void @scr_promise_release(ptr %top)`] : []), - ...exitListenerLines("xq"), - ...loopReportedReleases, - ` %failure_code_2 = call i32 @scr_exit_code_hint_get()`, - ` ret i32 %failure_code_2`, - `lclean:`, - ...(asyncEntry - ? [ - ` %tla_status = call i32 @scr_promise_finish_top_level(ptr %top)`, - ` %tla_rejected = icmp eq i32 %tla_status, 1`, - ` br i1 %tla_rejected, label %tla_fail, label %tla_not_rejected`, - `tla_fail:`, - // The loop already delivered every earlier-checkpoint - // rejection. Drop same-checkpoint competitors before - // surfacing the fatal module verdict. - ` call void @scr_discard_unhandled_rejections()`, - ` call void @scr_promise_rethrow_top_level(ptr %top)`, - ` call void @scr_promise_release(ptr %top)`, - ` call void @scr_exc_print_uncaught()`, - ...exitListenerLines("xt"), - ...topRejectReleases, - ` %failure_code_3 = call i32 @scr_exit_code_hint_get()`, - ` ret i32 %failure_code_3`, - `tla_not_rejected:`, - ` call void @scr_promise_release(ptr %top)`, - ] - : []), - ` %rej = call zeroext i1 @scr_report_unhandled_rejections()`, - ` br i1 %rej, label %lrej, label %lrok`, - `lrej:`, - ...exitListenerLines("xr"), - ...loopReleasesR, - ` %failure_code_4 = call i32 @scr_exit_code_hint_get()`, - ` ret i32 %failure_code_4`, - `lrok:`, - ...(asyncEntry - ? [ - ` %tla_pending = icmp eq i32 %tla_status, 13`, - ` br i1 %tla_pending, label %tla_stuck, label %tla_ok`, - `tla_stuck:`, - ...topPendingExitLines(), - `tla_ok:`, - ] - : []), - ] - : []), - ...exitListenerLines("xn"), - ...globalReleases, - ...(usesNodeTest - ? [` %test_exit = call i32 @scr_test_exit_code()`, ` ret i32 %test_exit`] - : [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]), - `}`, - ``, - ...this.inlineRcTail(flushedDecls), - // sanitize_address is inert under the plain pipeline; the sanitized - // lane's -fsanitize=address link activates instrumentation over the - // emitted functions too (the runtime TUs get theirs from clang). - `attributes #0 = { sanitize_address }`, - ...(this.usesCoroutines ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), - ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []), - ``, - ); - if (this.mod.workers) { - out.push( - `declare void @scr_context_cleanup()`, - `declare i32 @scr_worker_argc()`, - `declare ptr @scr_worker_argv()`, - `define i32 @sc_worker_entry(i32 %root) ${FN_ATTRS} {`, - `entry:`, - ` %argc = call i32 @scr_worker_argc()`, - ` %argv = call ptr @scr_worker_argv()`, - ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, - ` ret i32 %result`, - `}`, - `define i32 @main(i32 %argc, ptr %argv) ${FN_ATTRS} {`, - `entry:`, - ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, - ` call void @scr_context_cleanup()`, - ` ret i32 %result`, - `}`, - ); - } - out.push(...this.fieldAliasMetadata()); - if (this.debug !== null) out.push(this.debug.render()); - return out; + emitModuleParts(): string[] { + return emitModuleParts(this); } - - /** LIBRARY mode: the profile-declared external definitions — the - * export-map wrappers plus init / sink-registration / reset / collect. - * Plain `define` (not `define internal`) — the exact linkage distinction - * that separates the executable lane's @main from everything else. The - * bodies delegate every runtime half to scr_library.c, mirroring the C - * emission line for line, so the two lanes are identical by - * construction. */ - private emitLibDefs( + emitLibDefs( globals: IrGlobal[], globalReleaseLines: (prefix: string) => string[], stamps: string[], ): string[] { - const lib = this.mod.lib!; - const autoReset = lib.resultResetSymbol === null; - const out: string[] = [``, `; ── library-mode entries (profile: ${lib.profileName}) ──`, ``]; - // Every entry's prologue records its external symbol in the funnel's - // current-entry slot (structured trap-teaching field 2); the symbols - // live as internal constants, one per entry. - const symConst = (sym: string): string => `@sc_lib_sym_${sym}`; - const emitSymConst = (sym: string): void => { - out.push( - `${symConst(sym)} = internal constant [${Buffer.byteLength(sym, "utf8") + 1} x i8] c"${llStrBytes(sym)}"`, - ); - }; - emitSymConst(lib.initSymbol); - emitSymConst(lib.sinkRegisterSymbol); - if (lib.callbackRegisterSymbol !== null && lib.callbackRegisterSymbol !== undefined) - emitSymConst(lib.callbackRegisterSymbol); - if (lib.resultResetSymbol !== null) emitSymConst(lib.resultResetSymbol); - if (lib.collectSymbol !== null) emitSymConst(lib.collectSymbol); - for (const e of lib.exports) emitSymConst(e.symbol); - out.push(``); - if (this.wasi) { - for (const symbol of ["scriptc_alloc", "scriptc_free"]) emitSymConst(symbol); - out.push( - `define ptr @scriptc_alloc(i32 %size) ${FN_ATTRS} {`, - `entry:`, - ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst("scriptc_alloc")})`, - ` %p = call ptr @malloc(i32 %size)`, - ` ret ptr %p`, - `}`, - ``, - `define void @scriptc_free(ptr %p) ${FN_ATTRS} {`, - `entry:`, - ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst("scriptc_free")})`, - ` call void @free(ptr %p)`, - ` ret void`, - `}`, - ``, - ); - } - // The runtime detected-trap overlay table (scr_runtime.h declares it, - // the library trap funnel consults it): flat code/teaching/remediation - // triples, one per runtime trap code (SC4013–SC4019) the profile - // declares text for. The funnel assembles the structured sink message. - // The empty table still defines the symbols the funnel links against. - const ovlCells: string[] = []; - lib.trapOverlays.forEach((o, i) => { - const cell = (name: string, text: string | undefined): void => { - if (text === undefined) { - ovlCells.push("ptr null"); - return; - } - const sym = `@sc_lib_ovl_${i}_${name}`; - out.push( - `${sym} = internal constant [${Buffer.byteLength(text, "utf8") + 1} x i8] c"${llStrBytes(text)}"`, - ); - ovlCells.push(`ptr ${sym}`); - }; - cell("code", o.code); - cell("teach", o.teaching); - cell("rem", o.remediation); - }); - out.push( - ovlCells.length === 0 - ? `@scr_library_trap_overlays = constant [1 x ptr] zeroinitializer` - : `@scr_library_trap_overlays = constant [${ovlCells.length} x ptr] [${ovlCells.join(", ")}]`, - `@scr_library_trap_overlays_len = constant ${this.sizeType} ${lib.trapOverlays.length}`, - ``, - ); - // The init entry: full deterministic reset-and-reevaluate. Program - // globals release and zero first (run-once guards included), then the - // runtime session reset, the error-vt interval stamps verbatim from - // the executable main, %main itself, and the escaped-exception check. - const zeroStores = globals.map((g) => { - const ty = this.llType(g.type); - const zero = ty === "double" ? f64Lit(0) : ty === "ptr" ? "null" : "false"; - return ` store ${ty} ${zero}, ptr @${mangleGlobal(g.id)} ; ${g.name}`; - }); - out.push( - `define void @${lib.initSymbol}() ${FN_ATTRS} {`, - `entry:`, - ` call void @scr_library_entry(i1 zeroext true, ptr ${symConst(lib.initSymbol)}) ; init always resets the result arena`, - ...globalReleaseLines("ci"), - ...zeroStores, - ` call void @scr_library_reset()`, - ...stamps, - ` call void @${mangleFunction(this.mod.entry)}()`, - ` call void @scr_library_check_exc()`, - ` ret void`, - `}`, - ``, - `define void @${lib.sinkRegisterSymbol}(ptr %fn, ptr %ctx) ${FN_ATTRS} {`, - `entry:`, - ` call void @scr_library_callback_entry_guard(ptr ${symConst(lib.sinkRegisterSymbol)})`, - ` call void @scr_library_set_sink(ptr %fn, ptr %ctx)`, - ` ret void`, - `}`, - ``, - ); - if (lib.callbacks !== undefined && lib.callbacks.length > 0) { - // Host-callback channels: the per-channel name constants (the - // registration dispatch's strcmp operands), the per-channel - // unregistered-call trap constants (the ffiCall sites' - // scr_library_cb_require operands), and the registration function: - // a pure store dispatch - // (the sink registration's rule — no entry prologue, no poison - // guard) whose first operation rejects callback-time re-entry - // (SC4026). An unknown or NULL name is a defined -1, never a store. - for (const cb of lib.callbacks) { - out.push( - `@sc_lib_cb_name_${cb.slot} = internal constant [${Buffer.byteLength(cb.name, "utf8") + 1} x i8] c"${llStrBytes(cb.name)}"`, - `@sc_lib_cb_trap_${cb.slot} = internal constant [${Buffer.byteLength(cb.unregisteredTrap, "utf8") + 1} x i8] c"${llStrBytes(cb.unregisteredTrap)}"`, - ); - } - out.push( - ``, - `define i32 @${lib.callbackRegisterSymbol}(ptr %name, ptr %fn, ptr %ctx) ${FN_ATTRS} {`, - `entry:`, - ` call void @scr_library_callback_entry_guard(ptr ${symConst(lib.callbackRegisterSymbol!)})`, - ` %isnull = icmp eq ptr %name, null`, - ` br i1 %isnull, label %miss, label %try0`, - ); - lib.callbacks.forEach((cb, i) => { - const next = i + 1 < lib.callbacks!.length ? `try${i + 1}` : "miss"; - out.push( - `try${i}: ; channel '${cb.name}'`, - ` %cmp${i} = call i32 @strcmp(ptr %name, ptr @sc_lib_cb_name_${cb.slot})`, - ` %eq${i} = icmp eq i32 %cmp${i}, 0`, - ` br i1 %eq${i}, label %set${i}, label %${next}`, - `set${i}:`, - ` call void @scr_library_cb_set(${this.sizeType} ${cb.slot}, ptr %fn, ptr %ctx)`, - ` ret i32 0`, - ); - }); - out.push(`miss:`, ` ret i32 -1`, `}`, ``); - } - if (lib.identity !== undefined && this.emitLibraryIdentity) { - // Profile-declared identity getters (the ask-2 sidecar's boot-time - // pairing fence): pure data returns with NO entry prologue — exempt - // from the poisoned guard and every runtime touch (ratified), so a - // host can read them before init and after a trap. The u64 rides - // i64 two's-complement (LLVM integer constants are signed). - for (const line of emitLibraryIdentityLines(lib.identity, FN_ATTRS)) out.push(line); - } - if (lib.resultResetSymbol !== null) { - out.push( - `define void @${lib.resultResetSymbol}() ${FN_ATTRS} {`, - `entry:`, - ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst(lib.resultResetSymbol)})`, - ` call void @scr_library_arena_reset()`, - ` ret void`, - `}`, - ``, - ); - } - if (lib.collectSymbol !== null) { - out.push( - `define void @${lib.collectSymbol}() ${FN_ATTRS} {`, - `entry:`, - ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst(lib.collectSymbol)})`, - ` call void @scr_library_collect() ; arena reset + a full cycle collection`, - ` ret void`, - `}`, - ``, - ); - } - for (const e of lib.exports) { - const params: string[] = []; - const body: string[] = [ - ` call void @scr_library_entry(i1 zeroext ${autoReset ? "true" : "false"}, ptr ${symConst(e.symbol)})`, - ]; - const args: string[] = []; - if (e.inboundBytesTrap !== undefined) { - // The bytes-in helper's trap message: the compiler-assembled - // structured trap-teaching form (0x01 text 0x1F SC4012 0x1F symbol - // [0x1F remediation]) consumed by the runtime sink. - const trapBytes = Buffer.byteLength(e.inboundBytesTrap, "utf8"); - out.push( - `@sc_lib_bytes_trap_${e.symbol} = internal constant [${trapBytes + 1} x i8] c"${llStrBytes(e.inboundBytesTrap)}"`, - ``, - ); - } - if (e.inboundIntTrap !== undefined) { - // The i64/u64-in helpers' host-contract trap message (ask 4): an - // inbound integer past ±(2^53−1) cannot ride f64 exactly. Same - // assembled structured form, same SC4012 code, same - // emission-invariance argument as the bytes trap. - const trapBytes = Buffer.byteLength(e.inboundIntTrap, "utf8"); - out.push( - `@sc_lib_int_trap_${e.symbol} = internal constant [${trapBytes + 1} x i8] c"${llStrBytes(e.inboundIntTrap)}"`, - ``, - ); - } - if (e.inboundCstringTrap !== undefined) { - // The cstring-in helper's host-contract trap message (a NULL - // `const char *`): same assembled structured form and SC4012 code. - const trapBytes = Buffer.byteLength(e.inboundCstringTrap, "utf8"); - out.push( - `@sc_lib_cstr_trap_${e.symbol} = internal constant [${trapBytes + 1} x i8] c"${llStrBytes(e.inboundCstringTrap)}"`, - ``, - ); - } - e.params.forEach((cls, i) => { - switch (cls) { - case "f64": - params.push(`double %a${i}`); - args.push(`double %a${i}`); - break; - case "bool": - params.push(`i8 %a${i}`); - body.push(` %c${i} = icmp ne i8 %a${i}, 0`); - args.push(`i1 %c${i}`); - break; - case "u8": - params.push(`i8 %a${i}`); - body.push(` %c${i} = uitofp i8 %a${i} to double`); - args.push(`double %c${i}`); - break; - case "u32": - params.push(`i32 %a${i}`); - body.push(` %c${i} = uitofp i32 %a${i} to double`); - args.push(`double %c${i}`); - break; - case "i32": - params.push(`i32 %a${i}`); - body.push(` %c${i} = sitofp i32 %a${i} to double`); - args.push(`double %c${i}`); - break; - case "i64": - // Inbound declared-integer edge (ask 4): the helper converts - // exactly or delivers the host-contract trap (past ±(2^53−1) - // the value cannot ride f64 without rounding). - params.push(`i64 %a${i}`); - body.push( - ` %c${i} = call double @scr_library_i64_in(i64 %a${i}, ptr @sc_lib_int_trap_${e.symbol})`, - ); - args.push(`double %c${i}`); - break; - case "u64": - params.push(`i64 %a${i}`); - body.push( - ` %c${i} = call double @scr_library_u64_in(i64 %a${i}, ptr @sc_lib_int_trap_${e.symbol})`, - ); - args.push(`double %c${i}`); - break; - case "string": - params.push(`ptr %a${i}_ptr`, `${this.sizeType} %a${i}_len`); - body.push( - ` %c${i} = call ptr @scr_library_str_in(ptr %a${i}_ptr, ${this.sizeType} %a${i}_len)`, - ); - args.push(`ptr %c${i}`); - break; - case "bytes": - params.push(`ptr %a${i}_ptr`, `${this.sizeType} %a${i}_len`); - body.push( - ` %c${i} = call ptr @scr_library_bytes_in(ptr %a${i}_ptr, ${this.sizeType} %a${i}_len, ptr @sc_lib_bytes_trap_${e.symbol})`, - ); - args.push(`ptr %c${i}`); - break; - case "cstring": - params.push(`ptr %a${i}`); - body.push( - ` %c${i} = call ptr @scr_library_cstr_in(ptr %a${i}, ptr @sc_lib_cstr_trap_${e.symbol})`, - ); - args.push(`ptr %c${i}`); - break; - } - }); - if (e.returns === "string" || e.returns === "bytes") { - params.push(`ptr %out`, `ptr %out_len`); - } - const target = `@${mangleFunction(e.fnName)}`; - const callArgs = args.join(", "); - let retType = "void"; - switch (e.returns) { - case "void": - body.push( - ` call void ${target}(${callArgs})`, - ` call void @scr_library_check_exc()`, - ` ret void`, - ); - break; - case "f64": - retType = "double"; - body.push( - ` %r = call double ${target}(${callArgs})`, - ` call void @scr_library_check_exc()`, - ` ret double %r`, - ); - break; - case "i64": - case "u64": - // The outbound declared-integer edge (ask 4): every value - // reaching this return was PROVEN whole and inside the class's - // range at compile time, so the fp-to-int conversion is exact - // by construction — the crossing carries the mathematically - // exact integer the f64 held. - retType = "i64"; - body.push( - ` %r = call double ${target}(${callArgs})`, - ` call void @scr_library_check_exc()`, - ` %z = ${e.returns === "i64" ? "fptosi" : "fptoui"} double %r to i64`, - ` ret i64 %z`, - ); - break; - case "bool": - retType = "i8"; - body.push( - ` %r = call i1 ${target}(${callArgs})`, - ` call void @scr_library_check_exc()`, - ` %z = zext i1 %r to i8`, - ` ret i8 %z`, - ); - break; - case "string": - body.push( - ` %r = call ptr ${target}(${callArgs})`, - ` call void @scr_library_check_exc()`, - ` call void @scr_library_str_out(ptr %r, ptr %out, ptr %out_len)`, - ` ret void`, - ); - break; - case "bytes": - body.push( - ` %r = call ptr ${target}(${callArgs})`, - ` call void @scr_library_check_exc()`, - ` call void @scr_library_bytes_out(ptr %r, ptr %out, ptr %out_len)`, - ` ret void`, - ); - break; - case "cstring": - // The result's arena copy, returned as `const char *`. - retType = "ptr"; - body.push( - ` %r = call ptr ${target}(${callArgs})`, - ` call void @scr_library_check_exc()`, - ` %z = call ptr @scr_library_cstr_out(ptr %r)`, - ` ret ptr %z`, - ); - break; - } - out.push( - `define ${retType} @${e.symbol}(${params.join(", ")}) ${FN_ATTRS} { ; library export ${e.fnName}`, - `entry:`, - ...body, - `}`, - ``, - ); - } - return out; + return emitLibDefs(this, globals, globalReleaseLines, stamps); } - - /** The shared abort helpers (emitted only when referenced): the OOM - * abort of untraced shape allocation and the invalid-union-tag abort — - * both print the runtime ABI’s exact message on fd 2 and abort. */ - private helperDefs(): string[] { - const defs: string[] = []; - const msgHelper = (fnName: string, msgSym: string, msg: string): void => { - // The message routes through the runtime's trap funnel: executable - // builds expand to the historical bytes-on-stderr + abort; library - // builds route to the registered panic sink (scr_runtime.h). - const bytes = Buffer.byteLength(msg, "utf8"); - this.declare(`declare void @scr_trap(ptr)`); - defs.push( - `@${msgSym} = internal constant [${bytes + 1} x i8] c"${llStrBytes(msg)}"`, - `define internal void @${fnName}() ${FN_ATTRS} {`, - `entry:`, - ` call void @scr_trap(ptr @${msgSym})`, - ` unreachable`, - `}`, - ``, - ); - }; - if (this.needsOom) msgHelper("sc_oom", "sc_oom_msg", "scriptc: out of memory\n"); - if (this.needsBadTag) { - msgHelper("sc_bad_tag", "sc_bad_tag_msg", "scriptc: internal error: invalid union tag\n"); - } - if (this.needsBadKey) { - // The keyed-read miss on a result type that cannot say `undefined`: - // trap like an array OOB read instead of corrupting a typed slot - // (SEMANTICS.md; the C helper's message additionally interpolates - // the runtime key — a trap-path debugging nicety, never reachable - // by a program whose behavior matches Node). - msgHelper( - "sc_bad_key", - "sc_bad_key_msg", - "scriptc: TypeError: record has no key (typed slot — no undefined is representable)\n", - ); - } - if (this.needsRetainBox) { - // scr_box_retain is a static inline (increment-unless-immortal); - // emitted once with internal linkage, like the record retains. - defs.push( - `define internal ptr @sc_retain_box(ptr %b) ${FN_ATTRS} {`, - `entry:`, - ` %rc = load ${this.sizeType}, ptr %b`, - ` %imm = icmp eq ${this.sizeType} %rc, -1`, - ` br i1 %imm, label %done, label %inc`, - `inc:`, - ` %n = add ${this.sizeType} %rc, 1`, - ` store ${this.sizeType} %n, ptr %b`, - ` br label %done`, - `done:`, - ` ret ptr %b`, - `}`, - ``, - ); - } - // Number-key Map/Set lookups probe the runtime's direct index inline: - // while ndense is nonzero every live key is an integer below it, so an - // in-range integral key reads its slot (entry index + 1, 0 = absent) and - // any other key is absent. Maps without the index (ndense 0) hash the - // key in sc_map_entry_f64_hashed, which scans the entries of a small map - // or probes a compact bucket table as scr_map_find_f64 does, and calls - // the runtime lookup for a wide table. It stays out of line so that the - // direct-index path stays small at every call site. Both only read - // memory, so LLVM merges lookups of one key when nothing is stored in - // between (a has() followed by get(), for example). - if (this.decls.has(NUMBER_MAP_ENTRY_DECL)) { - const sz = this.sizeType; - let mapTmp = 0; - const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`); - defs.push( - `define internal ptr @sc_map_entry_f64(ptr %m, double %k) ${FN_ATTRS} {`, - `entry:`, - ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 16`, - ...(sz === "i64" - ? [` %nd32 = load i32, ptr %ndp`, ` %nd = zext i32 %nd32 to i64`] - : [` %nd = load i32, ptr %ndp`]), - // A non-integral key inside the table is as absent as one outside it. - ...mapIndex.lines.map((line) => ` ${line}`), - ` br i1 ${mapIndex.ok}, label %slot, label %outside`, - `slot:`, - sz === "i64" - ? ` %i = add i64 ${mapIndex.wide}, 0` - : ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, - ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 15`, - ` %dense = load ptr, ptr %dp`, - ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, - ` %s = load i32, ptr %sp`, - ` %empty = icmp eq i32 %s, 0`, - ` br i1 %empty, label %miss, label %hit`, - `hit:`, - ` %e32 = sub i32 %s, 1`, - sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, - ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, - ` %entries = load ptr, ptr %ep`, - ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, - ` ret ptr %found`, - `miss:`, - ` ret ptr null`, - `outside:`, - ` %dense.any = icmp ne ${sz} %nd, 0`, - ` br i1 %dense.any, label %miss, label %slow`, - `slow:`, - ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, - ` ret ptr %hashed`, - `}`, - ``, - // Hashing (scr_map_find_f64): the SameValueZero key word, its - // scr_key_mix hash (zero remapped to one) and the absent-key filter. - `define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, - `entry:`, - ` %nan = fcmp uno double %k, %k`, - ` %zero = fcmp oeq double %k, 0.0`, - ` %kz = select i1 %zero, double 0.0, double %k`, - ` %kzb = bitcast double %kz to i64`, - ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, - ` %x1s = lshr i64 %kb, 30`, - ` %x1 = xor i64 %kb, %x1s`, - ` %x2 = mul i64 %x1, -4658895280553007687`, - ` %x3s = lshr i64 %x2, 27`, - ` %x3 = xor i64 %x2, %x3s`, - ` %x4 = mul i64 %x3, -7723592293110705685`, - ` %x5s = lshr i64 %x4, 31`, - ` %x5 = xor i64 %x4, %x5s`, - ` %hz = icmp eq i64 %x5, 0`, - ` %h = select i1 %hz, i64 1, i64 %x5`, - ` %f1s = lshr i64 %h, 44`, - ` %f1a = and i64 %f1s, 63`, - ` %f1 = shl i64 1, %f1a`, - ` %f2s = lshr i64 %h, 50`, - ` %f2a = and i64 %f2s, 63`, - ` %f2 = shl i64 1, %f2a`, - ` %fb = or i64 %f1, %f2`, - ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, - ` %filter = load i64, ptr %fp`, - ` %fm = and i64 %filter, %fb`, - ` %fin = icmp eq i64 %fm, %fb`, - ` br i1 %fin, label %table, label %miss`, - `table:`, - ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, - ...(sz === "i64" - ? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`] - : [` %nb = load i32, ptr %nbp`]), - ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, - ` %ents = load ptr, ptr %eps`, - ` %linear = icmp eq ${sz} %nb, 0`, - ` br i1 %linear, label %scan, label %buckets`, - // Small maps keep no bucket table: scan the cached entry hashes. - `scan:`, - ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, - ...(sz === "i64" - ? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`] - : [` %ne = load i32, ptr %nep`]), - ` br label %scan.loop`, - `scan.loop:`, - ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, - ` %sdone = icmp eq ${sz} %si, %ne`, - ` br i1 %sdone, label %miss, label %scan.entry`, - `scan.entry:`, - ` %se = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %si`, - ` %seh = getelementptr inbounds %ScrMapEntry, ptr %se, i32 0, i32 2`, - ` %sh = load i64, ptr %seh`, - ` %sk = load i64, ptr %se`, - ` %shq = icmp eq i64 %sh, %h`, - ` %skq = icmp eq i64 %sk, %kb`, - ` %sq = and i1 %shq, %skq`, - ` br i1 %sq, label %scan.found, label %scan.next`, - `scan.found:`, - ` ret ptr %se`, - `scan.next:`, - ` %si.next = add ${sz} %si, 1`, - ` br label %scan.loop`, - // Compact tables tag each 24-bit entry index (+1) with the hash's - // top byte; wider tables take the runtime lookup. - `buckets:`, - ` %wide = icmp ugt ${sz} %nb, 16777216`, - ` br i1 %wide, label %runtime, label %probe`, - `probe:`, - ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, - ` %bk = load ptr, ptr %bp`, - ` %mask = sub ${sz} %nb, 1`, - ` %ht = lshr i64 %h, 32`, - ` %ht32 = trunc i64 %ht to i32`, - ` %tag = and i32 %ht32, -16777216`, - sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, - ` %i0 = and ${sz} %hi, %mask`, - ` br label %probe.loop`, - `probe.loop:`, - ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, - ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, - ` %b = load i32, ptr %bep`, - ` %bempty = icmp eq i32 %b, 0`, - ` br i1 %bempty, label %miss, label %probe.tag`, - `probe.tag:`, - ` %bt = and i32 %b, -16777216`, - ` %btq = icmp eq i32 %bt, %tag`, - ` br i1 %btq, label %probe.entry, label %probe.next`, - `probe.entry:`, - ` %bix = and i32 %b, 16777215`, - ` %bix1 = sub i32 %bix, 1`, - sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, - ` %pe = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %bi`, - ` %peh = getelementptr inbounds %ScrMapEntry, ptr %pe, i32 0, i32 2`, - ` %ph = load i64, ptr %peh`, - ` %pk = load i64, ptr %pe`, - ` %phq = icmp eq i64 %ph, %h`, - ` %pkq = icmp eq i64 %pk, %kb`, - ` %pq = and i1 %phq, %pkq`, - ` br i1 %pq, label %probe.found, label %probe.next`, - `probe.found:`, - ` ret ptr %pe`, - `probe.next:`, - ` %pi.inc = add ${sz} %pi, 1`, - ` %pi.next = and ${sz} %pi.inc, %mask`, - ` br label %probe.loop`, - `miss:`, - ` ret ptr null`, - `runtime:`, - ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k) memory(read) nounwind willreturn`, - ` ret ptr %r`, - `}`, - ``, - ); - } - // Map/Set iteration steps read the entries array inline: an index is - // live when it is below the entry count and its entry's cached hash is - // nonzero (a tombstone's is zero), as scr_map_iter_live decides. The - // entry count is read again at every step, so entries appended during - // the iteration are visited. - if (this.decls.has(MAP_ITER_DECL)) { - const sz = this.sizeType; - defs.push( - `define internal ptr @sc_map_iter_entry(ptr %m, double %i) ${FN_ATTRS} {`, - `entry:`, - ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, - ` %n = load i32, ptr %np`, - ` %nf = uitofp i32 %n to double`, - ` %lo = fcmp oge double %i, 0.0`, - ` %hi = fcmp olt double %i, %nf`, - ` %in = and i1 %lo, %hi`, - ` br i1 %in, label %load, label %none`, - `load:`, - ` %ix = fptoui double %i to ${sz}`, - ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, - ` %entries = load ptr, ptr %ep`, - ` %e = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %ix`, - ` %hp = getelementptr inbounds %ScrMapEntry, ptr %e, i32 0, i32 2`, - ` %h = load i64, ptr %hp`, - ` %live = icmp ne i64 %h, 0`, - ` br i1 %live, label %yes, label %none`, - `yes:`, - ` ret ptr %e`, - `none:`, - ` ret ptr null`, - `}`, - ``, - `define internal double @sc_map_iter_count(ptr %m) ${FN_ATTRS} {`, - `entry:`, - ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, - ` %n = load i32, ptr %np`, - ` %nf = uitofp i32 %n to double`, - ` ret double %nf`, - `}`, - ``, - ); - } - // String-key Map/Set lookups test the map's absent-key filter inline - // (scr_map.c scr_map_filter_bits): a key whose two filter bits are not - // both set is absent, so most misses on small and medium maps return - // without a call. On 64-bit targets the key's cached hash h is the high - // half of its capacity word, the runtime's widened hash is (h << 32) | h, - // and the filter reads its bits 44..55, which are the capacity word's - // own. A key without a cached hash, and every possible hit, take the - // runtime lookup. - if (this.decls.has(STRING_MAP_ENTRY_DECL)) { - defs.push( - `define internal ptr @sc_map_entry_str(ptr %m, ptr %k) ${FN_ATTRS} {`, - `entry:`, - ...(this.sizeType === "i64" - ? [ - ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, - ` %filter = load i64, ptr %fp`, - ` %capp = getelementptr inbounds %ScrStr, ptr %k, i32 0, i32 2`, - ` %cap = load i64, ptr %capp`, - ` %cached = icmp uge i64 %cap, 4294967296`, - ` br i1 %cached, label %test, label %slow`, - `test:`, - ` %s1 = lshr i64 %cap, 44`, - ` %i1 = and i64 %s1, 63`, - ` %b1 = shl i64 1, %i1`, - ` %s2 = lshr i64 %cap, 50`, - ` %i2 = and i64 %s2, 63`, - ` %b2 = shl i64 1, %i2`, - ` %bits = or i64 %b1, %b2`, - ` %have = and i64 %filter, %bits`, - ` %maybe = icmp eq i64 %have, %bits`, - ` br i1 %maybe, label %slow, label %absent`, - `absent:`, - ` ret ptr null`, - ] - : [` br label %slow`]), - `slow:`, - ` %r = call ptr @scr_map_entry_str(ptr %m, ptr %k)`, - ` ret ptr %r`, - `}`, - ``, - ); - } - // String comparisons enter through small internal wrappers that LLVM - // inlines at each site: interned literals and shared values compare by - // identity, and equality rejects different byte lengths, before any - // runtime call (scr_str_eq/scr_str_cmp keep the complete semantics). - if (this.decls.has(`declare zeroext i1 @scr_str_eq(ptr, ptr)`)) { - defs.push( - `define internal zeroext i1 @sc_str_eq(ptr %a, ptr %b) ${FN_ATTRS} {`, - `entry:`, - ` %same = icmp eq ptr %a, %b`, - ` br i1 %same, label %yes, label %lengths`, - `lengths:`, - ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, - ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, - ` %alen = load ${this.sizeType}, ptr %alen.p`, - ` %blen = load ${this.sizeType}, ptr %blen.p`, - ` %samelen = icmp eq ${this.sizeType} %alen, %blen`, - ` br i1 %samelen, label %bytes, label %no`, - `bytes:`, - ` %eq = call zeroext i1 @scr_str_eq(ptr %a, ptr %b)`, - ` ret i1 %eq`, - `yes:`, - ` ret i1 true`, - `no:`, - ` ret i1 false`, - `}`, - ``, - ); - } - for (const [text, name] of this.literalEqualities) { - defs.push( - ...literalEqualityDefinition( - name, - wtf8Bytes(text), - this.internLiteral(text), - this.sizeType, - FN_ATTRS, - ), - ``, - ); - } - // String length and charCodeAt read proven-ASCII strings inline: bit 31 - // of the capacity word (SCR_STR_ASCII_BIT, the low half's sign bit on - // little-endian targets) makes UTF-16 units bytes. Unproven strings and - // anything but an in-range index take the runtime's UTF-16 mapping. - // A scan's receiver reads inline too where runtime state is ordinary - // global storage (ScrStrScan; SCR_TL makes it thread-local otherwise). - const scanInline = this.mod.workers !== true && this.mod.lib?.threadInstances !== true; - let scanDeclared = false; - for (const helper of STRING_READ_HELPERS) { - if (!this.decls.has(helper.decl)) continue; - if (scanInline && !scanDeclared) { - defs.push(stringScanDecl(this.sizeType), ``); - scanDeclared = true; - } - defs.push(...helper.define(this.sizeType, FN_ATTRS, scanInline), ``); - } - // Ordering decides inline from the first differing bytes. The bytes - // before them are equal, so both strings are at the same position of a - // character: either both bytes continue characters with the same lead - // byte, or both start a character. Byte order is the UTF-16 order for - // two continuation bytes, and for two lead bytes when one starts a - // character below U+D000, whose UTF-16 unit is below every unit of a - // character with a lead byte of at least 0xED. Between lead bytes of at - // least 0xED, a supplementary character (0xF0..0xF4, a high surrogate - // unit) orders below U+E000..U+FFFF (0xEE, 0xEF), and a lead byte of - // 0xED (U+D000..U+D7FF or a lone surrogate) takes the runtime. - // Differing words without a byte of at least 0xED decide in byte - // order; other differing words decide by their first differing byte, - // and strings shorter than a word by their first byte. Otherwise a - // proven-ASCII string on either side orders by bytes (scr_str_cmp), and - // other pairs call scr_str_cmp_u16_bytes, which only reads the two - // strings, so LLVM can merge repeated comparisons of one pair. Callers - // only test the sign of the result. - if (this.decls.has(`declare i32 @scr_str_cmp_u16(ptr, ptr)`)) { - const fn = "scr_str_cmp_u16"; - this.declare("declare i64 @llvm.bswap.i64(i64)"); - this.declare("declare i64 @llvm.cttz.i64(i64, i1 immarg)"); - this.declare("declare i8 @llvm.umin.i8(i8, i8)"); - this.declare("declare i32 @scr_str_cmp(ptr, ptr)"); - this.declare("declare i32 @scr_str_cmp_u16_bytes(ptr, ptr)"); - defs.push( - `define internal i32 @${fn.replace(/^scr_/, "sc_")}(ptr %a, ptr %b) ${FN_ATTRS} {`, - `entry:`, - ` %same = icmp eq ptr %a, %b`, - ` br i1 %same, label %equal, label %lengths`, - `lengths:`, - ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, - ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, - ` %alen = load ${this.sizeType}, ptr %alen.p`, - ` %blen = load ${this.sizeType}, ptr %blen.p`, - ` %aempty = icmp eq ${this.sizeType} %alen, 0`, - ` %bempty = icmp eq ${this.sizeType} %blen, 0`, - ` %empty = or i1 %aempty, %bempty`, - ` br i1 %empty, label %order, label %words`, - // Both at least 8 bytes: the first word of each. - `words:`, - ` %along = icmp uge ${this.sizeType} %alen, 8`, - ` %blong = icmp uge ${this.sizeType} %blen, 8`, - ` %long = and i1 %along, %blong`, - ` br i1 %long, label %word, label %first`, - `word:`, - ` %awp = getelementptr inbounds %ScrStr, ptr %a, i32 1`, - ` %bwp = getelementptr inbounds %ScrStr, ptr %b, i32 1`, - ` %aw = load i64, ptr %awp, align 1`, - ` %bw = load i64, ptr %bwp, align 1`, - ` %wdiffer = icmp ne i64 %aw, %bw`, - ` br i1 %wdiffer, label %differ, label %wsame`, - // Equal first words and one length of at most 16 bytes: the word - // ending at that length covers the rest, and any difference lies - // past the equal first word. - `wsame:`, - ` %samelen = icmp eq ${this.sizeType} %alen, %blen`, - ` %short = icmp ule ${this.sizeType} %alen, 16`, - ` %tailable = and i1 %samelen, %short`, - ` br i1 %tailable, label %tail, label %order`, - `tail:`, - ` %toff = sub ${this.sizeType} %alen, 8`, - ` %atp = getelementptr inbounds i8, ptr %awp, ${this.sizeType} %toff`, - ` %btp = getelementptr inbounds i8, ptr %bwp, ${this.sizeType} %toff`, - ` %at = load i64, ptr %atp, align 1`, - ` %bt = load i64, ptr %btp, align 1`, - ` %tdiffer = icmp ne i64 %at, %bt`, - ` br i1 %tdiffer, label %differ, label %equal`, - `first:`, - ` %adata = getelementptr inbounds %ScrStr, ptr %a, i32 1`, - ` %bdata = getelementptr inbounds %ScrStr, ptr %b, i32 1`, - ` %x = load i8, ptr %adata`, - ` %y = load i8, ptr %bdata`, - ` %fdiffer = icmp ne i8 %x, %y`, - ` br i1 %fdiffer, label %bytes, label %order`, - // Two differing words. When neither has a byte of at least 0xED - // (a byte b is when its high bit and bit 7 of (b & 0x7f) + 0x13 are - // set), byte-swapped little-endian words compare in byte order. - // Otherwise the lowest set byte of their xor is the first differing - // byte. - `differ:`, - ` %dx = phi i64 [ %aw, %word ], [ %at, %tail ]`, - ` %dy = phi i64 [ %bw, %word ], [ %bt, %tail ]`, - ` %xl = and i64 %dx, 9187201950435737471`, - ` %yl = and i64 %dy, 9187201950435737471`, - ` %xa = add i64 %xl, 1374463283923456787`, - ` %ya = add i64 %yl, 1374463283923456787`, - ` %xh = and i64 %dx, %xa`, - ` %yh = and i64 %dy, %ya`, - ` %hh = or i64 %xh, %yh`, - ` %whigh = and i64 %hh, -9187201950435737472`, - ` %dlow = icmp eq i64 %whigh, 0`, - ` br i1 %dlow, label %wordorder, label %locate`, - `wordorder:`, - ` %xsw = call i64 @llvm.bswap.i64(i64 %dx)`, - ` %ysw = call i64 @llvm.bswap.i64(i64 %dy)`, - ` %wbelow = icmp ult i64 %xsw, %ysw`, - ` %wsign = select i1 %wbelow, i32 -1, i32 1`, - ` ret i32 %wsign`, - `locate:`, - ` %dxor = xor i64 %dx, %dy`, - ` %dtz = call i64 @llvm.cttz.i64(i64 %dxor, i1 true)`, - ` %dshift = and i64 %dtz, 56`, - ` %dxs = lshr i64 %dx, %dshift`, - ` %dys = lshr i64 %dy, %dshift`, - ` %dxb = trunc i64 %dxs to i8`, - ` %dyb = trunc i64 %dys to i8`, - ` br label %bytes`, - // The first differing bytes. - `bytes:`, - ` %bx = phi i8 [ %x, %first ], [ %dxb, %locate ]`, - ` %by = phi i8 [ %y, %first ], [ %dyb, %locate ]`, - ` %bmin = call i8 @llvm.umin.i8(i8 %bx, i8 %by)`, - ` %blow = icmp ult i8 %bmin, -19`, - ` br i1 %blow, label %byte, label %high`, - // Both at least 0xED. A lone surrogate (0xED) takes the runtime; - // supplementary lead bytes (0xF0..0xF4) order below 0xEE and 0xEF - // once 0x10 is subtracted from them. - `high:`, - ` %xed = icmp eq i8 %bx, -19`, - ` %yed = icmp eq i8 %by, -19`, - ` %ed = or i1 %xed, %yed`, - ` br i1 %ed, label %order, label %planes`, - `planes:`, - ` %xsupp = icmp uge i8 %bx, -16`, - ` %ysupp = icmp uge i8 %by, -16`, - ` %xlow = add i8 %bx, -16`, - ` %ylow = add i8 %by, -16`, - ` %xk = select i1 %xsupp, i8 %xlow, i8 %bx`, - ` %yk = select i1 %ysupp, i8 %ylow, i8 %by`, - ` %kbelow = icmp ult i8 %xk, %yk`, - ` %ksign = select i1 %kbelow, i32 -1, i32 1`, - ` ret i32 %ksign`, - `byte:`, - ` %below = icmp ult i8 %bx, %by`, - ` %sign = select i1 %below, i32 -1, i32 1`, - ` ret i32 %sign`, - `order:`, - ` %acw.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 2`, - ` %bcw.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 2`, - ` %acw = load i32, ptr %acw.p`, - ` %bcw = load i32, ptr %bcw.p`, - ` %cws = or i32 %acw, %bcw`, - ` %anyascii = icmp slt i32 %cws, 0`, - ` br i1 %anyascii, label %memorder, label %units`, - `memorder:`, - ` %m = call i32 @scr_str_cmp(ptr %a, ptr %b)`, - ` ret i32 %m`, - `units:`, - ` %c = call i32 @scr_str_cmp_u16_bytes(ptr %a, ptr %b) nounwind willreturn memory(argmem: read)`, - ` ret i32 %c`, - `equal:`, - ` ret i32 0`, - `}`, - ``, - ); - } - // The declarations these helpers added must land in the extern block, - // which already flushed — append here instead (LLVM is order-free). - return defs.length > 0 ? [...defs] : defs; + helperDefs(): string[] { + return helperDefs(this); } /** The inline RC helpers every call site requested, emitted last so * requests from main/library epilogues are included. Slow-path * declarations a late request added after the extern block flushed land * here too. */ - private inlineRcTail(flushedDecls: ReadonlySet): string[] { + inlineRcTail(flushedDecls: ReadonlySet): string[] { const late = inlineRcDecls(this.shapeHost).filter((d) => !flushedDecls.has(d)); // NULL-skipping retain wrappers of nullable unions; a runtime inner // retain requested after the extern block flushed is declared here. @@ -3857,7 +1631,7 @@ export class LlEmitter { /** Env-signature wrappers + interned immortal closures for declared * functions used as values (the runtime ABI’s sc_w_/sc_fc_ pair): every * mention of `f` yields the same pointer, so `f === f` holds. */ - private emitFnValueDefs(): string[] { + emitFnValueDefs(): string[] { const out: string[] = []; for (const name of this.fnValues) { const fn = this.fnByName.get(name)!; @@ -3893,74 +1667,12 @@ export class LlEmitter { } return out; } - - /** The argument-pack ABI and trampoline prefix shared by async functions - * and generators. Their promise/generator completion and spawn tails stay - * with the callers below. */ - private emitArgPackAndTrampolinePrologue(fn: IrFunction): LlArgPackAndTrampolinePrologue { - const pack = mangleArgPack(fn.name); - const lifted = fn.captures !== undefined; - const fieldTys = [...(lifted ? ["ptr"] : []), ...fn.params.map((p) => this.llType(p.type))]; - const definitions = [`%${pack} = type { ${fieldTys.join(", ") || "i8"} } ; ${fn.name} args`]; - const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${this.sizeType})`; - - // Async argument packs live from the spawn to the trampoline's first - // instructions, so they take the runtime's small-block allocator. - // Generator packs keep malloc: the runtime frees never-started ones. - const smallPack = fn.generator === undefined; - const packAlloc = smallPack ? "scr_rt_calloc" : "malloc"; - const packFree = smallPack ? "scr_rt_free" : "free"; - this.declare(`declare void @${packFree}(ptr)`); - this.declare(`declare ptr @${packAlloc}(${this.sizeType})`); - - const tr: string[] = [ - `define internal void @${mangleTrampoline(fn.name)}(ptr %self, ptr %ap) ${FN_ATTRS} {`, - `entry:`, - ]; - const loads: string[] = []; - fieldTys.forEach((ty, i) => { - tr.push( - ` %fp${i} = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${i}`, - ` %a${i} = load ${ty}, ptr %fp${i}`, - ); - loads.push(`${ty} %a${i}`); - }); - tr.push(` call void @${packFree}(ptr %ap)`); - const ret = fn.returnType; - const retTy = this.llType(ret); - const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${loads.join(", ")})`; - tr.push(retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`); - if (lifted && !this.coroLowered(fn)) { - tr.push(` call void @scr_closure_release(ptr %a0)`); - } - - const spawnParams = fieldTys.map((ty, i) => `${ty} %a${i}`); - const argPackLines = [ - ` %ap = call ptr @${packAlloc}(${this.sizeType} ${sizeOf})`, - ` %isnull = icmp eq ptr %ap, null`, - ` br i1 %isnull, label %oom, label %ok`, - `oom:`, - ` call void @sc_oom()`, - ` unreachable`, - `ok:`, - ]; - if (lifted) { - // scr_closure_retain is a header static inline — the `_v` twin is - // the exported symbol. - argPackLines.push(` %env = call ptr @scr_closure_retain_v(ptr %a0)`); - } - fieldTys.forEach((ty, i) => { - const src = lifted && i === 0 ? "%env" : `%a${i}`; - argPackLines.push( - ` %sp${i} = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${i}`, - ` store ${ty} ${src}, ptr %sp${i}`, - ); - }); - return { definitions, pack, lifted, fieldTys, ret, tr, spawnParams, argPackLines }; + emitArgPackAndTrampolinePrologue(fn: IrFunction): LlArgPackAndTrampolinePrologue { + return emitArgPackAndTrampolinePrologue(this, fn); } /** Settles `promise` from a clean async body result `%r` (moved in). */ - private asyncFulfillLines(ret: IrType, promise: string): string[] { + asyncFulfillLines(ret: IrType, promise: string): string[] { switch (ret.kind) { case "void": this.declare(`declare void @scr_promise_fulfill_void(ptr)`); @@ -3987,34 +1699,12 @@ export class LlEmitter { } } } - - /** Async functions whose calls can skip the fiber (scr_async_inline_*). - * A body qualifies only when nothing it runs can park its execution - * context: its emitted text, and the text of every module function it - * mentions (transitively), contains no suspension entry point. Such a - * body always completes before the call returns, which is exactly the - * behavior of a fiber that never suspends. Excluded conservatively: - * wasm32 (its coroutine lowering owns async bodies), module-evaluation - * bodies (their promise caches), - * `dyn` results (resolution may adopt a thenable), and every program - * that uses node:test subtests — `t.test()` runs a subtest inline and - * awaits it on the CURRENT fiber even from synchronous code. Worker - * programs keep fibers too: their fiber epilogue owns context - * termination (a stopping worker rejects and observes the promise), which - * the fiberless wrapper does not reproduce. A missed suspension still - * cannot reorder execution undetected: the runtime traps when a context parks while a - * fiberless body is on its stack. */ - private fiberlessAsyncFunctions(): Set { - const parks = this.parkingFunctions(); - const eligible = parks === null ? new Set() : this.neverParkingAsync(parks); - for (const name of this.inlineAwaitCallees) - if (!eligible.has(name)) - throw new InternalCompilerError(`llvm emitter bug: inline await of suspending ${name}`); - return eligible; + fiberlessAsyncFunctions(): Set { + return fiberlessAsyncFunctions(this); } /** The async functions `parks` leaves fiberless (fiberlessAsyncFunctions). */ - private neverParkingAsync(parks: ReadonlySet): Set { + neverParkingAsync(parks: ReadonlySet): Set { const eligible = new Set(); for (const fn of this.mod.functions) { if (fn.async !== true || fn.generator !== undefined) continue; @@ -4025,50 +1715,14 @@ export class LlEmitter { } return eligible; } - - /** The module functions whose bodies may park their execution context: - * their emitted text mentions a suspension entry point (SUSPENDS), or - * they mention such a function (transitively). Null where async bodies - * must keep fibers regardless: wasm32 (its coroutine lowering owns async - * bodies), worker programs, and programs that use node:test subtests, - * whose `t.test()` awaits on the CURRENT fiber even from synchronous - * code. */ - private parkingFunctions(): Set | null { - if (this.wasi || this.mod.workers === true) return null; - for (const decl of this.decls) if (/@scr_test_sub\b/.test(decl)) return null; - for (const text of this.fnDefText.values()) if (/@scr_test_sub\b/.test(text)) return null; - // Reverse mention edges, then propagate "may park" to every mentioner. - const mentionedBy = new Map>(); - const parks = new Set(); - const work: string[] = []; - for (const [name, text] of this.fnDefText) { - if (SUSPENDS.test(text)) { - parks.add(name); - work.push(name); - } - for (const callee of this.mentionedFunctions(text)) { - if (callee === name) continue; - let callers = mentionedBy.get(callee); - if (callers === undefined) mentionedBy.set(callee, (callers = new Set())); - callers.add(name); - } - } - while (work.length > 0) { - const callers = mentionedBy.get(work.pop()!); - if (callers === undefined) continue; - for (const caller of callers) { - if (parks.has(caller)) continue; - parks.add(caller); - work.push(caller); - } - } - return parks; + parkingFunctions(): Set | null { + return parkingFunctions(this); } private mangledFunctions: Map | null = null; /** The module functions whose ordinary or borrowed body `text` mentions. */ - private *mentionedFunctions(text: string): Generator { + *mentionedFunctions(text: string): Generator { if (this.mangledFunctions === null) { this.mangledFunctions = new Map(); for (const fn of this.mod.functions) { @@ -4095,7 +1749,7 @@ export class LlEmitter { * otherwise the fiber form stays. Bodies that never suspend stay * fiberless, and module evaluation bodies and `dyn` results keep fibers * as they do there. The runtime traps if a stackless body ever parks. */ - private selectStacklessAsync(fnDefs: string[]): void { + selectStacklessAsync(fnDefs: string[]): void { if (!this.stacklessAsyncAllowed) return; const parks = this.parkingFunctions(); if (parks === null) return; @@ -4129,7 +1783,7 @@ export class LlEmitter { * caller's stack between scr_async_inline_enter/leave and settles the * fresh promise from its outcome — the fiber trampoline's epilogue, * without the fiber. */ - private emitFiberlessAsyncSpawn(fn: IrFunction, spawnParams: string[]): string[] { + emitFiberlessAsyncSpawn(fn: IrFunction, spawnParams: string[]): string[] { const ret = fn.returnType; const retTy = this.llType(ret); const lifted = fn.captures !== undefined; @@ -4194,7 +1848,7 @@ export class LlEmitter { * body on the caller's stack between scr_async_inline_enter/exit, as in * its spawn wrapper, returning the result and leaving a throw pending for * the awaiting coroutine (emitCoroInlineAwait). */ - private emitAsyncInlineAwaitCall(fn: IrFunction): string[] { + emitAsyncInlineAwaitCall(fn: IrFunction): string[] { const retTy = this.llType(fn.returnType); const params = fn.params.map((p, i) => `${this.llType(p.type)} %a${i}`); const args = params.join(", "); @@ -4249,506 +1903,23 @@ export class LlEmitter { this.emitPendingCheck(); return value; } - - /** The await-call entry of an async function (`sc_aw_*`): one direct - * `await f(...)` as a call returning the awaited value, so the call site - * needs no promise of its own. A fiberless body runs on the caller's - * stack as in its spawn wrapper; a clean result then takes the await's - * one microtask turn (scr_await_hop) and is returned without ever - * materializing the promise, which nothing else could observe. A throw - * builds the rejected promise exactly as the spawn wrapper would and - * awaits it, so the rejection reaches the awaiter on the same turn. Other - * bodies pack their arguments as the spawn wrapper does and enter - * scr_async_spawn_await_*, which parks the caller on the promise as soon - * as the body first suspends. The adopting entry (`sc_ad_*`, for - * `return f(...)` in an async function) adds the turn of the then job - * that adopts the returned promise, before the await proper. */ - private emitAsyncAwaitCall( - fn: IrFunction, - argPackLines: string[] | null, - adopt = false, - ): string[] { - const ret = fn.returnType; - const retTy = this.llType(ret); - const params = fn.params.map((p, i) => `${this.llType(p.type)} %a${i}`); - const awaitLine = (dest: string, promise: string): string => { - switch (ret.kind) { - case "void": - this.declare(`declare void @scr_await_void(ptr)`); - return ` call void @scr_await_void(ptr ${promise})`; - case "f64": - case "date": - this.declare(`declare double @scr_await_f64(ptr)`); - return ` ${dest} = call double @scr_await_f64(ptr ${promise})`; - case "bool": - this.declare(`declare zeroext i1 @scr_await_bool(ptr)`); - return ` ${dest} = call zeroext i1 @scr_await_bool(ptr ${promise})`; - case "string": - this.declare(`declare ptr @scr_await_str(ptr)`); - return ` ${dest} = call ptr @scr_await_str(ptr ${promise})`; - default: - this.declare(`declare ptr @scr_await_ref(ptr)`); - return ` ${dest} = call ptr @scr_await_ref(ptr ${promise})`; - } - }; - const retLine = (v: string): string => - retTy === "void" ? ` ret void` : ` ret ${retTy} ${v}`; - const head = adopt - ? `define internal ${retTy} @${mangleAsyncAdoptCall(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; adopt ${fn.name}` - : `define internal ${retTy} @${mangleAsyncAwaitCall(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; await ${fn.name}`; - const args = params.join(", "); - if (adopt && argPackLines !== null) { - // A body that may suspend settles later than the adoption's then job - // runs: materialize its promise, take that turn, then await it. - this.declare(`declare void @scr_await_hop()`); - this.declare(`declare void @scr_promise_release(ptr)`); - return [ - head, - `entry:`, - ` %p = call ptr @${mangleAsyncSpawn(fn.name)}(${args})`, - ` call void @scr_await_hop()`, - awaitLine("%v", "%p"), - ` call void @scr_promise_release(ptr %p)`, - retLine("%v"), - `}`, - ``, - ]; - } - if (argPackLines !== null && this.stacklessAsync.has(fn.name)) { - // A stackless body's eager prefix already runs on the awaiter's - // stack, which then awaits the promise like the two separate steps. - this.declare(`declare void @scr_promise_release(ptr)`); - return [ - head, - `entry:`, - ` %p = call ptr @${mangleAsyncSpawn(fn.name)}(${args})`, - awaitLine("%v", "%p"), - ` call void @scr_promise_release(ptr %p)`, - retLine("%v"), - `}`, - ``, - ]; - } - if (argPackLines !== null) { - const kind = - ret.kind === "void" - ? "void" - : ret.kind === "f64" || ret.kind === "date" - ? "f64" - : ret.kind === "bool" - ? "bool" - : ret.kind === "string" - ? "str" - : "ref"; - const cret = kind === "bool" ? "zeroext i1" : retTy; - this.declare(`declare ${cret} @scr_async_spawn_await_${kind}(ptr, ptr)`); - this.needOom(); - const call = `call ${cret} @scr_async_spawn_await_${kind}(ptr @${mangleTrampoline(fn.name)}, ptr %ap)`; - return [ - head, - `entry:`, - ...argPackLines, - kind === "void" ? ` ${call}` : ` %v = ${call}`, - retLine("%v"), - `}`, - ``, - ]; - } - this.declare(`declare void @scr_async_inline_enter(ptr)`); - this.declare(`declare void @scr_async_inline_exit(ptr)`); - this.declare(`declare void @scr_await_hop()`); - this.declare(`declare ptr @scr_promise_new()`); - this.declare(`declare void @scr_promise_reject_pending(ptr)`); - this.declare(`declare void @scr_promise_release(ptr)`); - const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${args})`; - const out = [ - head, - `entry:`, - ` %frame = alloca { ptr, ptr }`, - ` call void @scr_async_inline_enter(ptr %frame)`, - retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`, - ...this.pendingTestLines("%pend"), - ` call void @scr_async_inline_exit(ptr %frame)`, - // Adopting: the then job's turn comes before the settled promise's - // own reaction, whichever way the body completed. - ...(adopt ? [` call void @scr_await_hop()`] : []), - ` br i1 %pend, label %thrown, label %clean`, - `clean:`, - ` call void @scr_await_hop()`, - retLine("%r"), - `thrown:`, - ]; - if (ret.kind !== "void" && isRefCounted(ret)) { - // An escaping throw means %r is the never-read dummy (NULL). - out.push(` call void ${releaseSym(this.shapeHost, ret)}(ptr %r)`); - } - out.push( - ` %p = call ptr @scr_promise_new()`, - ` call void @scr_promise_reject_pending(ptr %p)`, - awaitLine("%v", "%p"), - ` call void @scr_promise_release(ptr %p)`, - retLine("%v"), - `}`, - ``, - ); - return out; + emitAsyncAwaitCall(fn: IrFunction, argPackLines: string[] | null, adopt = false): string[] { + return emitAsyncAwaitCall(this, fn, argPackLines, adopt); } - - /** Per-async-function machinery — async.ts's scaffolding, .ll - * flavored: an argument-pack struct type, a fiber trampoline (unpacks, - * frees the pack, runs the ordinary compiled body, settles the - * promise — fulfilling on clean return, leaving a pending exception - * for the runtime to reject with), and a spawn wrapper call sites and - * closures enter through (packs the args +1, scr_async_spawn runs the - * fiber eagerly to its first suspension and returns the promise). */ - private emitAsyncScaffolding(): string[] { - const out: string[] = []; - if (this.wasi) { - this.declare(`declare void @llvm.coro.resume(ptr)`); - this.declare(`declare void @llvm.coro.destroy(ptr)`); - out.push( - `define void @scr_wasi_coro_resume(ptr %handle) ${FN_ATTRS} {`, - `entry:`, - ` call void @llvm.coro.resume(ptr %handle)`, - ` ret void`, - `}`, - `define void @scr_wasi_coro_destroy(ptr %handle) ${FN_ATTRS} {`, - `entry:`, - ` call void @llvm.coro.destroy(ptr %handle)`, - ` ret void`, - `}`, - ``, - ); - } - const fiberless = this.fiberlessAsyncFunctions(); - for (const fn of this.mod.functions) { - if (fn.async !== true || fn.generator !== undefined) continue; - if (fiberless.has(fn.name) && this.awaitCallees.has(fn.name)) { - for (const line of this.emitAsyncAwaitCall(fn, null)) out.push(line); - } - if (fiberless.has(fn.name) && this.adoptCallees.has(fn.name)) { - for (const line of this.emitAsyncAwaitCall(fn, null, true)) out.push(line); - } - if (this.inlineAwaitCallees.has(fn.name)) { - for (const line of this.emitAsyncInlineAwaitCall(fn)) out.push(line); - } - if (fiberless.has(fn.name)) { - const fieldTys = [ - ...(fn.captures !== undefined ? ["ptr"] : []), - ...fn.params.map((p) => this.llType(p.type)), - ]; - const params = fieldTys.map((ty, i) => `${ty} %a${i}`); - for (const line of this.emitFiberlessAsyncSpawn(fn, params)) out.push(line); - continue; - } - const { definitions, ret, tr, spawnParams, argPackLines } = - this.emitArgPackAndTrampolinePrologue(fn); - for (const line of definitions) out.push(line); - if (this.awaitCallees.has(fn.name)) { - for (const line of this.emitAsyncAwaitCall(fn, argPackLines)) out.push(line); - } - if (this.adoptCallees.has(fn.name)) { - for (const line of this.emitAsyncAwaitCall(fn, argPackLines, true)) out.push(line); - } - const stackless = this.stacklessAsync.has(fn.name); - const spawn = stackless ? "scr_async_spawn_stackless" : "scr_async_spawn"; - this.declare(`declare ptr @scr_fiber_promise(ptr)`); - this.declare(`declare ptr @${spawn}(ptr, ptr)`); - this.needOom(); - if (this.wasi || stackless) { - // The coroutine body settles its own promise and owns the retained - // lifted environment until final suspension. Its initial call - // returns here at the first suspend (or final suspend). - tr.push(` ret void`, `}`, ``); - } else { - if (fn.captures !== undefined) { - this.declare(`declare void @scr_closure_release(ptr)`); - } - tr.push( - ...this.pendingTestLines("%pend"), - ` br i1 %pend, label %thrown, label %clean`, - `clean:`, - ` %pr = call ptr @scr_fiber_promise(ptr %self)`, - ); - for (const line of this.asyncFulfillLines(ret, "%pr")) tr.push(line); - tr.push(` ret void`, `thrown:`); - if (ret.kind !== "void" && isRefCounted(ret)) { - // An escaping throw means %r is the never-read dummy (NULL). - tr.push(` call void ${releaseSym(this.shapeHost, ret)}(ptr %r)`); - } - tr.push(` ret void`, `}`, ``); - } - for (const line of tr) out.push(line); - - // Spawn wrapper: pack the args (+1 moves in), spawn the fiber. - const cache = fn.asyncCacheGlobal !== undefined ? mangleGlobal(fn.asyncCacheGlobal) : null; - const cycleCache = - fn.asyncCycleCacheGlobal !== undefined ? mangleGlobal(fn.asyncCycleCacheGlobal) : null; - if (cache !== null || cycleCache !== null) { - this.declare(`declare ptr @scr_promise_retain_v(ptr)`); - this.declare(`declare void @scr_promise_release(ptr)`); - } - if (cache !== null) { - this.declare(`declare void @scr_promise_mark_handled(ptr)`); - } - if (fn.captures !== undefined) { - this.declare(`declare ptr @scr_closure_retain_v(ptr)`); - } - const sp: string[] = [ - `define internal ptr @${mangleAsyncSpawn(fn.name)}(${spawnParams.join(", ")}) ${FN_ATTRS} { ; spawn ${fn.name}`, - `entry:`, - ...(cache !== null - ? [ - ` %cached = load ptr, ptr @${cache}`, - ` %cache_hit = icmp ne ptr %cached, null`, - ` br i1 %cache_hit, label %cached_return, label %cache_miss`, - `cached_return:`, - ` %cached_owned = call ptr @scr_promise_retain_v(ptr %cached)`, - ` ret ptr %cached_owned`, - `cache_miss:`, - ] - : []), - ...argPackLines, - ]; - sp.push( - ` %p = call ptr @${spawn}(ptr @${mangleTrampoline(fn.name)}, ptr %ap)`, - ...(cache !== null - ? [ - // The module loader owns this evaluation promise - // immediately. A later sibling can throw before the - // aggregate dependency wait is built, but this rejection - // must never become an unrelated unhandled rejection. - ` call void @scr_promise_mark_handled(ptr %p)`, - ] - : []), - ...(cache !== null - ? [ - ` %cache_owned = call ptr @scr_promise_retain_v(ptr %p)`, - // The eager spawn may have re-entered this guarded module - // through an admitted async cycle and installed a temporary - // cache entry. Drop that owned slot before replacing it - // with the outer evaluation promise. - ` %replaced_cache = load ptr, ptr @${cache}`, - ` call void @scr_promise_release(ptr %replaced_cache)`, - ` store ptr %cache_owned, ptr @${cache}`, - ] - : []), - ...(cycleCache !== null - ? [ - // Eager recursive spawns publish from the inside out. The - // runtime-requested outermost member writes last and is the - // SCC's actual evaluation root. - ` %cycle_cache_owned = call ptr @scr_promise_retain_v(ptr %p)`, - ` %replaced_cycle_cache = load ptr, ptr @${cycleCache}`, - ` call void @scr_promise_release(ptr %replaced_cycle_cache)`, - ` store ptr %cycle_cache_owned, ptr @${cycleCache}`, - ] - : []), - ` ret ptr %p`, - `}`, - ``, - ); - for (const line of sp) out.push(line); - } - for (const line of this.emitGenScaffolding()) out.push(line); - return out; + emitAsyncScaffolding(): string[] { + return emitAsyncScaffolding(this); } /** Whether `fn`'s body is emitted as an LLVM switched coroutine: every * wasm32 async or generator body, native synchronous generators, and the * native async bodies selectStacklessAsync chose. */ - private coroLowered(fn: IrFunction): boolean { + coroLowered(fn: IrFunction): boolean { if (this.wasi) return fn.async === true || fn.generator !== undefined; if (fn.async === true) return fn.generator === undefined && this.stacklessAsync.has(fn.name); return this.stacklessGenerators && fn.generator !== undefined; } - - /** Per-generator-function machinery — the async scaffolding's lazy - * sibling (async.ts's emitGenScaffolding): the same argument pack, - * a fiber trampoline whose epilogue stores the COMPLETION value (or - * consumes the GENRET sentinel, promoting the parked .return value), a - * spawn wrapper that only ALLOCATES the suspended fiber, and the - * never-started teardown that drops the packed (+1) arguments. */ - private emitGenScaffolding(): string[] { - const out: string[] = []; - for (const fn of this.mod.functions) { - if (fn.generator === undefined) continue; - const { definitions, pack, lifted, fieldTys, ret, tr, spawnParams, argPackLines } = - this.emitArgPackAndTrampolinePrologue(fn); - for (const line of definitions) out.push(line); - this.declare(`declare zeroext i1 @scr_exc_genret_pending()`); - this.declare(`declare void @scr_exc_clear()`); - this.declare(`declare ptr @scr_gen_of_fiber(ptr)`); - this.declare(`declare void @scr_gen_ret_to_out(ptr)`); - this.declare(`declare ptr @scr_gen_new(ptr, ptr, ptr)`); - this.needOom(); - const coroBody = this.coroLowered(fn); - if (lifted && !coroBody) { - this.declare(`declare void @scr_closure_release(ptr)`); - } - // Normal completion stores the (typed) return value; void completes - // with the NONE slot — JS's undefined done-value. A GENRET unwind - // consumes the sentinel and promotes the parked .return value; a - // real exception stays pending (the consumer-side resume moves it). - if (coroBody) { - tr.push(` ret void`, `}`, ``); - } else { - tr.push( - ` %g = call ptr @scr_gen_of_fiber(ptr %self)`, - ...this.pendingTestLines("%pend"), - ` br i1 %pend, label %thrown, label %clean`, - `clean:`, - ); - switch (ret.kind) { - case "void": - tr.push(` br label %done ; void body: the done value is undefined (NONE)`); - break; - case "f64": - case "date": - this.declare(`declare void @scr_gen_out_f64(ptr, double)`); - tr.push(` call void @scr_gen_out_f64(ptr %g, double %r)`, ` br label %done`); - break; - case "bool": - this.declare(`declare void @scr_gen_out_bool(ptr, i1 zeroext)`); - tr.push(` call void @scr_gen_out_bool(ptr %g, i1 %r)`, ` br label %done`); - break; - default: { - const v = vAdapters(this.shapeHost, ret); - this.declare(`declare void @scr_gen_out_ref(ptr, ptr, ptr)`); - tr.push( - ` call void @scr_gen_out_ref(ptr %g, ptr %r, ptr ${v.release})`, - ` br label %done`, - ); - } - } - tr.push( - `thrown:`, - ` %genret = call zeroext i1 @scr_exc_genret_pending()`, - ` br i1 %genret, label %promote, label %dropdummy`, - `promote:`, - ` call void @scr_exc_clear()`, - ` call void @scr_gen_ret_to_out(ptr %g)`, - ` br label %dropdummy`, - `dropdummy:`, - ); - if (ret.kind !== "void" && isRefCounted(ret)) { - tr.push( - ` call void ${releaseSym(this.shapeHost, ret)}(ptr %r) ; unwound: the never-read dummy`, - ); - } - tr.push(` br label %done`, `done:`, ` ret void`, `}`, ``); - } - for (const line of tr) out.push(line); - - let settleAsync: string | null = null; - if (fn.async) { - this.declare(`declare ptr @scr_async_gen_new(ptr, ptr, ptr, ptr)`); - const genT: IrType & { kind: "generator" } = { - kind: "generator", - async: true, - yieldT: fn.generator.yieldT, - retT: ret, - nextT: fn.generator.nextT, - }; - const resultT = fn.generator.resultType; - const build = this.genResultThunkFor(genT, resultT); - settleAsync = `${build}_async`; - const settleKey = `ags:${typeKey(genT)}`; - if (!this.resolveThunks.has(settleKey)) { - this.resolveThunks.set(settleKey, settleAsync); - const adapters = vAdapters(this.shapeHost, resultT); - this.resolveThunkDefs.push( - `define internal void @${settleAsync}(ptr %g, ptr %p) ${FN_ATTRS} {`, - `entry:`, - ` %r = call ptr @${build}(ptr %g)`, - ` call void @scr_promise_fulfill_ref(ptr %p, ptr %r, ptr ${adapters.retain}, ptr ${adapters.release}, ptr ${traceArg(this.shapeHost, resultT)})`, - ` ret void`, - `}`, - ``, - ); - } - this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`); - } - - // The never-started teardown: drop the packed (+1) arguments. - const dr: string[] = [ - `define internal void @${mangleGenDrop(fn.name)}(ptr %ap) ${FN_ATTRS} {`, - `entry:`, - ]; - fieldTys.forEach((ty, i) => { - const pType = lifted && i === 0 ? null : fn.params[lifted ? i - 1 : i]!.type; - const refcounted = pType === null || isRefCounted(pType); - if (!refcounted) return; - dr.push( - ` %dp${i} = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${i}`, - ` %dv${i} = load ptr, ptr %dp${i}`, - ); - if (pType === null) { - this.declare(`declare void @scr_closure_release(ptr)`); - dr.push(` call void @scr_closure_release(ptr %dv${i})`); - } else { - dr.push(` call void ${releaseSym(this.shapeHost, pType)}(ptr %dv${i})`); - } - }); - 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 (fn.generator.tag !== undefined) this.declare(`declare void @scr_gen_set_tag(ptr, ptr)`); - if (fn.generator.inheritsTag === true) - this.declare(`declare void @scr_gen_copy_tag(ptr, ptr)`); - if (closesSource) { - const field = lifted ? 1 : 0; - this.declare(`declare void @scr_gen_close(ptr)`); - 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) { - this.declare(`declare ptr @scr_closure_retain_v(ptr)`); - } - const sp: string[] = [ - `define internal ptr @${mangleGenSpawn(fn.name)}(${spawnParams.join(", ")}) ${FN_ATTRS} { ; gen spawn ${fn.name}`, - `entry:`, - ...argPackLines, - ]; - const genNew = coroBody && !this.wasi ? "scr_gen_new_stackless" : "scr_gen_new"; - if (genNew !== "scr_gen_new") this.declare(`declare ptr @${genNew}(ptr, ptr, ptr)`); - sp.push( - settleAsync === null - ? ` %gg = call ptr @${genNew}(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)})` - : ` %gg = call ptr @scr_async_gen_new(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)}, ptr @${settleAsync})`, - ...(fn.generator.tag !== undefined - ? [` call void @scr_gen_set_tag(ptr %gg, ptr ${this.cstr(fn.generator.tag)})`] - : []), - ...(fn.generator.inheritsTag === true - ? [` call void @scr_gen_copy_tag(ptr %gg, ptr %a${lifted ? 1 : 0})`] - : []), - ...(closesSource - ? [ - ` call void @scr_gen_set_unstarted_return(ptr %gg, ptr @${mangleGenSourceClose(fn.name)})`, - ] - : []), - ` ret ptr %gg`, - `}`, - ``, - ); - for (const line of sp) out.push(line); - } - return out; + emitGenScaffolding(): string[] { + return emitGenScaffolding(this); } // ── plumbing (frame and scope ownership) ────── @@ -5107,7 +2278,7 @@ export class LlEmitter { } } - private releaseScope(scope: LlScopeEntry[]): void { + releaseScope(scope: LlScopeEntry[]): void { for (const e of scope) { const t = this.B.tmp(); this.B.line(`${t} = load ptr, ptr ${e.slot}`); @@ -5136,7 +2307,7 @@ export class LlEmitter { * frames and entered scopes down to the given depths, innermost first — * everything whose normal fall-through releases the jump bypasses. * Release every scope crossed by the control-flow edge. */ - private releaseForJump(frameDepth: number, scopeDepth: number): void { + releaseForJump(frameDepth: number, scopeDepth: number): void { for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseTemps(this.frames[i]!); for (let i = this.scopes.length - 1; i >= scopeDepth; i--) this.releaseScope(this.scopes[i]!); } @@ -5144,7 +2315,7 @@ export class LlEmitter { /** Run the finally regions an abrupt loop/block jump crosses, then * release to the already-resolved target. This is the return path's * completion walk without a parked result value. */ - private emitFinallysForJump(finallyDepth: number, frameDepth: number, scopeDepth: number): void { + emitFinallysForJump(finallyDepth: number, frameDepth: number, scopeDepth: number): void { if (this.finallyStack.length <= finallyDepth) { this.releaseForJump(frameDepth, scopeDepth); return; @@ -5178,7 +2349,7 @@ export class LlEmitter { * result of the function's own return type: with nothing to release, the * unwind returns it as the dummy, so a call in tail position keeps a single * return and stays eligible for tail-call and recursion elimination. */ - private emitUnwind(passThrough?: string): void { + emitUnwind(passThrough?: string): void { if (this.mod.workers && this.tryStack.length > 0) { const stop = this.B.newLabel("context.stop"); const ordinary = this.B.newLabel("context.throw"); @@ -5414,7 +2585,7 @@ export class LlEmitter { /** A recursive function's entry check: below the running stack's guard, * throw Node's RangeError and unwind like any other pending exception. */ - private emitStackCheck(): void { + emitStackCheck(): void { const B = this.B; this.declare(`declare ptr @llvm.stacksave.p0()`); this.declare(`declare i1 @llvm.expect.i1(i1, i1)`); @@ -5501,7 +2672,7 @@ export class LlEmitter { } } - private workerLoopBudget( + workerLoopBudget( loop: Extract, ): string | null { if (!this.mod.workers) return null; @@ -5513,128 +2684,16 @@ export class LlEmitter { this.B.line(`store i32 0, ptr ${slot}`); return slot; } - - /** Emit a loop's condition, body and update through `emitCopy`, twice - * when speculative-integers.ts plans a fast integer copy. Both copies - * share the loop's setup and exit; the ordinary copy comes first so the - * fast copy is only emitted when every resume point exists. */ - private emitLoopCopies( + emitLoopCopies( loop: Extract, allowed: boolean, exit: string, emitCopy: (exit: string) => void, ): void { - const plan = - allowed && - this.speculativeLoopsEnabled && - this.speculation === null && - this.speculationResume === null - ? planSpeculativeLoop( - loop, - (id) => this.speculativeLocal(id), - (id) => this.integerLoopBindings.has(id) || this.int32Locals.has(id), - (e) => { - const range = this.integerRanges.get(e); - return ( - range !== undefined && - range !== null && - range.min >= -Number.MAX_SAFE_INTEGER && - range.max <= Number.MAX_SAFE_INTEGER - ); - }, - (id) => this.speculativeLocal(id) || this.speculativeInvariantGlobal(id), - ) - : null; - if (!plan) { - emitCopy(exit); - return; - } - const B = this.B; - const entries = new Map(); - let ok = "true"; - for (const [id, live] of plan.locals) { - if (!live) continue; - const value = B.tmp(); - B.line(`${value} = load double, ptr ${this.binding(id).slot}`); - const [integer, exact] = emitSafeIntegerCheck(this, value); - entries.set(id, integer); - if (ok === "true") ok = exact; - else { - const both = B.tmp(); - B.line(`${both} = and i1 ${ok}, ${exact}`); - ok = both; - } - } - const fast = B.newLabel("spec.fast"), - slow = B.newLabel("spec.slow"); - B.condBr(ok, fast, slow); - const resume = new Map(); - for (const s of plan.writes) resume.set(s, { label: B.newLabel("spec.resume"), placed: false }); - B.startBlock(slow); - this.speculationResume = resume; - try { - emitCopy(exit); - } finally { - this.speculationResume = null; - } - B.startBlock(fast); - if (![...resume.values()].every((r) => r.placed)) { - B.br(slow); - return; - } - for (const id of plan.locals.keys()) { - const shadow = B.slot(); - B.entryAllocas.push( - `${shadow} = alloca i64 ; speculative integer ${this.currentLocals.get(id)?.name ?? id}`, - ); - const entry = entries.get(id); - if (entry !== undefined) { - // The same double, now visibly an integer conversion: the double - // phis of the fast copy fold into integer phis. - const exact = B.tmp(); - B.line(`store i64 ${entry}, ptr ${shadow}`); - B.line(`${exact} = sitofp i64 ${entry} to double`); - B.line(`store double ${exact}, ptr ${this.binding(id).slot}`); - } else B.line(`store i64 0, ptr ${shadow}`); - this.integerLoopBindings.set(id, { - slot: shadow, - type: "i64", - signed: true, - range: SPECULATIVE_RANGE, - }); - } - // A global's store stays in the loop (calls may read it). When nothing - // in the loop can observe it, its double is written once on leaving the - // copy instead, so the loop keeps it in a register as the double copy - // would. - const deferred = new Set(); - // Worker loops also leave through their cancellation poll. - if (plan.observerFree && !this.mod.workers) - for (const s of plan.writes) { - const id = speculativeWriteTarget(s)!.localId; - if (!this.currentLocals.has(id)) deferred.add(id); - } - this.speculation = { - writes: plan.writes, - resume: new Map([...resume].map(([s, r]) => [s, r.label])), - deferred, - }; - try { - if (deferred.size === 0) emitCopy(exit); - else { - const leave = B.newLabel("spec.leave"); - emitCopy(leave); - B.startBlock(leave); - this.writeBackSpeculativeGlobals(deferred); - B.br(exit); - } - } finally { - this.speculation = null; - for (const id of plan.locals.keys()) this.integerLoopBindings.delete(id); - } + return emitLoopCopies(this, loop, allowed, exit, emitCopy); } - private writeBackSpeculativeGlobals(deferred: Set): void { + writeBackSpeculativeGlobals(deferred: Set): void { const B = this.B; for (const id of deferred) { const integer = B.tmp(), @@ -5647,7 +2706,7 @@ export class LlEmitter { /** A number local whose only storage is its own double slot, so an i64 * shadow beside it can stand for it within one loop. */ - private speculativeLocal(id: string): boolean { + speculativeLocal(id: string): boolean { const local = this.currentLocals.get(id); if (!local) return this.speculativeGlobal(id); if (local.type.kind !== "f64" || local.boxed || local.tdz) return false; @@ -5679,7 +2738,7 @@ export class LlEmitter { /** A module-level number a loop of any function only reads, written by * nothing but top-level code: an init function never runs during a loop * of another function, so the entry value stays its value. */ - private speculativeInvariantGlobal(id: string): boolean { + speculativeInvariantGlobal(id: string): boolean { const writers = this.numberGlobalWriters(id); return writers !== null && [...writers].every((name) => name.startsWith("%init.")); } @@ -5713,65 +2772,8 @@ export class LlEmitter { } return this.globalWriters.get(id) ?? new Set(); } - - /** A speculated write in the fast copy: compute the value as a safe - * integer, store it to the shadow and its exact double to the local. - * Any failed check re-executes the statement in the ordinary copy. */ private emitSpeculativeWrite(s: IrStmt, resume: string): void { - const B = this.B; - const target = speculativeWriteTarget(s)!; - const binding = this.integerLoopBindings.get(target.localId)!; - const failed = B.newLabel("spec.bail"); - let bails = false; - this.declare("declare i1 @llvm.expect.i1(i1, i1)"); - const bail = (condition: string): void => { - if (condition === "false") return; - bails = true; - const expected = B.tmp(); - B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${condition}, i1 false)`); - const next = B.newLabel("spec.ok"); - B.condBr(expected, failed, next); - B.startBlock(next); - }; - // Heap reads come first, before any check, and release their temporaries - // at once: a bailing path then owns nothing, and reads have no effect - // whose reordering before the arithmetic could be observed. - const reads = new Map(); - if (target.value !== null) { - this.frames.push([]); - for (const read of speculativeReads(target.value)) reads.set(read, this.emitExpr(read)); - this.releaseTemps(this.frames.pop()!); - } - this.frames.push([]); - let result: string; - if (target.value === null) { - const old = B.tmp(); - result = B.tmp(); - B.line(`${old} = load i64, ptr ${binding.slot}`); - B.line(`${result} = ${target.op === "+" ? "add" : "sub"} nsw i64 ${old}, 1`); - const biased = B.tmp(), - outside = B.tmp(); - B.line(`${biased} = add i64 ${result}, ${Number.MAX_SAFE_INTEGER}`); - B.line(`${outside} = icmp ugt i64 ${biased}, ${2 * Number.MAX_SAFE_INTEGER}`); - bail(outside); - } else result = emitSpeculativeInteger(this, target.value, bail, reads); - // Speculable values read only numbers past this point. - if (this.frames.pop()!.length > 0) - throw new InternalCompilerError("llvm emitter bug: speculative write owns a temporary"); - B.line(`store i64 ${result}, ptr ${binding.slot}`); - const deferred = this.speculation!.deferred; - if (!deferred.has(target.localId)) { - const number = B.tmp(); - B.line(`${number} = sitofp i64 ${result} to double`); - B.line(`store double ${number}, ptr ${this.binding(target.localId).slot}`); - } - if (!bails) return; - const done = B.newLabel("spec.done"); - B.br(done); - B.startBlock(failed); - this.writeBackSpeculativeGlobals(deferred); - B.br(resume); - B.startBlock(done); + return emitSpeculativeWrite(this, s, resume); } /** The amortized cancellation poll, emitted on the loop's back edge (the @@ -5779,7 +2781,7 @@ export class LlEmitter { * exit ahead of the body; LLVM then cannot rotate the loop, so loads that * the body repeats on every iteration (a typed array's length, for one) * stay in the loop. */ - private emitWorkerLoopCheck(slot: string | null): void { + emitWorkerLoopCheck(slot: string | null): void { if (slot === null) return; const B = this.B; const budget = B.tmp(), @@ -6385,6 +3387,34 @@ export class LlEmitter { /** A binding's storage: a module global, a plain local slot, or a boxed * local (the slot holds the capture BOX; access goes through it). */ + matchBytesLoopVersion( + s: IrStmt & { kind: "for" }, + counted: IntegerCountedForLoop | null, + ): ReturnType { + const fn = this.bytesLoopFunction; + if (!fn) return null; + this.bytesLoopEntries ??= findInitializedByteLoopBindings(fn); + const initialized = this.bytesLoopEntries.get(s); + if (!initialized) return null; + const invariantGlobals = new Set(this.stableGlobals); + for (const global of this.initializerBindings.get(fn.name) ?? []) + invariantGlobals.add(global.id); + return matchBytesLoopVersion(s, counted, { + locals: this.currentLocals, + invariantGlobals, + captured: this.captureIds, + ranges: this.integerRanges, + initialized, + proven: this.bytesBounds, + }); + } + + /** The alias tag of a whole-slot load of a binding (module globals only). */ + slotAlias(id: string): string { + const binding = this.binding(id); + return binding.kind === "global" ? globalSlotAlias(this, id, binding.type.kind) : ""; + } + binding(id: string): { kind: "global" | "local" | "boxed"; slot: string; @@ -6409,7 +3439,7 @@ export class LlEmitter { }; } - private emitDebugLocal(local: IrLocal, slot: string, arg = 0, captured = false): void { + emitDebugLocal(local: IrLocal, slot: string, arg = 0, captured = false): void { const debug = this.debug?.local(local, this.debugScope, arg, captured); if (!debug) return; this.declare("declare void @llvm.dbg.declare(metadata, metadata, metadata)"); @@ -6426,7 +3456,7 @@ export class LlEmitter { } /** The payload slot of a capture box (ScrBox.slot, field 5). */ - private boxSlot(box: string): string { + boxSlot(box: string): string { const slot = this.B.tmp(); this.B.line(`${slot} = getelementptr inbounds %ScrBox, ptr ${box}, i32 0, i32 5`); return slot; @@ -6505,7 +3535,7 @@ export class LlEmitter { /** The initializing write of a scalar TDZ box: mint the one-element * array cell holding the value and move it into the ARR-kind box * (set_ref releases nothing — the slot was the empty sentinel). */ - private tdzScalarInit(box: string, t: IrType, value: string): void { + tdzScalarInit(box: string, t: IrType, value: string): void { const B = this.B; const acc = boxAccess(t); const cell = B.tmp(); @@ -6521,7 +3551,7 @@ export class LlEmitter { * read back as the temporal dead zone; such boxes start at the module's * immortal NULLABLE_ABSENT sentinel instead, which no value of the * binding can hold and every RC entry point and the collector skip. */ - private tdzEmptySentinel(t: IrType): string | null { + tdzEmptySentinel(t: IrType): string | null { if (!this.nullableUnions.of(t)) return null; this.needsNullableAbsent = true; this.immortalValues.add(NULLABLE_ABSENT); @@ -6805,75 +3835,13 @@ export class LlEmitter { ); this.B.startBlock(resume); } - - /** Move a clean async return value into the current fiber's promise. */ - private emitWasiFulfill(v: LlValue | null): void { - const coro = this.currentCoro; - if (coro === null) - throw new InternalCompilerError("llvm emitter bug: wasm fulfillment outside async body"); - const ret = this.currentReturnType; - if (coro.kind === "generator") { - this.declare(`declare ptr @scr_gen_of_fiber(ptr)`); - const gen = this.B.tmp(); - this.B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${coro.self})`); - switch (ret.kind) { - case "void": - return; - case "f64": - case "date": - this.declare(`declare void @scr_gen_out_f64(ptr, double)`); - this.B.line(`call void @scr_gen_out_f64(ptr ${gen}, double ${v!.name})`); - return; - case "bool": - this.declare(`declare void @scr_gen_out_bool(ptr, i1 zeroext)`); - this.B.line(`call void @scr_gen_out_bool(ptr ${gen}, i1 ${v!.name})`); - return; - default: - this.declare(`declare void @scr_gen_out_ref(ptr, ptr, ptr)`); - this.B.line( - `call void @scr_gen_out_ref(ptr ${gen}, ptr ${v!.name}, ptr ${vAdapters(this.shapeHost, ret).release})`, - ); - return; - } - } - this.declare(`declare ptr @scr_fiber_promise(ptr)`); - const pr = this.B.tmp(); - this.B.line(`${pr} = call ptr @scr_fiber_promise(ptr ${coro.self})`); - switch (ret.kind) { - case "void": - this.declare(`declare void @scr_promise_fulfill_void(ptr)`); - this.B.line(`call void @scr_promise_fulfill_void(ptr ${pr})`); - break; - case "f64": - case "date": - this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`); - this.B.line(`call void @scr_promise_fulfill_f64(ptr ${pr}, double ${v!.name})`); - break; - case "bool": - this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`); - this.B.line(`call void @scr_promise_fulfill_bool(ptr ${pr}, i1 ${v!.name})`); - break; - case "string": - this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`); - this.B.line(`call void @scr_promise_fulfill_str(ptr ${pr}, ptr ${v!.name}) ; moves in`); - break; - case "dyn": - this.declare(`declare void @scr_promise_resolve_dyn(ptr, ptr)`); - this.B.line(`call void @scr_promise_resolve_dyn(ptr ${pr}, ptr ${v!.name}) ; moves in`); - break; - default: { - const rc = vAdapters(this.shapeHost, ret); - this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`); - this.B.line( - `call void @scr_promise_fulfill_ref(ptr ${pr}, ptr ${v!.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(this.shapeHost, ret)})`, - ); - } - } + emitWasiFulfill(v: LlValue | null): void { + return emitWasiFulfill(this, v); } /** Number locals declared as a copy of a capture or of a lazily boxed * parameter, at the top of the body. */ - private doubleCopies(fn: IrFunction): string[] { + doubleCopies(fn: IrFunction): string[] { const copies: string[] = []; for (const s of fn.body) { if (s.kind !== "varDecl") break; @@ -6883,596 +3851,14 @@ export class LlEmitter { } return copies; } - - private emitFunction(fn: IrFunction): string { - const B = new BlockBuilder(); - this.B = B; - this.currentFunctionName = fn.name; - // Fiber switches restore the active exception cell before control - // returns to a synchronous frame (emitPendingCheck). - B.excCellInvariant = fn.async !== true && fn.generator === undefined; - this.debugScope = this.debug?.function(fn) ?? null; - B.debugLocation = this.debug?.location(fn.loc, this.debugScope) ?? null; - this.frames = []; - this.scopes = []; - this.unwindCleanups.clear(); - this.jumpTargets = []; - this.currentLocals = new Map(fn.locals.map((l) => [l.id, l])); - this.lazyCaptureBoxes.clear(); - this.lazyStackBoxes.clear(); - // An unchanged captured parameter keeps its value in a plain slot; the - // binding is boxed only for closures (captureBox). - const lazyParameters = this.lazyCaptures.parameters(fn); - for (const id of lazyParameters) { - const { boxed: _boxed, ...plain } = this.currentLocals.get(id)!; - this.currentLocals.set(id, plain); - this.lazyCaptureBoxes.set(id, ""); - } - this.currentConstantCallbacks = - this.debug === null ? (this.constantCallbacks.get(fn.name) ?? new Map()) : new Map(); - // Preserve concrete source bindings for debugger inspection. Suspended - // functions keep the established array lifetime across continuations. - this.streamingSplitsEnabled = this.debug === null && !fn.async && !fn.generator; - this.stackCallbacks.reset(this.streamingSplitsEnabled); - this.stackCaptures.reset(fn, this.streamingSplitsEnabled, lazyParameters); - this.privateSplitLocals = this.streamingSplitsEnabled ? findPrivateSplitLocals(fn) : new Map(); - this.storedSplits.clear(); - this.splitSpans.clear(); - this.stringSlices.clear(); - 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(); - this.projectedParameters.clear(); - const projectedParameterIndexes = this.callLifetimes.parameters.get(fn.name); - if (projectedParameterIndexes) - for (const index of projectedParameterIndexes) - this.projectedParameters.add(fn.params[index]!.localId); - this.stableCallBindings = this.callLifetimes.bindings.get(fn.name) ?? new Set(); - const borrowedParameterIndexes = this.callLifetimes.borrowed.get(fn.name); - if (borrowedParameterIndexes) { - // Rebound walk parameters borrow too, but only unchanged ones may - // serve as stable owners for call arguments and aliases. - for (const index of borrowedParameterIndexes) - if ( - !this.walkBorrowsByFunction.get(fn.name)?.has(fn.params[index]!.localId) && - !this.reboundByFunction.get(fn.name)?.has(fn.params[index]!.localId) - ) - this.borrowedParameters.add(fn.params[index]!.localId); - } - // A generator frame owns its unchanged parameters across suspensions. - const generatorParameters = this.callLifetimes.generatorParameters.get(fn.name); - if (generatorParameters !== undefined) - for (const id of generatorParameters) this.borrowedParameters.add(id); - this.captureIds = new Set( - [...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId), - ); - this.stableCaptureIds = - fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set(); - this.integerLoopBindings.clear(); - this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator; - this.speculativeLoopsEnabled = this.countedLoopsEnabled; - this.speculativeFunction = fn.name; - this.speculation = null; - this.speculationResume = null; - 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) - : new Map(); - this.integerViews.clear(); - this.fieldPointerTags.clear(); - this.integerArrayBindings.clear(); - this.loopArrayBorrows = - this.debug === null - ? findLoopArrayBorrows( - fn, - this.callLifetimes, - this.referenceEffects, - this.optionalArrayReads, - this.referenceWrites, - this.fnByName, - ) - : new Set(); - this.localArrayReads = findLocalArrayReads( - fn, - this.fnByName, - this.unionsById, - this.referenceEffects.functions, - this.callLifetimes, - this.optionalArrayReads, - this.loopArrayBorrows, - this.debug === null ? this.referenceEffects : undefined, - ); - this.callArrayReads = findCallArrayReads( - fn, - this.optionalArrayReads, - this.referenceEffects.functions, - this.callLifetimes, - this.debug === null ? this.referenceEffects : undefined, - ); - 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; - this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById); - this.localStackObjects = this.stackObjects - ? findLocalStackObjects(this, fn, this.callLifetimes) - : new Map(); - this.reassignedStackObjects = this.stackObjects - ? findReassignedStackObjects(this, fn, this.callLifetimes) - : new Set(); - this.stackObjectSlots = new Map(); - this.stackObjectFieldPtrs = new Map(); - this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.unionSources); - this.localUnionStorage = new Map(); - this.walkBorrows = this.walkBorrowsByFunction.get(fn.name) ?? new Set(); - const slotFacts = this.int32Slots.facts(fn.name); - this.integerRanges = analyzeIntegerRanges(numericFn, slotFacts); - this.int32Elements = slotFacts.elements ?? null; - this.bytesBounds = findBytesBounds(numericFn, this.integerRanges); - // Debug builds keep every number local a double for the debugger's - // variable view. Lazily boxed captured parameters keep their double, - // which the box is created from, and so do plain copies of a capture or - // such a parameter (copyStableCaptures): the source already holds the - // double, which an i32 copy would convert and widen again on every call. - // Initializer bindings proven int32 move into an i32 local of their - // initializer: no other code observes the global, so the loop-carried - // value never round-trips through the double slot. - const initializerLocals = initializerLocalCandidates(initializerBindings); - const int32Locals = - this.debug === null - ? findInt32Locals( - withInitializerLocals(fn, initializerLocals), - this.integerRanges, - this.int32Slots.facts(fn.name), - new Set([...this.lazyCaptureBoxes.keys(), ...this.doubleCopies(fn)]), - ) - : null; - this.int32Locals = int32Locals?.locals ?? new Map(); - this.int32LocalWrites = int32Locals?.values ?? new Set(); - const localizedGlobals = new Set(); - for (const global of initializerLocals) { - if (!this.int32Locals.has(global.id)) continue; - localizedGlobals.add(global.id); - this.currentLocals.set(global.id, { - id: global.id, - name: global.name, - type: global.type, - mutable: global.mutable, - }); - } - this.chainSlots.clear(); - this.finallyStack = []; - this.tryStack = []; - this.loopDepth = 0; - this.currentReturnType = fn.returnType; - const int32Abi = this.int32Abis.get(fn.name); - this.currentInt32Result = int32Abi?.result === true; - this.currentUnionResult = int32Abi?.union === true; - this.currentGenerator = fn.generator ?? null; - this.currentAsyncGenerator = fn.async === true && fn.generator !== undefined; - this.currentBorrowedReturn = this.borrowedReturns.has(fn.name); - this.lastUseMoves.clear(); - this.varRefCounts = null; - this.currentFunctionBody = fn.body; - this.inConstructor = this.constructorFunctions.has(fn.name); - this.currentCoro = null; - this.logArgSlots = 0; - - if (this.coroLowered(fn)) { - this.usesCoroutines = true; - this.declare(`declare token @llvm.coro.id(i32, ptr, ptr, ptr)`); - this.declare(`declare ${this.sizeType} @llvm.coro.size.${this.sizeType}()`); - this.declare(`declare ptr @llvm.coro.begin(token, ptr)`); - this.declare(`declare token @llvm.coro.save(ptr)`); - this.declare(`declare i8 @llvm.coro.suspend(token, i1)`); - this.declare(`declare ptr @llvm.coro.free(token, ptr)`); - this.declare(`declare void @llvm.coro.end(ptr, i1, token)`); - // Native frames come from the runtime's small-block allocator. - const frameAlloc = this.wasi ? "malloc" : "scr_rt_calloc"; - this.declare(`declare ptr @${frameAlloc}(${this.sizeType})`); - this.declare(`declare void @${this.wasi ? "free" : "scr_rt_free"}(ptr)`); - const started = this.wasi ? "scr_wasi_coro_started" : "scr_coro_started"; - this.declare(`declare void @${started}(ptr)`); - this.declare(`declare ptr @scr_fiber_self()`); - const id = B.tmp(); - const size = B.tmp(); - const mem = B.tmp(); - const handle = B.tmp(); - const self = B.tmp(); - B.line(`${id} = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)`); - B.line(`${size} = call ${this.sizeType} @llvm.coro.size.${this.sizeType}()`); - B.line(`${mem} = call ptr @${frameAlloc}(${this.sizeType} ${size})`); - B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`); - B.line(`call void @${started}(ptr ${handle})`); - B.line(`${self} = call ptr @scr_fiber_self()`); - // A native synchronous body owns its generator for the whole run (the - // ScrGen owns the frame), so its OUT slot address is fixed. - let out: string | null = null; - const yieldKind = fn.generator?.yieldT.kind; - if ( - !this.wasi && - fn.async !== true && - (yieldKind === "f64" || yieldKind === "date" || yieldKind === "bool") - ) { - this.declare(`declare ptr @scr_gen_of_fiber(ptr)`); - const gen = B.tmp(); - B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${self})`); - out = this.genOutSlot(gen); - } - this.currentCoro = { - kind: fn.generator !== undefined ? "generator" : "async", - id, - handle, - self, - finalLabel: B.newLabel("coro.final"), - cleanupLabel: B.newLabel("coro.cleanup"), - suspendLabel: B.newLabel("coro.suspend"), - out, - }; - } - - const paramIds = new Set(fn.params.map((p) => p.localId)); - if (this.debug === null) { - for (const id of findIntegerViews(numericFn)) { - // An i32 slot is its own view. - if (this.int32Locals.has(id)) continue; - const slot = B.slot(); - B.entryAllocas.push( - `${slot} = alloca i32 ; integer view ${this.numericLocals.get(id)!.name}`, - ); - this.integerViews.set(id, slot); - } - } - for (const global of initializerBindings) { - if (!this.integerViews.has(global.id)) continue; - const value = B.tmp(); - B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`); - this.storeIntegerView(global.id, { name: value, type: global.type }); - } - // A number global only its guarded, synchronous initializer reads or - // writes (findInitializerBindings) is invisible to every other function, - // and reentry returns before reaching it, so a local slot can stand in - // for it: LLVM keeps it in a register across calls and loops that would - // otherwise force a store and reload per iteration. The global's - // storage is never read again, so nothing writes the slot back. Bindings - // proven int32 already live in an i32 local (localizedGlobals above); - // this covers the remaining doubles. - this.privateGlobalSlots.clear(); - if (this.debug === null && this.currentCoro === null && !fn.async && !fn.generator) { - const conditional = initializerLocals.length ? conditionalLoopWrites(fn.body) : null; - for (const global of initializerLocals) { - if (localizedGlobals.has(global.id)) continue; - // A conditional update in a loop stays a memory update (see - // conditionalLoopWrites). - if (conditional?.has(global.id)) continue; - const slot = B.slot(); - const value = B.tmp(); - B.entryAllocas.push(`${slot} = alloca double ; module binding ${global.name}`); - B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`); - B.line(`store double ${value}, ptr ${slot}`); - this.privateGlobalSlots.set(global.id, slot); - } - } - for (const local of this.currentLocals.values()) { - // Boxed locals' slots hold their capture BOX (a ptr); captured - // (env-borrowed) locals bind the incoming box below. A caught-typed - // local is a catch binding: its slot holds the ScrCaught snapshot - // box the catch prologue takes (scr_exc_take). - const slotTy = - local.boxed || this.captureIds.has(local.id) || local.type.kind === "caught" - ? "ptr" - : this.int32Locals.has(local.id) - ? "i32" - : this.llType(local.type); - B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`); - this.emitDebugLocal( - local, - `%${mangleLocal(local.id)}`, - fn.params.findIndex((p) => p.localId === local.id) + 1, - this.captureIds.has(local.id), - ); - // A localized initializer binding starts at its global's zero value. - if (localizedGlobals.has(local.id)) B.line(`store i32 0, ptr %${mangleLocal(local.id)}`); - // Refcounted/boxed locals start NULL (the C prologue's `= NULL`): - // scope-exit releases run whether or not an assign ever did. - if (paramIds.has(local.id) || this.captureIds.has(local.id)) continue; - if (local.boxed || isRefCounted(local.type)) { - B.line(`store ptr null, ptr %${mangleLocal(local.id)}`); - } - } - // A body holding thousands of capture boxes compiles unoptimized, its - // box pointers kept in their slots (see boxSlotCount). - const unoptimized = this.currentCoro === null && boxSlotCount(fn) >= UNOPTIMIZED_BOX_SLOTS; - if (unoptimized) { - const slots = new Set(); - for (const local of this.currentLocals.values()) - if (local.boxed || this.captureIds.has(local.id)) slots.add(`%${mangleLocal(local.id)}`); - B.volatileSlots = slots; - } - // Captured bindings come in through the environment — borrowed for the - // whole call (the closure owns them): bound here, never released here. - (fn.captures ?? []).forEach((c, i) => { - const caps = B.tmp(); - const p = B.tmp(); - const box = B.tmp(); - B.line(`${caps} = getelementptr inbounds %ScrClosure, ptr %sc_env, i64 1 ; caps`); - B.line(`${p} = getelementptr inbounds ptr, ptr ${caps}, ${this.sizeType} ${i} ; caps[${i}]`); - B.line(`${box} = load ptr, ptr ${p}`); - B.line(`store ptr ${box}, ptr %${mangleLocal(c.localId)} ; captured ${c.name}`); - }); - (fn.classCaptures ?? []).forEach((c) => { - const self = fn.params[0]!; - if (self.type.kind !== "object") - throw new InternalCompilerError("class captures require an instance receiver"); - const classSlot = B.tmp(); - const classValue = B.tmp(); - const caps = B.tmp(); - const slot = B.tmp(); - const box = B.tmp(); - B.line( - `${classSlot} = getelementptr inbounds %${mangleClassStruct(self.type.className)}, ptr %p_${mangleLocal(self.localId)}, i64 0, i32 ${classEnvironmentIndex(this.classMeta.get(self.type.className)!)}`, - ); - B.line(`${classValue} = load ptr, ptr ${classSlot}`); - B.line(`${caps} = getelementptr inbounds %ScrClassObj, ptr ${classValue}, i64 1`); - B.line(`${slot} = getelementptr inbounds ptr, ptr ${caps}, ${this.sizeType} ${c.slot}`); - B.line(`${box} = load ptr, ptr ${slot}`); - B.line(`store ptr ${box}, ptr %${mangleLocal(c.localId)} ; class capture ${c.name}`); - }); - // Params spill into their slots; the function scope owns refcounted - // params (callees own their params — callers passed +1). Boxed params - // allocate the shared binding and move the raw value in. - const fnScope: LlScopeEntry[] = []; - for (const id of this.lazyCaptureBoxes.keys()) { - const cache = B.slot(); - B.entryAllocas.push(`${cache} = alloca ptr ; lazy box ${this.currentLocals.get(id)!.name}`); - B.line(`store ptr null, ptr ${cache}`); - fnScope.push({ - slot: cache, - type: this.currentLocals.get(id)!.type, - boxed: true, - lazy: true, - }); - this.lazyCaptureBoxes.set(id, cache); - // Environments that finish during the call can share an immortal - // frame box. Its payload owner is released with the function scope. - const stack = this.stackCaptures.emit(this, id, this.currentLocals.get(id)!.type); - if (stack) { - this.lazyStackBoxes.set(id, stack.box); - if (stack.owner) fnScope.push(stack.owner); - } - } - const borrowed = this.callLifetimes.borrowed.get(fn.name); - for (const [index, p] of fn.params.entries()) { - const local = this.currentLocals.get(p.localId)!; - const slot = `%${mangleLocal(p.localId)}`; - if (local.boxed) { - const box = B.tmp(); - B.line(`${box} = ${boxNewCall(this.shapeHost, p.type)} ; ${p.name} (boxed param)`); - this.boxSet(box, p.type, `%p_${mangleLocal(p.localId)}`); - B.line(`store ptr ${box}, ptr ${slot}`); - fnScope.push({ slot, type: p.type, boxed: true }); - continue; - } - const incoming: LlValue = { name: `%p_${mangleLocal(p.localId)}`, type: p.type }; - if (int32Abi?.params[index]) { - const bits = `%p_${mangleLocal(p.localId)}${INT32_ABI_SUFFIX}`; - incoming.uint32 = bits; - incoming.integer = { name: bits, type: "i32", signed: true, range: INT32_RANGE }; - } - if (this.storeInt32Local(p.localId, incoming)) continue; - B.line(`store ${this.llType(p.type)} %p_${mangleLocal(p.localId)}, ptr ${slot}`); - this.storeIntegerView(p.localId, incoming); - if (isRefCounted(p.type) && !borrowed?.has(index)) { - fnScope.push({ slot, type: p.type }); - this.noteParameterMove(fn, p.localId, slot); - } - if (this.reboundByFunction.get(fn.name)?.has(p.localId) && borrowed?.has(index)) { - // The caller's borrow stays in the parameter slot; values the body - // assigns are owned here until rebinding or exit releases them. - const owner = `${slot}.owner`; - B.entryAllocas.push(`${owner} = alloca ptr`); - B.line(`store ptr null, ptr ${owner}`); - fnScope.push({ slot: owner, type: p.type }); - this.reboundOwnerSlots.set(p.localId, owner); - } - } - this.scopes.push(fnScope); - // 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.unframedFunctions.has(fn.name) - ) { - B.entryAllocas.push(`%source_frame = alloca { ptr, ptr }`); - 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); - this.emitStmts(fn.body, true); - // Implicit exit of a void function: release the function scope unless - // the body already terminated its final block (return, or a throw - // whose unwind released everything down to depth 0). - if (fn.returnType.kind === "void" && !B.isTerminated()) { - this.releaseScope(this.scopes[0]!); - // Completing by falling off the end has nothing to await. - if (this.currentAsyncGenerator) this.emitAsyncGenSettled(); - if (this.currentCoro !== null) { - this.emitWasiFulfill(null); - B.terminate(`br label %${this.currentCoro.finalLabel}`); - } else { - B.terminate("ret void"); - } - } - this.scopes.pop(); - - const coro = this.currentCoro; - if (coro !== null) { - B.startBlock(coro.finalLabel); - // Every exit of a coroutine body joins here: its source frame (part - // of the coroutine frame) leaves the generator's own chain. - const epilogue = B.returnEpilogue; - B.returnEpilogue = null; - if (epilogue !== null) B.line(epilogue); - if (fn.captures !== undefined) { - this.declare(`declare void @scr_closure_release(ptr)`); - B.line(`call void @scr_closure_release(ptr %sc_env)`); - } - if (coro.kind === "generator") { - const finish = this.wasi ? "scr_wasi_gen_finish" : "scr_gen_coro_finish"; - this.declare(`declare void @${finish}(ptr)`); - B.line(`call void @${finish}(ptr ${coro.self})`); - } else { - const finish = this.wasi ? "scr_wasi_async_finish" : "scr_coro_async_finish"; - this.declare(`declare void @${finish}(ptr)`); - B.line(`call void @${finish}(ptr ${coro.self})`); - } - const finalState = B.tmp(); - B.line(`${finalState} = call i8 @llvm.coro.suspend(token none, i1 true)`); - B.terminate( - `switch i8 ${finalState}, label %${coro.suspendLabel} [ i8 1, label %${coro.cleanupLabel} ]`, - ); - B.startBlock(coro.cleanupLabel); - const frame = B.tmp(); - B.line(`${frame} = call ptr @llvm.coro.free(token ${coro.id}, ptr ${coro.handle})`); - B.line(`call void @${this.wasi ? "free" : "scr_rt_free"}(ptr ${frame})`); - B.br(coro.suspendLabel); - B.startBlock(coro.suspendLabel); - B.line(`call void @llvm.coro.end(ptr ${coro.handle}, i1 false, token none)`); - const ret = this.llType(fn.returnType); - if (ret === "void") B.terminate(`ret void`); - else if (ret === "double") B.terminate(`ret double ${f64Lit(0)}`); - else if (ret === "i1") B.terminate(`ret i1 false`); - else B.terminate(`ret ptr null`); - } - - // Shared blocks have multiple source locations. Keep the location on - // each incoming exception check instead of assigning one to the cleanup. - B.debugLocation = null; - for (const cleanup of this.unwindCleanups.values()) { - B.startBlock(cleanup.label); - this.releaseScope(cleanup.entries); - B.terminate(cleanup.terminator); - } - - if (this.logArgSlots > 0) { - B.entryAllocas.push(`%logargs = alloca [${this.logArgSlots} x %ScrLogArg]`); - } - const params = fn.params.map((p, index) => - int32Abi?.params[index] - ? `i32 %p_${mangleLocal(p.localId)}${INT32_ABI_SUFFIX}` - : `${this.llType(p.type)} %p_${mangleLocal(p.localId)}`, - ); - // The i32 entry widens its i32 parameters once; the proof makes the - // double exact, so readers of the incoming number see the same value. - if (int32Abi) - fn.params.forEach((p, index) => { - if (!int32Abi.params[index]) return; - const name = `%p_${mangleLocal(p.localId)}`; - B.entryAllocas.push(`${name} = sitofp i32 ${name}${INT32_ABI_SUFFIX} to double`); - }); - // Lifted functions receive their closure first (the callValue ABI). - if (fn.captures !== undefined) params.unshift("ptr %sc_env"); - const ret = int32Abi?.result - ? "i32" - : int32Abi?.union - ? SCALAR_UNION_RESULT - : this.llType(fn.returnType); - let attrs = coro !== null ? "#1" : FN_ATTRS; - // The shared indexed-read helper (lowerSafeIndexRead) is a dense bounds - // test, a state test and a load behind cold runtime fallbacks, called - // from every `a[i]` of one element type. Without the hint LLVM keeps the - // hot instances out of line once they have many callers: every read pays - // a call, and an integer index converts to a double and back across it. - if (fn.name.startsWith("%arr.idxOr.")) attrs = `inlinehint ${attrs}`; - // Interface dispatch is a few key tests and direct calls ahead of the - // checked dynamic call. Keeping that fallback out of line leaves the - // tests small enough to inline at each call site. - if (fn.name.startsWith("%dyn.class.protocol.fallback.")) attrs = `noinline ${attrs}`; - else if (fn.name.startsWith("%dyn.class.protocol.")) attrs = `inlinehint ${attrs}`; - if (fn.name.startsWith(RESULT_ENTRY_PREFIX)) attrs = `inlinehint ${attrs}`; - if (unoptimized && attrs === FN_ATTRS) attrs = `noinline optnone ${attrs}`; - const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`; - const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name); - this.privateGlobalSlots.clear(); - let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`; - if (int32Abi) body += "\n" + this.emitInt32AbiWrapper(fn, symbol, int32Abi); - if (this.integerClones.members.has(fn.name)) { - body += "\n" + emitIntegerClone(fn, this, this.stackChecks?.has(fn.name) === true); - if (this.integerClones.dispatched.has(fn.name)) - body += - "\n" + - emitIntegerDispatcher( - fn, - this, - `${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}`, - int32Abi?.params ?? [], - int32Abi?.result === true, - ); - } - if (!borrowed) return body; - const slot = this.implSlotBorrowed(fn.name); - const virtual = - slot.size > 0 && slot.size !== borrowed.size - ? "\n" + this.emitOwnedCallAdapter(fn, borrowed, slot) - : ""; - return body + "\n" + this.emitOwnedCallAdapter(fn, borrowed) + virtual; + emitFunction(fn: IrFunction): string { + return emitFunction(this, fn); } /** The double-ABI entry of a function with an i32 entry: every caller * that is not a direct call (closures, dispatch, adapters, the runtime) * enters here. A pending exception passes through to its caller. */ - private emitInt32AbiWrapper(fn: IrFunction, symbol: string, abi: Int32Abi): string { + emitInt32AbiWrapper(fn: IrFunction, symbol: string, abi: Int32Abi): string { const lines: string[] = []; const params: string[] = []; const args: string[] = []; @@ -7632,7 +4018,7 @@ export class LlEmitter { /** With `kept`, the virtual adapter of a borrowing slot instead: the * slot's parameters stay borrowed, the body's other borrowed ones are * released here. */ - private emitOwnedCallAdapter( + emitOwnedCallAdapter( fn: IrFunction, borrowed: ReadonlySet, kept: ReadonlySet = NO_BORROWED, @@ -7672,86 +4058,13 @@ export class LlEmitter { kept.size > 0 ? `${mangleBorrowedFunction(fn.name)}.virtual` : mangleFunction(fn.name); return `define internal ${ret} @${symbol}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`; } - - /** A local stable binding keeps its value alive while later arguments - * and the callee execute. A proven local union box cannot escape or be - * rebound during a call; its independent payload owner keeps it alive. - * Other writable bindings need an owned snapshot. */ canBorrowCallArgument(value: IrExpr): boolean { - if (value.kind === "strLit") return true; - // An exact capsule owns its instance while the capsule's binding holds it. - if (value.kind === "dynCheck" && value.exact) - return value.value.kind === "varRef" && this.canBorrowCallArgument(value.value); - // An inline union field has no box to lend. - if (this.inlineFieldGet(value)) return false; - // Class casts reinterpret the same pointer (prefix layouts). - if ( - (value.kind === "upcast" || value.kind === "downcast") && - value.value.type.kind === "object" - ) - return this.canBorrowCallArgument(value.value); - if (value.kind === "seqExpr") { - const guarded = guardedValue(value); - return guarded !== null && this.canBorrowCallArgument(guarded); - } - // A nullable-union wrap reinterprets its payload pointer; an immortal - // unit constant needs no owner at all. - const wrap = this.borrowableNullableWrap(value); - if (wrap === "unit") return true; - if (wrap === "ref" && value.kind === "unionWrap") - return this.canBorrowCallArgument(value.value); - // Checked casts (`x as C`, `x!` on a nullable) lower to a ternary whose - // failing arm always throws; the successful arm projects the same - // stable owner. The throwing arm's typed dummy owns nothing. - if (value.kind === "unionNarrow") return this.canBorrowCallArgument(value.value); - if (value.kind === "libCall") return value.fn === "error.nodeThrow"; - // A final field never changes outside constructors: while its object is - // borrowable, so is the field's value. Nullable-pointer fields have no - // box to lend and keep their owning path. - if ( - value.kind === "fieldGet" && - this.finalFieldBorrows && - isRefCounted(value.type) && - this.finalFields.isFinal(value.className, value.field) && - !this.nullableFieldGet(value) - ) - return this.canBorrowReceiver(value.obj) && this.canBorrowCallArgument(value.obj); - if (value.kind === "ternary") - return ( - this.canBorrowReceiver(value.then) && - this.canBorrowReceiver(value.else_) && - this.canBorrowCallArgument(value.then) && - this.canBorrowCallArgument(value.else_) - ); - // A construction-only field of a stable owner keeps its value until the - // owner dies: no later operand or callee can replace and release it. - if ((value.kind === "fieldGet" || value.kind === "recordGet") && this.immutableRead(value)) - return this.canBorrowCallArgument(value.obj); - // An optional chain's bound receiver stays unchanged until its body - // finishes (see emitReadReceiver). - if (value.kind === "chainRecv") return this.chainSlots.has(value.id); - if (value.kind !== "varRef") return false; - const binding = this.binding(value.localId); - // A module constant owns its value for the rest of the program. - if (binding.kind === "global") return this.stableGlobals.has(value.localId); - // So does the environment for a capture nothing ever rebinds. - if (binding.kind === "boxed") - return this.stableCaptureIds.has(value.localId) && this.canBorrowReceiver(value); - return ( - binding.kind === "local" && - binding.local !== undefined && - (!binding.local.mutable || - this.stableCallBindings.has(value.localId) || - this.borrowedParameters.has(value.localId) || - this.localUnionStorage.has(value.localId)) && - !binding.local.boxed && - !binding.local.tdz - ); + return canBorrowCallArgument(this, value); } /** A plain-pointer read of a construction-only field outside any * constructor (ImmutableFields). */ - private immutableRead(e: ExprOf<"fieldGet" | "recordGet">): boolean { + immutableRead(e: ExprOf<"fieldGet" | "recordGet">): boolean { if (this.inConstructor || this.immutableFields === null) return false; const t = e.type.kind; if (t !== "object" && t !== "record" && t !== "array" && t !== "string") return false; @@ -7775,7 +4088,7 @@ export class LlEmitter { * rest of its block (its whole lifetime) writes none of the projection's * locations: the source edge then keeps the value alive, and uses that * escape still retain their own copies. */ - private unwrittenAlias(s: IrStmt & { kind: "varDecl" }): boolean { + unwrittenAlias(s: IrStmt & { kind: "varDecl" }): boolean { const cursor = this.stmtCursor; const rw = this.referenceWrites; if (rw === null || s.init === null || cursor === null || cursor.stmts[cursor.index] !== s) @@ -7794,7 +4107,7 @@ export class LlEmitter { * element when the rest of the block writes no array element and none of * the locations keeping the array alive (which must itself be read * without a frame owner). */ - private unwrittenLocalArrayRead(s: IrStmt & { kind: "varDecl" }, read: LocalArrayRead): boolean { + unwrittenLocalArrayRead(s: IrStmt & { kind: "varDecl" }, read: LocalArrayRead): boolean { const cursor = this.stmtCursor; const rw = this.referenceWrites; if (rw === null || s.init === null || cursor === null || cursor.stmts[cursor.index] !== s) @@ -7824,7 +4137,7 @@ export class LlEmitter { * finally copies, like any pending return value, and the adopting await * runs afterwards with no handler in scope: a rejection settles the * function's promise instead of reaching its own catch clauses. */ - private emitAdoptingReturn(s: IrStmt & { kind: "return" }, adopted: ExprOf<"awaitExpr">): void { + emitAdoptingReturn(s: IrStmt & { kind: "return" }, adopted: ExprOf<"awaitExpr">): void { const B = this.B; const promise = this.emitExpr(adopted.value); this.toFrame(promise); @@ -7880,19 +4193,19 @@ export class LlEmitter { /** An async generator completing without a further turn: its request * settles as the body returns (scr_async_gen_hop_done). */ - private emitAsyncGenSettled(): void { + emitAsyncGenSettled(): void { this.declare(`declare void @scr_async_gen_hop_done()`); this.B.line(`call void @scr_async_gen_hop_done()`); } /** Owned-binding reads that take over the binding's reference, by read * node (the binding's slot). */ - private readonly lastUseMoves = new Map(); + readonly lastUseMoves = new Map(); /** How often each varRef node object occurs in the current function: * lowering may share one node between several reads, and a move keyed by * node identity must fire for exactly one of them. */ - private varRefCounts: Map | null = null; - private currentFunctionBody: IrStmt[] = []; + varRefCounts: Map | null = null; + currentFunctionBody: IrStmt[] = []; private varRefCount(e: IrExpr): number { if (this.varRefCounts === null) { @@ -7911,6 +4224,20 @@ export class LlEmitter { /** Moves need plain slots that nothing else reads behind the binding's * back: no debugger view, no coroutine frame, no borrowed (+0) return. */ + /** Whether the program reads event-loop utilization (perf.loopStart / + * perf.idleTime): main then marks the loop's start before running it. */ + private loopMetricsRead: boolean | undefined; + readsLoopMetrics(): boolean { + if (this.loopMetricsRead !== undefined) return this.loopMetricsRead; + const visitor = { + expr: (e: IrExpr): boolean => + !(e.kind === "libCall" && (e.fn === "perf.loopStart" || e.fn === "perf.idleTime")), + stmt: (): boolean => true, + }; + this.loopMetricsRead = !this.mod.functions.every((fn) => everyStmtList(fn.body, visitor)); + return this.loopMetricsRead; + } + private lastUseMovesAllowed(): boolean { return ( this.debug === null && @@ -7922,10 +4249,7 @@ export class LlEmitter { /** An owned local declared at the statement cursor whose last statement * reads it once (lastUseRead): that read moves the reference. */ - private noteLastUseMove( - s: IrStmt & { kind: "varDecl" }, - b: ReturnType, - ): void { + noteLastUseMove(s: IrStmt & { kind: "varDecl" }, b: ReturnType): void { const cursor = this.stmtCursor; if ( !this.lastUseMovesAllowed() || @@ -7947,7 +4271,7 @@ export class LlEmitter { * parameter's slot (they need it until the function returns), so bodies * with walks keep their parameters, and locals rebound from the * parameter count as derived from it. */ - private noteParameterMove(fn: IrFunction, localId: string, slot: string): void { + noteParameterMove(fn: IrFunction, localId: string, slot: string): void { if ( !this.lastUseMovesAllowed() || fn.async || @@ -7972,7 +4296,7 @@ export class LlEmitter { /** `return x` of a local whose slot is an owned scope entry of the same * type: the return can take over the local's reference. */ - private movableReturn( + movableReturn( value: IrExpr | null, ): { scope: LlScopeEntry[]; entry: LlScopeEntry; index: number } | null { if (value === null || value.kind !== "varRef" || !isRefCounted(value.type)) return null; @@ -7994,7 +4318,7 @@ export class LlEmitter { /** A walk host for a body that may write the heap: each walk must read * only locations the whole body (with its callees) never writes; * rebinding the walk locals themselves releases nothing. */ - private unwrittenWalkHost(fn: IrFunction, host: WalkBorrowHost): WalkBorrowHost { + unwrittenWalkHost(fn: IrFunction, host: WalkBorrowHost): WalkBorrowHost { return { ...host, unwritten: (value, candidates) => @@ -8094,7 +4418,7 @@ export class LlEmitter { // ── statements ────────────────────────────────────────────────────────── - private emitStmts(stmts: IrStmt[], scoped = false): void { + emitStmts(stmts: IrStmt[], scoped = false): void { const saved = this.stmtCursor; const cursor = scoped ? { stmts, index: 0 } : null; try { @@ -8117,7 +4441,7 @@ export class LlEmitter { * scope opens, before the statements — the catch-binding hook: it may * emit prelude lines and register entries the scope owns (released on * every exit, jumps and unwinds included). */ - private emitBlock(stmts: IrStmt[], setup?: (scope: LlScopeEntry[]) => void): void { + emitBlock(stmts: IrStmt[], setup?: (scope: LlScopeEntry[]) => void): void { const scope: LlScopeEntry[] = []; this.scopes.push(scope); setup?.(scope); @@ -8151,1674 +4475,21 @@ export class LlEmitter { if (loop) this.loopDepth--; } } - private emitStmtBody(s: IrStmt): void { - const B = this.B; - this.frames.push([]); - switch (s.kind) { - case "varDecl": { - if (s.init && this.int32Locals.has(s.localId)) { - this.storeInt32Local(s.localId, this.emitExpr(s.init), s.init); - break; - } - const b = this.binding(s.localId); - if (this.walkBorrows.has(s.localId) && b.kind === "local") { - // A borrowed walk pointer: no retain now, no release at scope exit. - const v = s.init === null ? "null" : this.emitReadReceiver(s.init).name; - B.line(`store ptr ${v}, ptr ${b.slot}`); - break; - } - const slice = this.scalarStringSlices.get(s.localId); - if (slice) { - const snapshot = emitStringSliceSnapshot(this, slice); - this.stringSlices.set(s.localId, snapshot); - this.scopes[this.scopes.length - 1]!.push({ slot: snapshot.source, type: STRING }); - break; - } - const split = this.privateSplitLocals.get(s.localId); - if (split) { - const snapshot = storeSplitSnapshot(this, emitSplitSnapshot(this, split)); - this.storedSplits.set(s.localId, snapshot); - this.scopes[this.scopes.length - 1]!.push( - { slot: snapshot.sourceSlot, type: STRING }, - { slot: snapshot.separatorSlot, type: STRING }, - ); - break; - } - const stackObject = this.localStackObjects.get(s.localId); - if (stackObject) { - const value = emitStackObject(this, stackObject, (fields) => this.ownScopeFields(fields)); - B.line(`store ptr ${value.name}, ptr ${b.slot}`); - break; - } - if (s.init && this.reassignedStackObjects.has(s.localId)) { - const slot = this.stackObjectSlot(s.localId, s.init.type); - emitStackObject( - this, - s.init as StackObjectSource, - (fields) => this.ownScopeFields(fields), - slot, - ); - B.line(`store ptr ${slot}, ptr ${b.slot}`); - break; - } - const localUnion = this.localStackUnions.get(s.localId); - if (localUnion) { - const union = emitStackUnion(this, localUnion); - B.line(`store ptr ${union.value.name}, ptr ${b.slot}`); - if (union.payload) { - this.toFrame(union.payload); - this.scopes[this.scopes.length - 1]!.push({ - slot: union.payloadSlot, - type: union.payload.type, - }); - } - break; - } - const localRead = this.localArrayReads.get(s.localId); - if (localRead) { - const owner = emitLocalArrayRead( - this, - !localRead.borrow && this.unwrittenLocalArrayRead(s, localRead) - ? { ...localRead, borrow: true } - : localRead, - b.slot, - ); - if (owner) this.scopes[this.scopes.length - 1]!.push(owner); - break; - } - const mapRead = this.mapReadLifetimes.locals.get(s.localId); - if (mapRead) { - const result = emitStackMapRead(this, mapRead); - B.line(`store ptr ${result.value.name}, ptr ${b.slot}`); - if (result.owner) this.scopes[this.scopes.length - 1]!.push(result.owner); - break; - } - const localStorage = this.localUnionStorageProofs.get(s.localId); - if (localStorage && s.init) { - const storage = allocateLocalUnion(this, localStorage); - this.localUnionStorage.set(s.localId, storage); - storeLocalUnion(this, storage, s.init); - B.line(`store ptr ${storage.box}, ptr ${b.slot}`); - if (storage.owner && storage.ownerType) - this.scopes[this.scopes.length - 1]!.push({ - slot: storage.owner, - type: storage.ownerType, - }); - break; - } - if (b.kind === "boxed") { - const stack = this.stackCaptures.emit(this, s.localId, b.type); - if (stack) { - B.line(`store ptr ${stack.box}, ptr ${b.slot}`); - if (stack.owner) this.scopes[this.scopes.length - 1]!.push(stack.owner); - if (s.init) { - const value = this.emitExpr(s.init); - if (isRefCounted(value.type)) this.toFrame(value); - this.boxSet(stack.box, b.type, value.name); - } - break; - } - // Box FIRST, then evaluate the initializer: a named function - // expression's closure captures this box during init evaluation. - // A SCALAR TDZ box rides an ARR-kind box: the value lives in a - // one-element array cell, so the empty (NULL) slot stays the - // not-yet-initialized sentinel — a raw scalar slot has no spare - // bit pattern to spend on it (stmts.ts's varDecl). - const boxNew = - b.local!.tdz === true && boxAccess(b.type) !== "ref" - ? (this.declare(`declare ptr @scr_box_new(i32)`), `call ptr @scr_box_new(i32 3)`) - : boxNewCall(this.shapeHost, b.type); - const box = B.tmp(); - B.line(`${box} = ${boxNew} ; let ${b.local!.name} (boxed)`); - B.line(`store ptr ${box}, ptr ${b.slot}`); - this.scopes[this.scopes.length - 1]!.push({ slot: b.slot, type: b.type, boxed: true }); - if (s.init === null) { - // An empty TDZ box whose payload can legitimately be NULL - // starts at its distinct sentinel (tdzEmptySentinel). - const sentinel = b.local!.tdz === true ? this.tdzEmptySentinel(b.type) : null; - if (sentinel !== null) B.line(`store ptr ${sentinel}, ptr ${this.boxSlot(box)}`); - break; - } - const v = this.emitExpr(s.init); - if (isRefCounted(v.type)) this.toHeap(v); // the box takes ownership - if (b.local!.tdz === true && boxAccess(b.type) !== "ref") { - this.tdzScalarInit(box, b.type, v.name); - } else { - this.boxSet(box, b.type, v.name); - } - break; - } - if (s.init === null) { - // Declared, uninitialized (`let x: number;`): reset the slot — - // inside a loop the previous iteration's scope exit released the - // old value and left a stale pointer (NULL-tolerant releases). - if (isRefCounted(b.type)) { - B.line(`store ptr null, ptr ${b.slot}`); - this.scopes[this.scopes.length - 1]!.push({ slot: b.slot, type: b.type }); - } - break; - } - if (this.loopArrayBorrows.has(s) && s.init.kind === "arrayGet") { - emitBorrowedArrayRead(this, s.init, b.slot); - break; - } - const holeRead = this.loopArrayBorrows.has(s) ? dynHoleRead(s.init) : null; - if (holeRead && s.init.kind === "ternary") { - // A hole reads as the immortal undefined value, which needs no owner. - const present = this.emitExpr(s.init.cond); - const filled = B.newLabel("borrow.hole.filled"), - missing = B.newLabel("borrow.hole.missing"), - done = B.newLabel("borrow.hole.done"); - B.condBr(present.name, filled, missing); - B.startBlock(filled); - emitBorrowedArrayRead(this, holeRead, b.slot); - B.br(done); - B.startBlock(missing); - this.declare("declare ptr @scr_dyn_undefined()"); - const undef = B.tmp(); - B.line(`${undef} = call ptr @scr_dyn_undefined()`); - B.line(`store ptr ${undef}, ptr ${b.slot}`); - B.br(done); - B.startBlock(done); - break; - } - // A stable lexical alias cannot outlive its unchanged local owner. - // Whole-value uses still retain their own copies, so returning or - // storing the alias preserves the ordinary heap representation. - // Boxed captures, reassigned sources and projections need ownership. - if ( - b.kind === "local" && - b.local && - isRefCounted(b.type) && - this.stableCallBindings.has(s.localId) && - (this.canBorrowCallArgument(s.init) || this.unwrittenAlias(s)) - ) { - const v = this.emitReadReceiver(s.init); - B.line(`store ptr ${v.name}, ptr ${b.slot}`); - break; - } - const v = this.emitExpr(s.init); - if (b.kind === "global") this.toHeap(v); - else this.toFrame(v); - B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`); - this.storeIntegerView(s.localId, v, s.init); - if (isRefCounted(b.type)) { - this.scopes[this.scopes.length - 1]!.push({ slot: b.slot, type: b.type }); - this.noteLastUseMove(s, b); - } - break; - } - case "assign": { - if (this.int32Locals.has(s.localId)) { - this.storeInt32Local(s.localId, this.emitExpr(s.value), s.value); - break; - } - const owner = this.reboundOwnerSlots.get(s.localId); - if (owner !== undefined && !this.walkBorrows.has(s.localId)) { - // A rebound borrowed parameter: the new value moves into the owner - // slot, replacing (and releasing) the previous owned value. This - // precedes the in-place string append, which would release the - // caller's borrow. - const param = this.binding(s.localId); - const slot = param.slot; - const v = this.emitExpr(s.value); - this.toFrame(v); - const old = B.tmp(); - B.line(`${old} = load ptr, ptr ${owner}`); - this.releaseFrame(old, param.type); - B.line(`store ptr ${v.name}, ptr ${slot}`); - B.line(`store ptr ${v.name}, ptr ${owner}`); - break; - } - if (this.reassignedStackObjects.has(s.localId)) { - // The new value may read the old one: build it apart (its - // reference fields owned by this statement until it completes), - // then copy it over the binding's slot. The binding takes over the - // new fields; the old ones are released after the slot no longer - // holds them. - const struct = stackObjectStruct(s.value.type); - let owned: readonly StackObjectField[] = []; - const value = emitStackObject(this, s.value as StackObjectSource, (fields) => { - owned = fields; - for (const field of fields) this.ownSlot(field.ptr, field.type); - }); - const target = this.stackObjectSlot(s.localId, s.value.type); - const old = stackObjectFields(this, s.value.type, target).map((field) => { - const t = B.tmp(); - B.line(`${t} = load ptr, ptr ${field.ptr}`); - return { name: t, type: field.type }; - }); - const bits = B.tmp(); - B.line(`${bits} = load ${struct}, ptr ${value.name}`); - B.line(`store ${struct} ${bits}, ptr ${target}`); - this.disownSlots(owned); - for (const field of old) this.releaseFrame(field.name, field.type); - break; - } - const localStorage = this.localUnionStorage.get(s.localId); - if (localStorage) { - storeLocalUnion(this, localStorage, s.value); - break; - } - const self = matchStringSelfConcat(s.localId, s.value); - if (self) { - this.emitStringSelfConcatAssign(s.localId, self.left, self.suffix, false); - break; - } - const b = this.binding(s.localId); - if (this.walkBorrows.has(s.localId) && b.kind === "local") { - // Rebinding a borrowed walk pointer releases nothing. - B.line(`store ptr ${this.emitReadReceiver(s.value).name}, ptr ${b.slot}`); - break; - } - const v = this.emitExpr(s.value); - if (b.kind === "global" && !s.initializes) this.checkGlobalTdz(s.localId); - if (b.kind === "boxed") { - this.writeBindingBox(this.loadBox(b.slot), b.local!, v.name, s.initializes); - // The RHS remains frame-owned until a possible TDZ throw passes. - if (isRefCounted(v.type)) this.toHeap(v); - break; - } - if (b.kind === "global") this.toHeap(v); - else this.toFrame(v); - if (isRefCounted(b.type)) { - const old = B.tmp(); - B.line(`${old} = load ptr, ptr ${b.slot}`); - if (b.kind === "global") this.releaseHeap(old, b.type); - else this.releaseFrame(old, b.type); - } - B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`); - this.storeIntegerView(s.localId, v, s.value); - if (b.kind === "global" && this.immortalGlobals.has(s.localId)) { - // An Immutable module constant (contracts/check.ts proved it is - // fresh, publishable and never written): its graph becomes - // immortal, so reads skip reference counting and the collector - // never visits it. - const lines = this.publishWalkers.emitPublish(v.name, v.type, s.loc); - for (const line of lines) B.line(line.trimStart()); - // Reads of this global skip retains from here on, which is only - // sound once the graph really is immortal. Node.js never fails - // here, so a refusal means the contract proof was wrong: stop - // rather than keep running with a mortal graph read as immortal. - if (lines.length > 0) { - this.declare(`declare zeroext i1 @scr_exc_pending()`); - this.declare(`declare void @scr_trap(ptr)`); - const refused = B.tmp(); - B.line(`${refused} = call zeroext i1 @scr_exc_pending()`); - const stop = B.newLabel("immutable.refused"); - const done = B.newLabel("immutable.published"); - B.condBr(refused, stop, done); - B.startBlock(stop); - B.line( - `call void @scr_trap(ptr ${this.cstr("scriptc: internal error: an Immutable value could not be published\n")})`, - ); - B.terminate("unreachable"); - B.startBlock(done); - } - } - break; - } - case "exprStmt": - // A statement-position splice never observes its removed elements. - if (!emitDiscardedSplice(this, s.expr)) this.emitDiscarded(s.expr); - break; - case "arraySet": { - // Evaluation order matches JS: array, index, then value. Ownership - // of a refcounted value moves into the array (the runtime releases - // the replaced element itself). A local receiver that the index and - // value cannot rebind is read in place, as for field stores; the - // released element cannot reach that binding. - if (this.emitArraySelfConcat(s)) break; - const arr = this.emitStableReceiver(s.arr, [s.index, s.value]); - const idx = this.emitExpr(s.index); - const v = this.emitExpr(s.value); - if (s.arr.type.kind !== "array") - throw new InternalCompilerError("llvm emitter bug: arraySet on non-array"); - const acc = elemAccess(s.arr.type.elem); - this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name); - if (acc === "ref") this.toHeap(v); - emitDenseArraySet( - this, - arr.name, - idx, - s.index, - acc, - s.arr.type.elem, - v.name, - this.provenDenseIndex(s.arr, s.index), - ); - break; - } - case "arraySetLength": { - // Pop's `length = 0` store on an empty array only matters when the - // array can be frozen (published); its operand is a local. - if ( - s.pop === true && - s.length.kind === "numLit" && - s.length.value === 0 && - this.publishedTypes?.containerGuarded(s.arr.type) !== true - ) - break; - const arr = emitBorrowedInput(this, s.arr); - const length = this.emitExpr(s.length); - if (s.arr.type.kind !== "array") - throw new InternalCompilerError("llvm emitter bug: arraySetLength on non-array"); - this.emitPublishedArrayGuard(arr.name, s.arr.type, s.pop === true ? 2 : 3); - this.declare(`declare void @scr_arr_set_len(ptr, double)`); - // Assigning the current length (a reused work array reset to where - // it already is) changes nothing: the runtime's validation accepts - // exactly the value equal to the length (-0 included, NaN never), - // and no element is released. Everything else keeps the call. - const lenPtr = B.tmp(), - len = B.tmp(), - lenNumber = B.tmp(), - same = B.tmp(); - const resize = B.newLabel("arr.len.set"), - done = B.newLabel("arr.len.done"); - B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`); - this.markMemoryPointer(lenPtr, "array:header"); - B.line( - `${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`, - ); - B.line(`${lenNumber} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${len} to double`); - B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`); - B.condBr(same, done, resize); - B.startBlock(resize); - if (s.pop === true) { - // A discarded pop's `length - 1` on a packed array removes the - // last element inline, then releases it. - const call = B.newLabel("arr.len.call"), - pop = B.newLabel("arr.len.pop"); - const lastNumber = B.tmp(), - dropsLast = B.tmp(); - B.line(`${lastNumber} = fsub double ${lenNumber}, 1.0`); - B.line(`${dropsLast} = fcmp oeq double ${length.name}, ${lastNumber}`); - B.condBr(dropsLast, pop, call); - B.startBlock(pop); - const elem = s.arr.type.elem; - const acc = elemAccess(elem); - const raw = emitDenseArrayPop(this, arr.name, acc, call, call); - if (acc === "ref" && isRefCounted(elem)) { - const old = B.tmp(); - B.line(`${old} = inttoptr i64 ${raw} to ptr`); - this.releaseHeap(old, elem); - } - B.br(done); - B.startBlock(call); - } - B.line(`call void @scr_arr_set_len(ptr ${arr.name}, double ${length.name})`); - this.emitPendingCheck(); - B.br(done); - B.startBlock(done); - break; - } - case "arraySetUndefined": - case "arrayDelete": { - const arr = emitBorrowedInput(this, s.arr); - const idx = this.emitExpr(s.index); - if (s.arr.type.kind !== "array") - throw new InternalCompilerError(`llvm emitter bug: ${s.kind} on non-array`); - this.emitPublishedArrayGuard( - arr.name, - s.arr.type, - s.kind === "arrayDelete" ? 5 : 0, - idx.name, - ); - const fn = s.kind === "arraySetUndefined" ? "scr_arr_set_undefined" : "scr_arr_delete"; - this.declare( - `declare ${s.kind === "arrayDelete" ? "zeroext i1" : "void"} @${fn}(ptr, double)`, - ); - if (s.kind === "arrayDelete") - B.line(`call zeroext i1 @${fn}(ptr ${arr.name}, double ${idx.name})`); - else B.line(`call void @${fn}(ptr ${arr.name}, double ${idx.name})`); - break; - } - case "bytesSet": { - // Typed-array element write: same evaluation order as arraySet; - // the value is a scalar (the kind-specific inline path coerces - // JS-exactly), so no ownership moves. Invalid writes are ignored. - // IrBytesElem is static, so never rediscover it through the - // generic runtime switch in a hot loop. - const arr = this.emitStableReceiver(s.arr, [s.index, s.value]); - const idx = this.emitExpr(s.index); - const v = this.emitExpr(s.value); - if (s.arr.type.kind !== "bytes") - throw new InternalCompilerError("llvm emitter bug: bytesSet on non-bytes"); - this.emitBytesSet( - s.arr.type.elem, - arr.name, - idx, - v, - s.index, - s.value, - this.bytesBounds.has(s), - ); - break; - } - case "fieldSet": - case "recordSet": { - // Evaluation order: obj, then value. New value moved in; the old - // value is released AFTER the field is overwritten (unlink-then- - // release — a release can trigger a cycle collection, which must - // never see a heap edge whose count was already given up). - // Classes and records share the struct layout, so one emission. - const nullable = - s.kind === "fieldSet" ? this.nullableFields.get(s.className, s.field) : null; - if (nullable && s.kind === "fieldSet") { - const obj = this.emitStableReceiver(s.obj, [s.value]); - // (The guard precedes the value here: the nullable store - // evaluates it itself.) - this.emitStoreGuard(obj.name, s); - this.emitNullableFieldStore(obj.name, s.className, s.field, nullable, s.value); - break; - } - if (s.kind === "fieldSet" && this.int32Slots.isField(s.className, s.field)) { - const obj = this.emitStableReceiver(s.obj, [s.value]); - const v = this.emitExpr(s.value); - this.emitStoreGuard(obj.name, s); - const { ptr } = this.classFieldPtr(obj.name, s.className, s.field); - this.storeInt32Field(ptr, v, s.value); - break; - } - if (this.emitFieldSelfConcat(s)) break; - const obj = this.emitStableReceiver(s.obj, [s.value]); - const v = this.emitFieldValue( - s.value, - s.kind === "recordSet" - ? this.recordFieldInline(s.shapeId, s.field) - : this.inlineUnions.classField(s.className, s.field), - ); - if (s.kind === "fieldSet") this.emitStoreGuard(obj.name, s); - else if (this.publishedTypes?.recordGuarded(s.shapeId) === true) - this.emitPublishedFieldGuard(obj.name, null, s.field); - const { ptr, type, inline } = - s.kind === "fieldSet" - ? this.classFieldPtr(obj.name, s.className, s.field) - : this.recordFieldPtr(obj.name, s.shapeId, s.field); - // A result-slot entry fills zeroed caller storage, each field once. - if ( - s.kind === "recordSet" && - s.obj.kind === "varRef" && - s.obj.localId === RESULT_DST_ID && - this.currentFunctionName.startsWith(RESULT_ENTRY_PREFIX) - ) - this.initField(ptr, type, inline, v); - else this.replaceField(ptr, type, inline, v); - if ( - s.kind === "fieldSet" && - s.field === "name" && - this.classMeta.get(s.className)?.root.def.name === "%Error" - ) { - const present = this.classFieldPtr(obj.name, s.className, "%namePresent").ptr; - const enumerable = this.classFieldPtr(obj.name, s.className, "%nameEnumerable").ptr; - const wasPresent = B.tmp(), - wasEnumerable = B.tmp(), - isPresent = B.tmp(), - nextEnumerable = B.tmp(); - B.line(`${wasPresent} = load i8, ptr ${present}`); - B.line(`${wasEnumerable} = load i8, ptr ${enumerable}`); - B.line(`${isPresent} = icmp ne i8 ${wasPresent}, 0`); - B.line(`${nextEnumerable} = select i1 ${isPresent}, i8 ${wasEnumerable}, i8 1`); - B.line(`store i8 1, ptr ${present}`); - B.line(`store i8 ${nextEnumerable}, ptr ${enumerable}`); - } - break; - } - case "recordKeyDelete": { - // `delete obj[k]` on a pure index-signature shape: a Map delete on - // the overflow (key and value released; absent keys no-op). - const obj = this.emitExpr(s.obj); - const key = this.emitExpr(s.key); - if (this.publishedTypes?.recordGuarded(s.shapeId) === true) { - // Node: "Cannot delete property 'k' of #" (key known at - // run time only; the guard reports the frozen record generically). - this.emitPublishedFieldGuard(obj.name, null, "[key]"); - } - const ovf = this.recordOvfPtr(obj.name, s.shapeId); - this.declare(`declare zeroext i1 @scr_map_delete_str(ptr, ptr)`); - const t = B.tmp(); - B.line(`${t} = call zeroext i1 @scr_map_delete_str(ptr ${ovf}, ptr ${key.name})`); - break; - } - case "recordKeySet": { - // Dynamic-keyed record write (the C per-shape helper, inline): - // declared keys write through (same-typed — dyn-valued shapes, - // whose writes validate and can throw, stay refused), undeclared - // keys insert/replace in the overflow map. Evaluation order: obj, - // key, value; the write OWNS the value (+1 moves in). - const obj = this.emitExpr(s.obj); - const key = this.emitExpr(s.key); - const v = this.emitExpr(s.value); - if (this.publishedTypes?.recordGuarded(s.shapeId) === true) - this.emitPublishedFieldGuard(obj.name, null, "[key]"); - if (isRefCounted(v.type)) this.toHeap(v); - const shape = this.recordShape(s.shapeId); - // Signature-free shapes dispatch over their (one-typed) declared - // fields and TRAP on a miss (scr_record_key_miss — JS would add - // the property, which a monomorphic struct cannot); overflow - // shapes keep the map insert tail. - const iv = shape.indexValue ?? shape.fields[0]?.type; - if (!iv) - throw new InternalCompilerError( - `llvm emitter bug: keyed write on field-free non-overflow shape ${s.shapeId}`, - ); - const vAcc = iv.kind === "f64" ? "f64" : iv.kind === "bool" ? "bool" : "ref"; - if (s.overflowOnly === true) { - // A LITERAL key naming no declared field: a plain overflow - // insert — no field chain. - const ovf = this.recordOvfPtr(obj.name, s.shapeId); - this.mapSet(ovf, "str", vAcc, key.name, v.name); - break; - } - const join = B.newLabel("rks.j"); - if (iv.kind === "dyn" && shape.indexValue) { - // A dyn-valued shape (the C recordKeySetHelper's dyn arm, - // inline): declared keys VALIDATE the dyn value against the - // field's type first (dynCheck — a mismatched write throws the - // catchable TypeError and leaves the field untouched; JS would - // store anything, the documented divergence); undeclared keys - // insert the dyn value into the overflow map as-is. - this.declare(`declare void @scr_dyn_release(ptr)`); - for (const f of shape.fields) { - const hit = B.tmp(); - B.line( - `${hit} = call zeroext i1 ${this.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`, - ); - const lh = B.newLabel("rks.h"); - const ln = B.newLabel("rks.n"); - B.condBr(hit, lh, ln); - B.startBlock(lh); - const pathSlot = B.slot(); - B.entryAllocas.push(`${pathSlot} = alloca %ScrDynPath`); - const pp = B.tmp(); - const kp = B.tmp(); - const ip = B.tmp(); - B.line(`${pp} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 0`); - B.line(`store ptr null, ptr ${pp}`); - B.line(`${kp} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 1`); - B.line(`store ptr ${this.cstr(f.name)}, ptr ${kp}`); - B.line(`${ip} = getelementptr inbounds %ScrDynPath, ptr ${pathSlot}, i64 0, i32 2`); - B.line(`store ${this.sizeType} 0, ptr ${ip}`); - const helper = this.dyn.dynCheckHelper(f.type); - const fty = this.llType(f.type); - const nv = B.tmp(); - B.line( - `${nv} = call ${fty === "i1" ? "zeroext i1" : fty} @${helper}(ptr ${v.name}, ptr ${pathSlot})`, - ); - B.line(`call void @scr_dyn_release(ptr ${v.name})`); - // Mismatched write: TypeError pending, field untouched — the - // statement-level check below unwinds. - const pend = B.tmp(); - for (const line of this.pendingTestLines(pend)) B.line(line.trimStart()); - const lw = B.newLabel("rks.w"); - B.condBr(pend, join, lw); - B.startBlock(lw); - const { ptr, type, inline } = this.recordFieldPtr(obj.name, s.shapeId, f.name); - if (inline) { - // The slot copies the checked value's bits; nv is released below. - this.storeField(ptr, type, nv, true); - this.releaseFrame(nv, type); - } else if (isRefCounted(type)) { - const old = B.tmp(); - B.line(`${old} = load ptr, ptr ${ptr}`); - this.storeField(ptr, type, nv, false); - this.releaseHeap(old, type); - } else { - this.storeField(ptr, type, nv, false); - } - B.br(join); - B.startBlock(ln); - } - const ovf = this.recordOvfPtr(obj.name, s.shapeId); - this.mapSet(ovf, "str", vAcc, key.name, v.name); - B.br(join); - B.startBlock(join); - // MAY THROW exactly when a dyn value can validate against a - // declared field (stmts.ts's condition). - if (shape.fields.length > 0) this.emitPendingCheck(); - break; - } - for (const f of shape.fields) { - // typeEquals(f.type, iv) — the frontend fences everything else. - const hit = B.tmp(); - B.line( - `${hit} = call zeroext i1 ${this.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`, - ); - const lh = B.newLabel("rks.h"); - const ln = B.newLabel("rks.n"); - B.condBr(hit, lh, ln); - B.startBlock(lh); - const { ptr, type, inline } = this.recordFieldPtr(obj.name, s.shapeId, f.name); - if (inline) { - // The write owns the moved-in value; the slot copies its bits. - this.storeField(ptr, type, v.name, true); - this.releaseHeap(v.name, type); - } else if (isRefCounted(type)) { - const old = B.tmp(); - B.line(`${old} = load ptr, ptr ${ptr}`); - this.storeField(ptr, type, v.name, false); - this.releaseHeap(old, type); - } else { - this.storeField(ptr, type, v.name, false); - } - B.br(join); - B.startBlock(ln); - } - if (!shape.indexValue) { - // The MISS on a fixed shape: release the moved-in value, throw - // the catchable TypeError naming the key (scr_record_key_miss — - // JS would add the property, the documented divergence). - if (isRefCounted(iv)) this.releaseHeap(v.name, iv); - this.declare(`declare void @scr_record_key_miss(ptr)`); - B.line(`call void @scr_record_key_miss(ptr ${key.name})`); - B.br(join); - B.startBlock(join); - this.emitPendingCheck(); - break; - } - const ovf = this.recordOvfPtr(obj.name, s.shapeId); - this.mapSet(ovf, "str", vAcc, key.name, v.name); - B.br(join); - B.startBlock(join); - break; - } - case "block": { - if (s.labels === undefined) { - this.emitBlock(s.body); - break; - } - // A labeled block: `break lbl` inside branches to the end label. - const le = B.newLabel("blk.e"); - this.jumpTargets.push({ - kind: "block", - brkLabel: le, - contLabel: null, - labels: s.labels, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - finallyDepth: this.finallyStack.length, - }); - this.emitBlock(s.body); - this.jumpTargets.pop(); - B.br(le); - B.startBlock(le); - break; - } - case "if": { - const cond = this.emitCondition(s.cond); - const trueLabel = B.newLabel("if.t"); - const joinLabel = B.newLabel("if.j"); - const falseLabel = s.else_ ? B.newLabel("if.f") : joinLabel; - B.condBr(cond, trueLabel, falseLabel); - B.startBlock(trueLabel); - this.emitBlock(s.then); - B.br(joinLabel); - if (s.else_) { - B.startBlock(falseLabel); - this.emitBlock(s.else_); - B.br(joinLabel); - } - B.startBlock(joinLabel); - break; - } - case "while": { - const workerBudget = this.workerLoopBudget(s); - const le = B.newLabel("loop.e"); - this.emitLoopCopies(s, true, le, (exit) => { - const lc = B.newLabel("loop.c"); - const lb = B.newLabel("loop.b"); - const lp = workerBudget !== null ? B.newLabel("loop.p") : lc; - B.br(lc); - B.startBlock(lc); - B.condBr(this.emitCondition(s.cond), lb, exit); - B.startBlock(lb); - this.jumpTargets.push({ - kind: "loop", - brkLabel: exit, - contLabel: lp, - labels: s.labels, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - finallyDepth: this.finallyStack.length, - }); - this.emitBlock(s.body); - this.jumpTargets.pop(); - B.br(lp); - if (lp !== lc) { - B.startBlock(lp); - this.emitWorkerLoopCheck(workerBudget); - B.br(lc); - } - }); - B.startBlock(le); - break; - } - case "doWhile": { - const workerBudget = this.workerLoopBudget(s); - // Body first (runs at least once); continue jumps to the CONDITION. - const le = B.newLabel("loop.e"); - this.emitLoopCopies(s, true, le, (exit) => { - const lb = B.newLabel("loop.b"); - const lc = B.newLabel("loop.c"); - B.br(lb); - B.startBlock(lb); - this.jumpTargets.push({ - kind: "loop", - brkLabel: exit, - contLabel: lc, - labels: s.labels, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - finallyDepth: this.finallyStack.length, - }); - this.emitBlock(s.body); - this.jumpTargets.pop(); - B.br(lc); - B.startBlock(lc); - this.emitWorkerLoopCheck(workerBudget); - B.condBr(this.emitCondition(s.cond), lb, exit); - }); - B.startBlock(le); - break; - } - case "for": { - // The init's scope wraps the whole loop (break/continue must NOT - // release it — scopeDepth captured after the push, C parity). - const integerArrayLoop = - this.debug === null - ? matchIntegerArrayForLoop(s, this.currentLocals, this.integerArrayBindings) - : null; - const integerLoop = - integerArrayLoop ?? - (this.debug === null ? matchIntegerBytesForLoop(s, this.numericLocals) : null); - const countedLoop = - !integerLoop && this.countedLoopsEnabled - ? matchIntegerCountedForLoop( - s, - this.numericLocals, - this.integerRanges, - this.unaliasedArrays, - ) - : null; - const window = - countedLoop && this.byteWindowsEnabled - ? matchByteWindow( - s, - countedLoop, - this.numericLocals, - this.captureIds, - this.integerRanges, - 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"), - slow = B.newLabel("bytes.window.slow"), - done = B.newLabel("bytes.window.done"); - B.condBr(valid, fast, slow); - const originalRanges = this.integerRanges, - originalBounds = this.bytesBounds; - this.byteWindowsEnabled = false; - try { - B.startBlock(slow); - this.emitStmt(s); - B.br(done); - B.startBlock(fast); - this.integerRanges = window.ranges; - this.bytesBounds = new Set([...originalBounds, ...window.bounds]); - this.emitStmt(s); - B.br(done); - } finally { - this.integerRanges = originalRanges; - this.bytesBounds = originalBounds; - this.byteWindowsEnabled = true; - } - B.startBlock(done); - break; - } - let countedJoin: string | null = null; - if (countedLoop?.guarded) { - let safe = "true"; - if (countedLoop.limitGuarded) { - const bound = this.emitExpr(countedLoop.limit); - safe = B.tmp(); - B.line( - `${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`, - ); - } - if (countedLoop.startGuarded) { - // An exact integer of at most 2^53 - 1 in magnitude, and not -0 - // (the bit patterns of the value and its round trip agree). - // NaN and infinities fail the ordered range tests. - const start = this.emitExpr(countedLoop.start); - const low = B.tmp(), - high = B.tmp(), - raw = B.tmp(), - wide = B.tmp(), - back = B.tmp(), - bits = B.tmp(), - backBits = B.tmp(), - exact = B.tmp(), - inRange = B.tmp(), - ok = B.tmp(); - B.line(`${low} = fcmp oge double ${start.name}, ${f64Lit(-Number.MAX_SAFE_INTEGER)}`); - B.line(`${high} = fcmp ole double ${start.name}, ${f64Lit(Number.MAX_SAFE_INTEGER)}`); - B.line(`${inRange} = and i1 ${low}, ${high}`); - B.line(`${raw} = fptosi double ${start.name} to i64`); - B.line(`${wide} = freeze i64 ${raw}`); - B.line(`${back} = sitofp i64 ${wide} to double`); - B.line(`${bits} = bitcast double ${start.name} to i64`); - B.line(`${backBits} = bitcast double ${back} to i64`); - B.line(`${exact} = icmp eq i64 ${bits}, ${backBits}`); - B.line(`${ok} = and i1 ${inRange}, ${exact}`); - if (safe === "true") safe = ok; - else { - const both = B.tmp(); - B.line(`${both} = and i1 ${safe}, ${ok}`); - safe = both; - } - } - const fast = B.newLabel("counted.fast"); - const slow = B.newLabel("counted.slow"); - countedJoin = B.newLabel("counted.done"); - B.condBr(safe, fast, slow); - B.startBlock(slow); - this.countedLoopsEnabled = false; - this.emitStmt(s); - this.countedLoopsEnabled = true; - B.br(countedJoin); - B.startBlock(fast); - } - this.scopes.push([]); - let integerSlot: string | null = null; - let countedLimit: string | null = null; - if (integerLoop) { - integerSlot = B.slot(); - B.entryAllocas.push( - `${integerSlot} = alloca ${this.sizeType} ; integer induction ${this.currentLocals.get(integerLoop.localId)!.name}`, - ); - let start = "0"; - if (integerArrayLoop && s.init?.kind === "varDecl" && s.init.init) { - const value = this.emitExpr(s.init.init); - start = B.tmp(); - // The start is 0 or an enclosing array index plus one (at most - // 2^32 - 1): on 64-bit targets the signed conversion is exact - // and avoids x86-64's unsigned 64-bit conversion sequence. - const conversion = this.sizeType === "i64" ? "fptosi" : "fptoui"; - B.line(`${start} = ${conversion} double ${value.name} to ${this.sizeType}`); - this.integerArrayBindings.add(integerLoop.localId); - } - B.line(`store ${this.sizeType} ${start}, ptr ${integerSlot}`); - this.integerLoopBindings.set(integerLoop.localId, { - slot: integerSlot, - type: this.sizeType, - 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(); - B.entryAllocas.push( - `${integerSlot} = alloca i64 ; integer induction ${this.currentLocals.get(countedLoop.localId)!.name}`, - ); - const startNumber = this.emitExpr(countedLoop.start); - const start = B.tmp(); - B.line(`${start} = fptosi double ${startNumber.name} to i64`); - B.line(`store i64 ${start}, ptr ${integerSlot}`); - this.integerLoopBindings.set(countedLoop.localId, { - slot: integerSlot, - type: "i64", - signed: true, - range: - this.denseLoop?.counter === countedLoop.localId - ? this.denseLoop.range - : countedLoop.range, - }); - countedLimit = emitCountedLoopLimit(this, countedLoop); - } else if (s.init) { - // A multi-declarator head shares the loop's scope. Emitting its - // IR block as an ordinary block would release captured/ref locals - // before the first condition. - const initializers = s.init.kind === "block" ? s.init.body : [s.init]; - for (const initializer of initializers) this.emitStmt(initializer); - } - const le = B.newLabel("loop.e"); - // JS `for (let i ...)`: each iteration gets a FRESH binding holding - // a copy of the previous one (closures made in iteration k keep - // seeing iteration k's value) — only observable, and only emitted, - // when a let variable is captured (boxed). Freshen before the first - // condition and again in the continue-target block before updating. - const initializers = s.init?.kind === "block" ? s.init.body : s.init ? [s.init] : []; - const capturedLets = initializers.flatMap((init) => { - const local = init.kind === "varDecl" ? this.currentLocals.get(init.localId) : undefined; - return local?.boxed && local.mutable ? [local] : []; - }); - const freshenBindings = (): void => { - for (const local of capturedLets) { - const slot = `%${mangleLocal(local.id)}`; - const fresh = B.tmp(); - const old = B.tmp(); - B.line( - `${fresh} = ${boxNewCall(this.shapeHost, local.type)} ; per-iteration ${local.name}`, - ); - B.line(`${old} = load ptr, ptr ${slot}`); - const val = this.boxGet(old, local.type); - this.boxSet(fresh, local.type, val); - this.declare(`declare void @scr_box_release(ptr)`); - B.line(`call void @scr_box_release(ptr ${old})`); - B.line(`store ptr ${fresh}, ptr ${slot}`); - } - }; - const workerBudget = this.workerLoopBudget(s); - freshenBindings(); - this.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => { - const lc = B.newLabel("loop.c"); - const lb = B.newLabel("loop.b"); - const lu = - s.update || capturedLets.length > 0 || workerBudget !== null - ? B.newLabel("loop.u") - : lc; - B.br(lc); - B.startBlock(lc); - if (integerLoop && integerSlot) { - const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []); - const lenPtr = B.tmp(); - const len = B.tmp(); - const index = B.tmp(); - const inBounds = B.tmp(); - B.line( - `${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`, - ); - if (integerArrayLoop) this.markMemoryPointer(lenPtr, "array:header"); - B.line( - `${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`, - ); - B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`); - B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`); - B.condBr(inBounds, lb, exit); - } else if (countedLoop && integerSlot && countedLimit) { - const index = B.tmp(); - const inBounds = B.tmp(); - B.line(`${index} = load i64, ptr ${integerSlot}`); - B.line( - `${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`, - ); - B.condBr(inBounds, lb, exit); - } else if (s.cond) B.condBr(this.emitCondition(s.cond), lb, exit); - else B.br(lb); - B.startBlock(lb); - this.jumpTargets.push({ - kind: "loop", - brkLabel: exit, - contLabel: lu, - labels: s.labels, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - finallyDepth: this.finallyStack.length, - }); - this.emitBlock(s.body); - this.jumpTargets.pop(); - B.br(lu); - if (lu !== lc) { - B.startBlock(lu); - this.emitWorkerLoopCheck(workerBudget); - freshenBindings(); - if (integerLoop && integerSlot) { - const old = B.tmp(); - const next = B.tmp(); - B.line(`${old} = load ${this.sizeType}, ptr ${integerSlot}`); - B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`); - B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`); - } else if (countedLoop && integerSlot) { - const old = B.tmp(); - const next = B.tmp(); - B.line(`${old} = load i64, ptr ${integerSlot}`); - B.line(`${next} = add nsw i64 ${old}, ${countedLoop.step}`); - B.line(`store i64 ${next}, ptr ${integerSlot}`); - } else if (s.update) this.emitStmt(s.update); - B.br(lc); - } - }); - B.startBlock(le); - this.releaseScope(this.scopes.pop()!); - if (integerLoop) this.integerLoopBindings.delete(integerLoop.localId); - if (countedLoop) this.integerLoopBindings.delete(countedLoop.localId); - if (integerArrayLoop) this.integerArrayBindings.delete(integerArrayLoop.localId); - if (countedJoin) { - B.br(countedJoin); - B.startBlock(countedJoin); - } - break; - } - case "forOf": { - // Ascending index loop; the length is re-read every iteration - // (JS-exact — pushes inside the body extend the iteration). The - // iterable temp lives in this statement's frame, so it is released - // when the whole loop ends (and by `return`'s frame sweep). - if (s.iterable.type.kind !== "array") - throw new LlvmUnsupportedError(`forOf:${s.iterable.type.kind}`, s.loc); - const elem = s.iterable.type.elem; - const stored = - s.iterable.kind === "varRef" ? this.storedSplits.get(s.iterable.localId) : undefined; - const direct = this.streamingSplitsEnabled ? stringSplit(s.iterable) : null; - const snapshot = stored - ? loadSplitSnapshot(this, stored) - : direct - ? emitSplitSnapshot(this, direct) - : null; - const arr = snapshot ? null : this.emitExpr(s.iterable); - const cursor = snapshot ? emitSplitCursor(this, snapshot) : null; - const scratch = snapshot ? emitSplitScratch(this) : null; - const deferred = snapshot && canDeferSplitPiece(s, this.currentLocals.get(s.localId)); - const spanLength = deferred ? B.slot() : null; - if (spanLength) B.entryAllocas.push(`${spanLength} = alloca ${this.sizeType}`); - const idxSlot = snapshot ? null : B.slot(); - if (idxSlot) { - B.entryAllocas.push(`${idxSlot} = alloca double`); - B.line(`store double ${f64Lit(0)}, ptr ${idxSlot}`); - } - const lc = B.newLabel("fof.c"); - const lb = B.newLabel("fof.b"); - const lu = B.newLabel("fof.u"); - const le = B.newLabel("fof.e"); - const workerBudget = this.workerLoopBudget(s); - B.br(lc); - B.startBlock(lc); - const inBounds = B.tmp(); - let cur: string; - if (snapshot && cursor && scratch) { - cur = spanLength - ? emitSplitSpanNext(this, snapshot, cursor, spanLength) - : emitSplitNext(this, snapshot, cursor, scratch); - B.line(`${inBounds} = icmp ne ptr ${cur}, null`); - } else { - const i = B.tmp(), - lenPtr = B.tmp(), - rawLen = B.tmp(), - len = B.tmp(); - B.line(`${i} = load double, ptr ${idxSlot}`); - // The inline form of scr_arr_len, like the `length` intrinsic. - B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr!.name}, i32 0, i32 1`); - this.markMemoryPointer(lenPtr, "array:header"); - B.line( - `${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`, - ); - B.line(`${len} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${rawLen} to double`); - B.line(`${inBounds} = fcmp olt double ${i}, ${len}`); - cur = i; - } - B.condBr(inBounds, lb, le); - B.startBlock(lb); - this.jumpTargets.push({ - kind: "loop", - brkLabel: le, - contLabel: lu, - labels: s.labels, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - finallyDepth: this.finallyStack.length, - }); - // The loop variable is a fresh const per iteration: its scope opens - // here, holds the (for ref elements: owned +1) current element, and - // releases it at the end of each iteration. - this.scopes.push([]); - const localInfo = this.currentLocals.get(s.localId); - const slot = `%${mangleLocal(s.localId)}`; - const borrowedElement = !snapshot && this.loopArrayBorrows.has(s); - if (!snapshot) { - const acc = elemAccess(elem); - const getter = borrowedElement ? "scr_arr_borrow_ref" : `scr_arr_get_${acc}`; - cur = emitDenseArrayGet( - this, - arr!.name, - { name: cur, type: { kind: "f64" } }, - undefined, - acc, - elem, - getter, - !borrowedElement, - ); - } - if (spanLength) { - const length = B.tmp(); - B.line(`${length} = load ${this.sizeType}, ptr ${spanLength}`); - this.splitSpans.set(s.localId, { bytes: cur, length, scratch: scratch! }); - cur = "null"; - } - if (localInfo?.boxed) { - // Captured loop variable: a fresh box per iteration, matching the - // fresh const binding. The box takes ownership of a ref element's - // +1 and is released with the iteration's scope. - const box = B.tmp(); - B.line(`${box} = ${boxNewCall(this.shapeHost, elem)} ; per-iteration ${localInfo.name}`); - this.boxSet(box, elem, cur); - B.line(`store ptr ${box}, ptr ${slot}`); - this.scopes[this.scopes.length - 1]!.push({ slot, type: elem, boxed: true }); - } else { - B.line(`store ${this.llType(elem)} ${cur}, ptr ${slot}`); - if (isRefCounted(elem) && !borrowedElement) - this.scopes[this.scopes.length - 1]!.push({ slot, type: elem }); - } - this.emitStmts(s.body); - if (spanLength) this.splitSpans.delete(s.localId); - const endedWithJump = endsWithJump(s.body); - const scope = this.scopes.pop()!; - if (!endedWithJump) this.releaseScope(scope); - this.jumpTargets.pop(); - B.br(lu); - B.startBlock(lu); - this.emitWorkerLoopCheck(workerBudget); - if (idxSlot) { - const i2 = B.tmp(), - i3 = B.tmp(); - B.line(`${i2} = load double, ptr ${idxSlot}`); - B.line(`${i3} = fadd double ${i2}, ${f64Lit(1)}`); - B.line(`store double ${i3}, ptr ${idxSlot}`); - } - B.br(lc); - B.startBlock(le); - break; - } - case "switch": - this.emitSwitch(s); - break; - case "break": { - // Unlabeled: the innermost loop OR switch (labeled blocks are - // skipped); labeled: the entry carrying the label. - let target: (typeof this.jumpTargets)[number] | undefined; - for (let i = this.jumpTargets.length - 1; i >= 0; i--) { - const t = this.jumpTargets[i]!; - if (s.label !== undefined ? t.labels?.includes(s.label) : t.kind !== "block") { - target = t; - break; - } - } - if (!target) throw new InternalCompilerError("llvm emitter bug: break target not found"); - this.emitFinallysForJump(target.finallyDepth, target.frameDepth, target.scopeDepth); - if (!B.isTerminated()) B.terminate(`br label %${target.brkLabel}`); - break; - } - case "continue": { - // Unlabeled: the innermost loop; labeled: the loop carrying the - // label (tsc + the validator guarantee it IS a loop). - let target: (typeof this.jumpTargets)[number] | undefined; - for (let i = this.jumpTargets.length - 1; i >= 0; i--) { - const t = this.jumpTargets[i]!; - if (t.kind === "loop" && (s.label === undefined || t.labels?.includes(s.label))) { - target = t; - break; - } - } - if (!target || target.contLabel === null) - throw new InternalCompilerError("llvm emitter bug: continue target not found"); - this.emitFinallysForJump(target.finallyDepth, target.frameDepth, target.scopeDepth); - if (!B.isTerminated()) B.terminate(`br label %${target.contLabel}`); - break; - } - case "return": { - // The value computes FIRST (an SSA temp — finally mutations of - // returned locals cannot change it, Node-exact), then every - // crossed finally runs innermost-first with the frames/scopes/ - // tryStack it sees truncated to its region (its releases already - // ran; a throw inside a copy propagates OUT of the completing - // try, past its own catch), then the function-level releases and - // the actual ret. The parked value owns a synthetic slot-backed - // scope entry during each copy so a throwing finally releases it. - const adopted = - this.finallyStack.length > 0 || this.tryStack.length > 0 - ? adoptingAwaitOf(s.value) - : null; - if (adopted !== null) { - this.emitAdoptingReturn(s, adopted); - break; - } - let v: LlValue | null = null; - const moved = - this.finallyStack.length === 0 && !this.currentUnionResult - ? this.movableReturn(s.value) - : null; - if (s.value !== null && this.currentUnionResult) { - // The tag and payload bits leave in registers; a heap box the - // value needed is released with its frame below. - const box = this.emitUnionProjection(s.value); - v = { name: this.scalarUnionPair(box.name, s.value.type), type: s.value.type }; - } else if (moved !== null) { - // Returning an owned local hands its reference to the caller: no - // retain now and no release of the local below. - const t = B.tmp(); - B.line(`${t} = load ptr, ptr ${moved.entry.slot}`); - v = { name: t, type: s.value!.type }; - moved.scope.splice(moved.scope.indexOf(moved.entry), 1); - this.releaseForJump(0, 0); - moved.scope.splice(moved.index, 0, moved.entry); - } else if (s.value !== null && this.currentBorrowedReturn) { - // A borrowed-return body hands back its walk without a reference - // (it has no try statements, so no finally can intervene). An - // optional element read lends the element its array keeps alive. - const read = this.optionalArrayReads.get(s.value); - if (read) { - const slot = B.slot(); - B.entryAllocas.push(`${slot} = alloca ptr`); - emitLocalArrayRead(this, { ...read, borrow: true }, slot); - const t = B.tmp(); - B.line(`${t} = load ptr, ptr ${slot}`); - v = { name: t, type: s.value.type }; - } else v = this.emitReadReceiver(s.value); - } else if (s.value !== null) { - v = this.emitExpr(s.value); - if (this.currentCoro !== null) this.toHeap(v); - else this.toFrame(v); - } - // An async generator's `return v` awaits v before any crossed - // finally runs. Past finally regions that turn is taken here and - // the request settles at completion; otherwise the completion's - // own turn is the await (nothing runs in between). - const genSettles = - this.currentAsyncGenerator && (s.awaited === true || this.finallyStack.length > 0); - // A completion turn that awaits x replaces a return() request's - // already-taken await. - if (this.currentAsyncGenerator && !genSettles) { - this.declare(`declare void @scr_async_gen_return_awaits()`); - B.line(`call void @scr_async_gen_return_awaits()`); - } - if (this.currentAsyncGenerator && s.awaited !== true && this.finallyStack.length > 0) { - if (this.wasi) this.emitWasiSuspend(null); - else { - this.declare(`declare void @scr_await_hop()`); - B.line(`call void @scr_await_hop()`); - } - } - if (this.finallyStack.length > 0) { - let pretSlot: string | null = null; - if (v !== null && isRefCounted(v.type) && !this.currentUnionResult) { - pretSlot = B.slot(); - B.entryAllocas.push(`${pretSlot} = alloca ptr ; pending return (through finally)`); - B.line(`store ptr ${v.name}, ptr ${pretSlot}`); - } - const savedFrames = this.frames; - const savedScopes = this.scopes; - const savedFinally = this.finallyStack; - const savedTry = this.tryStack; - for (let i = savedFinally.length - 1; i >= 0 && !B.isTerminated(); i--) { - const fin = savedFinally[i]!; - this.releaseForJump(fin.frameDepth, fin.scopeDepth); - this.frames = this.frames.slice(0, fin.frameDepth); - this.scopes = this.scopes.slice(0, fin.scopeDepth); - this.finallyStack = savedFinally.slice(0, i); - this.tryStack = savedTry.slice(0, fin.tryDepth); - if (pretSlot !== null) this.scopes.push([{ slot: pretSlot, type: v!.type }]); - this.emitBlock(fin.body); - if (pretSlot !== null) this.scopes.pop(); - } - if (!B.isTerminated()) this.releaseForJump(0, 0); - this.frames = savedFrames; - this.scopes = savedScopes; - this.finallyStack = savedFinally; - this.tryStack = savedTry; - } else if (moved === null) { - this.releaseForJump(0, 0); - } - if (!B.isTerminated()) { - if (genSettles) this.emitAsyncGenSettled(); - if (this.currentCoro !== null) { - this.emitWasiFulfill(v); - B.terminate(`br label %${this.currentCoro.finalLabel}`); - } else if (v === null) B.terminate("ret void"); - else if (this.currentInt32Result) - B.terminate(`ret i32 ${this.int32Argument(v, s.value!)}`); - else if (this.currentUnionResult) B.terminate(`ret ${SCALAR_UNION_RESULT} ${v.name}`); - else B.terminate(`ret ${this.llType(s.value!.type)} ${v.name}`); - } - break; - } - case "throw": { - // Evaluate, move ownership into the runtime's exception cell, then - // unwind unconditionally (the innermost try handler, or out of the - // function) — the same release path as return/break/continue. - const v = this.emitExpr(s.value); - if (isRefCounted(s.value.type)) this.toHeap(v); // the cell takes ownership - this.emitThrowValue({ name: v.name, type: s.value.type }); - this.emitUnwind(); - break; - } - case "rethrow": { - // Re-raise the saved snapshot (payload retained — the binding - // local releases with its scope) and unwind like `throw`. - const c = this.emitExpr({ kind: "varRef", localId: s.localId, type: CAUGHT, loc: s.loc }); - this.declare(`declare void @scr_rethrow(ptr)`); - B.line(`call void @scr_rethrow(ptr ${c.name})`); - this.emitUnwind(); - break; - } - case "runtimeFence": { - // The deferred JS compile fence: throw a catchable Error naming - // the construct (message) with the SC code stamped on `code`, - // then unwind exactly like `throw`. SCR_ERR_ERROR = 0. - const bytes = Buffer.byteLength(s.message, "utf8"); - this.declare(`declare void @scr_throw_error_msg_code(i32, ptr, ${this.sizeType}, ptr)`); - B.line( - `call void @scr_throw_error_msg_code(i32 0, ptr ${this.cstr(s.message)}, ${this.sizeType} ${bytes}, ptr ${this.cstr(s.code)})`, - ); - this.emitUnwind(); - break; - } - case "tryCatch": - this.emitTryCatch(s); - break; - default: { - // Statement coverage is now total (bytesSet closed the set) — - // keep throwing LlvmUnsupportedError for any future IR statement kind. - const rest: never = s; - const k = (rest as IrStmt).kind; - throw new LlvmUnsupportedError(`stmt:${k}`, (rest as IrStmt).loc); - } - } - const frame = this.frames.pop()!; - // return/throw already released their frames on the jump path; the - // fall-through releases after them would be dead double-release code. - if ( - s.kind !== "return" && - s.kind !== "throw" && - s.kind !== "rethrow" && - s.kind !== "runtimeFence" - ) { - this.releaseTemps(frame); - } + return emitStmtBody(this, s); } - - /** try/catch/finally via pending-flag unwinding — stmts.ts's - * emitTryCatch, block-flavored. Entering a try emits NO code: the try - * context is compile-time state (tryStack) redirecting unwinds inside - * the region to a label here. Shape: - * - * { try body } unwinds inside release frames/scopes down to - * this statement's depths, then br the handler - * br after (normal completion skips the handler) - * try.c: (emitted only when some unwind targets it) - * binding = scr_exc_take() (or scr_exc_clear() when bindingless) - * { catch body } - * after: { finally body } normal path - * br try.e - * try.fx: exception path: the pending exception is - * stash = scr_exc_take() STASHED across the finally body so the - * { finally body } body's own pending checks answer for - * scr_rethrow(stash) themselves; a throw inside REPLACES the - * stash (it unwinds through the synthetic - * try.e: scope entry) — JS's semantics exactly - * - * Abrupt completions inside tryBody/catchBody ride the finallyStack: - * returns inline copies at the return site, while break/continue use the - * shared region walk before branching to their resolved target. */ - private emitTryCatch(s: IrStmt & { kind: "tryCatch" }): void { - const B = this.B; - const hasCatch = s.catchBody !== null; - const hasFinally = s.finallyBody !== null; - const catchLabel = B.newLabel("try.c"); - const finExcLabel = B.newLabel("try.fx"); - const endLabel = B.newLabel("try.e"); - const afterTryLabel = hasFinally ? B.newLabel("try.f") : endLabel; - - const handler = { - label: hasCatch ? catchLabel : finExcLabel, - used: false, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - }; - if (hasFinally) { - this.finallyStack.push({ - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - tryDepth: this.tryStack.length, - body: s.finallyBody!, - }); - } - this.tryStack.push(handler); - this.emitBlock(s.tryBody); - this.tryStack.pop(); - B.br(afterTryLabel); // no-op when the try body already terminated - - // Exceptions raised in the CATCH body unwind to the exception-path - // finally (pending stays set through it) when one exists. - const excHandler = { - label: finExcLabel, - used: !hasCatch && handler.used, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - }; - - if (hasCatch && handler.used) { - B.startBlock(catchLabel); - if (hasFinally) this.tryStack.push(excHandler); - if (this.currentGenerator !== null) { - // Generator bodies: a pending GENRET sentinel (.return(v) - // injected at a yield) is a RETURN completion, not a throw — - // catch must not take it. Re-unwind past this handler (finally - // still runs — the unwind targets the exception-path finally or - // the enclosing context; the depths here equal the handler's). - this.declare(`declare zeroext i1 @scr_exc_genret_pending()`); - const gr = B.tmp(); - B.line(`${gr} = call zeroext i1 @scr_exc_genret_pending()`); - const lg = B.newLabel("try.gr"); - const lk = B.newLabel("try.gk"); - B.condBr(gr, lg, lk); - B.startBlock(lg); - this.emitUnwind(); - B.startBlock(lk); - } - if (s.catchLocalId !== null) { - // catch (e): the exception MOVES into the binding's snapshot box, - // owned by the catch body's scope (released on every exit — - // normal fall-through, jumps out, and unwinds from the body). - const slot = `%${mangleLocal(s.catchLocalId)}`; - this.declare(`declare ptr @scr_exc_take()`); - this.emitBlock(s.catchBody!, (scope) => { - const c = B.tmp(); - B.line(`${c} = call ptr @scr_exc_take() ; catch binding`); - if (this.currentLocals.get(s.catchLocalId!)?.boxed) { - const box = B.tmp(); - B.line(`${box} = ${boxNewCall(this.shapeHost, CAUGHT)} ; captured exception`); - this.boxSet(box, CAUGHT, c); - B.line(`store ptr ${box}, ptr ${slot}`); - scope.push({ slot, type: CAUGHT, boxed: true }); - } else { - B.line(`store ptr ${c}, ptr ${slot}`); - scope.push({ slot, type: CAUGHT }); - } - }); - } else { - this.declare(`declare void @scr_exc_clear()`); - B.line(`call void @scr_exc_clear() ; catch takes the exception`); - this.emitBlock(s.catchBody!); - } - if (hasFinally) this.tryStack.pop(); - B.br(afterTryLabel); // the catch's normal completion - } - if (hasFinally) this.finallyStack.pop(); - - if (hasFinally) { - B.startBlock(afterTryLabel); - this.emitBlock(s.finallyBody!); // normal path - B.br(endLabel); - if (excHandler.used) { - // The pending exception is STASHED across the finally body (a - // ScrCaught snapshot, re-raised after) so the body runs with a - // CLEAN cell — see stmts.ts's exception-path copy. The - // stash rides an alloca slot so a throw inside the body unwinds - // through the synthetic scope entry (replace semantics). - B.startBlock(finExcLabel); - this.declare(`declare ptr @scr_exc_take()`); - this.declare(`declare void @scr_rethrow(ptr)`); - this.declare(`declare void @scr_caught_release(ptr)`); - const stash = B.tmp(); - const stashSlot = B.slot(); - B.entryAllocas.push(`${stashSlot} = alloca ptr ; finally exception stash`); - 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 || 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(); - this.scopes.pop(); // normal completion keeps the stash for the re-raise - B.line(`call void @scr_rethrow(ptr ${stash})`); - B.line(`call void @scr_caught_release(ptr ${stash})`); - this.emitUnwind(); - if (suppressHandler?.used) { - B.startBlock(suppressHandler.label); - 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(); - } - } - B.startBlock(endLabel); - } else { - B.startBlock(endLabel); - } + emitTryCatch(s: IrStmt & { kind: "tryCatch" }): void { + return emitTryCatch(this, s); } - - /** JS-exact switch: lazily evaluated, arbitrary-expression case tests in - * source order, bodies falling through in source order until a break — - * Literal-only tests can use native dispatch; other tests keep a chain - * of conditional branches. All case bodies - * share ONE scope; because dispatch can jump PAST a varDecl into a later - * case, refcounted/boxed case-body locals are NULL-reset up front and - * the scope-exit releases rely on NULL tolerance. */ - private emitSwitch(s: IrStmt & { kind: "switch" }): void { - const B = this.B; - const discKind = s.disc.type.kind; - if (discKind !== "f64" && discKind !== "string" && discKind !== "bool") { - throw new LlvmUnsupportedError(`switch:${discKind}`, s.loc); - } - // The disc temp lives in the whole statement's frame: for a string - // discriminant it stays alive across every test and body, released - // when the switch statement ends (break lands past this statement's - // frame release — releaseForJump keeps the target's own frame). - const disc = this.emitExpr(s.disc); - for (const c of s.cases) { - for (const stmt of c.body) { - if (stmt.kind !== "varDecl") continue; - const local = this.currentLocals.get(stmt.localId)!; - if (local.boxed || isRefCounted(local.type)) { - B.line(`store ptr null, ptr %${mangleLocal(local.id)} ; case-scoped ${local.name}`); - } - } - } - const end = B.newLabel("sw.e"); - const caseLabels = s.cases.map(() => B.newLabel("sw.c")); - const defaultIdx = s.cases.findIndex((c) => c.test === null); - const fallback = defaultIdx >= 0 ? caseLabels[defaultIdx]! : end; - if (!emitLiteralSwitch(this, disc, s.disc, s.cases, caseLabels, fallback)) { - s.cases.forEach((c, i) => { - if (c.test === null) return; - // Lazy source-order test evaluation (a test after the match never - // runs). Each test's temps release right after its comparison. - this.frames.push([]); - const t = this.emitExpr(c.test); - const hit = B.tmp(); - if (c.test.kind === "strLit") { - B.line(`${hit} = call zeroext i1 ${this.literalEquals(c.test.value)}(ptr ${disc.name})`); - } else if (c.test.type.kind === "string") { - this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`); - B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${disc.name}, ptr ${t.name})`); - } else if (c.test.type.kind === "bool") { - B.line(`${hit} = icmp eq i1 ${disc.name}, ${t.name}`); - } else { - B.line(`${hit} = fcmp oeq double ${disc.name}, ${t.name}`); - } - this.releaseTemps(this.frames.pop()!); - const next = B.newLabel("sw.t"); - B.condBr(hit, caseLabels[i]!, next); - B.startBlock(next); - }); - B.br(fallback); - } - - this.jumpTargets.push({ - kind: "switch", - brkLabel: end, - contLabel: null, - labels: s.labels, - frameDepth: this.frames.length, - scopeDepth: this.scopes.length, - finallyDepth: this.finallyStack.length, - }); - const scope: LlScopeEntry[] = []; - this.scopes.push(scope); - s.cases.forEach((c, i) => { - B.br(caseLabels[i]!); // the previous body's natural fall-through - B.startBlock(caseLabels[i]!); - this.emitStmts(c.body); - }); - this.jumpTargets.pop(); - this.scopes.pop(); - // Natural fall-off of the last body releases the shared scope; a jump - // already released it before jumping. - const lastBody = s.cases[s.cases.length - 1]?.body; - if (!lastBody || !endsWithJump(lastBody)) this.releaseScope(scope); - B.br(end); - B.startBlock(end); + emitSwitch(s: IrStmt & { kind: "switch" }): void { + return emitSwitch(this, s); } /** Evaluates a condition (IR conds are bool-typed) and releases its * temps BEFORE the branch — safe because the result is a scalar i1, and * required in loop-condition blocks (their temps must not survive into * later blocks across the back edge). */ - private emitCondition(cond: IrExpr): string { + emitCondition(cond: IrExpr): string { const v = this.emitExpr(cond); const frame = this.currentFrame(); this.releaseTemps(frame); @@ -9901,7 +4572,7 @@ export class LlEmitter { return this.canBorrowCallArgument(value); } - private emitDiscarded(e: IrExpr): void { + emitDiscarded(e: IrExpr): void { // A statement-position yield never reads the resumed value: it stays // in the IN slot until the next resume replaces it. if (e.kind === "yieldExpr" && e.captureCompletion === undefined && e.type.kind !== "void") { @@ -9956,81 +4627,19 @@ export class LlEmitter { ): LlValue { return emitStringExpr(this, e); } - - /** - * Lower canonical `target = target + suffix` after evaluating the old - * left side and suffix in JavaScript order. The snapshot stays owned by - * the statement frame while the destination relinquishes its CURRENT - * value, making the snapshot unique unless a real observable alias exists. - */ emitStringSelfConcatAssign( localId: string, left: IrExpr, suffix: IrExpr, retainForYield: boolean, ): LlValue { - const moved = this.emitMovedSelfConcat(localId, left, suffix, retainForYield); - if (moved) return moved; - const snapshot = this.emitExpr(left); - // A suffix of several parts, or a number, appends each operand to the - // snapshot directly (in place once the binding lets go of it) instead - // of first building the suffix as a separate string. - const parts = stringParts(suffix); - const mixed = parts.length > 1 || numberPart(parts[0]!) !== null; - const charCode = mixed ? null : this.emitSingleCharCode(suffix); - const inputs = mixed ? emitConcatInputs(this, parts) : []; - // The concat only reads the suffix; relinquishing the binding below - // runs no code that could rebind a borrowed local. - const right = mixed || charCode !== null ? null : emitStringInputs(this, [suffix])[0]!; - const b = this.binding(localId); - const B = this.B; - if (b.kind === "boxed") { - // set_ref(NULL) unlinks then releases the binding's post-suffix value. - this.boxSet(this.loadBox(b.slot), b.type, "null"); - } else { - const old = B.tmp(); - B.line(`${old} = load ptr, ptr ${b.slot}`); - B.line(`store ptr null, ptr ${b.slot}`); - if (b.kind === "global") this.releaseHeap(old, b.type); - else this.releaseFrame(old, b.type); - } - let raw: string; - if (charCode !== null) { - // The snapshot moves into the consuming character append. - this.toFrame(snapshot); - this.declare(`declare ptr @scr_str_concat_move_char_code(ptr, double)`); - raw = B.tmp(); - B.line( - `${raw} = call ptr @scr_str_concat_move_char_code(ptr ${snapshot.name}, double ${charCode.name})`, - ); - } else if (right === null) { - raw = emitMixedConcat(this, snapshot.name, inputs); - } else { - this.declare(`declare ptr @scr_str_concat(ptr, ptr)`); - raw = B.tmp(); - B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`); - } - const result = this.own({ name: raw, type: left.type }); - if (retainForYield) { - const stored = - b.kind === "local" - ? this.retainFrame(result.name, result.type) - : this.retainHeap(result.name, result.type); - if (b.kind === "boxed") this.boxSet(this.loadBox(b.slot), b.type, stored); - else B.line(`store ptr ${stored}, ptr ${b.slot}`); - } else { - if (b.kind === "local") this.toFrame(result); - else this.toHeap(result); - if (b.kind === "boxed") this.boxSet(this.loadBox(b.slot), b.type, result.name); - else B.line(`store ptr ${result.name}, ptr ${b.slot}`); - } - return result; + return emitStringSelfConcatAssign(this, localId, left, suffix, retainForYield); } /** The code of a one-argument `String.fromCharCode(c)` suffix, emitted, so * `s += String.fromCharCode(c)` appends the character without building * the one-character string; null for any other suffix. */ - private emitSingleCharCode(suffix: IrExpr): LlValue | null { + emitSingleCharCode(suffix: IrExpr): LlValue | null { if (suffix.kind !== "libCall" || suffix.fn !== "string.fromCharCode") return null; const packed = suffix.args[0]; if (packed?.kind !== "arrayLit" || packed.elems.length !== 1 || packed.spreads?.length) @@ -10047,7 +4656,7 @@ export class LlEmitter { * copying the whole string on every append. Returns false (emitting * nothing) for every other store. */ - private emitFieldSelfConcat(s: Extract): boolean { + emitFieldSelfConcat(s: Extract): boolean { const suffix = matchFieldSelfConcat(s); if (!suffix || s.value.kind !== "strConcat") return false; let fieldType: IrType | undefined; @@ -10114,7 +4723,7 @@ export class LlEmitter { * the snapshot, the array lets go of it (emitDenseSlotUnlink) so the * concat can append in place, and the result is stored as usual. */ - private emitArraySelfConcat(s: Extract): boolean { + emitArraySelfConcat(s: Extract): boolean { const value = s.value; if ( value.kind !== "strConcat" || @@ -10165,7 +4774,7 @@ export class LlEmitter { * expressions): reading the binding after the suffix gives the value the * left side had, so it moves into a consuming concat, and the result takes * its place. Null when the general snapshot path is needed. */ - private emitMovedSelfConcat( + emitMovedSelfConcat( localId: string, left: IrExpr, suffix: IrExpr, @@ -10434,7 +5043,7 @@ export class LlEmitter { /** Lifetime regions in modules with lifetimeRegions (llvm/regions.ts). * Created with the class graph; null elsewhere. */ - private regions: LifetimeRegions | null = null; + regions: LifetimeRegions | null = null; /** LifetimeRegion's runtime calls: create answers the region id, enter * and leave bracket run(), release condemns. */ @@ -10465,10 +5074,10 @@ export class LlEmitter { return { name: "", type: e.type }; } - private publishWalkersCache: PublishWalkers | null = null; + publishWalkersCache: PublishWalkers | null = null; /** Per-type publication walkers, created on the first publish. */ - private get publishWalkers(): PublishWalkers { + get publishWalkers(): PublishWalkers { if (this.publishWalkersCache !== null) return this.publishWalkersCache; const byPre = [...this.classMeta.values()].sort((a, b) => a.pre - b.pre); this.publishWalkersCache = new PublishWalkers({ @@ -10553,7 +5162,7 @@ export class LlEmitter { private publishedNamesSym: string | null = null; /** The class-name table the hierarchy field guards index by preorder. */ - private publishedClassNamesDefs(): string[] { + publishedClassNamesDefs(): string[] { if (this.publishedNamesSym === null) return []; const metas = [...this.classMeta.values()]; const size = metas.reduce((max, m) => Math.max(max, m.pre + 1), 0); @@ -10567,7 +5176,7 @@ export class LlEmitter { /** The frozen-object guard of a class field store, unless the store * provably targets a still-private `this` (PublishedTypes.privateStores). */ - private emitStoreGuard(obj: string, s: Extract): void { + emitStoreGuard(obj: string, s: Extract): void { if (this.publishedTypes?.privateStores.has(s) === true) return; this.emitPublishedFieldGuard(obj, s.className, s.field); } @@ -10659,153 +5268,12 @@ export class LlEmitter { ): LlValue { return emitAsyncExpr(this, e); } - - /** Read a receiver whose value is consumed immediately by a field or tag - * load. No user code may run between this read and its consumer. Locals - * and their projections already have an owner; other expressions keep - * their ordinary statement-frame ownership. The caller must retain any - * reference result before evaluating another expression. */ emitReadReceiver(e: IrExpr): LlValue { - if (!isRefCounted(e.type)) return this.emitExpr(e); - if (e.kind === "strLit") return { name: this.internLiteral(e.value), type: e.type }; - const guarded = guardedValue(e); - if (guarded !== null) { - // Throwing guards run in place, exactly as the sequence emits them. - for (const s of (e as IrExpr & { kind: "seqExpr" }).stmts) this.emitStmt(s); - return this.emitReadReceiver(guarded); - } - if (e.kind === "varRef") { - this.materializeSplitLocal(e.localId); - const binding = this.binding(e.localId); - if (binding.kind === "boxed") { - // A projection consumes this pointer before any user code runs. - // Keep TDZ checks and caught-value conversion on the owned path. - if (!this.canBorrowReceiver(e)) return this.emitExpr(e); - const box = this.loadBox(binding.slot); - const payload = this.B.tmp(); - const value = this.B.tmp(); - this.B.line(`${payload} = getelementptr inbounds %ScrBox, ptr ${box}, i32 0, i32 5`); - this.B.line(`${value} = load ptr, ptr ${payload}`); - return { name: value, type: e.type }; - } - if (binding.kind === "global") this.checkGlobalTdz(e.localId); - const value = this.B.tmp(); - this.B.line(`${value} = load ptr, ptr ${binding.slot}`); - return { name: value, type: e.type }; - } - if (e.kind === "dynCheck" && e.exact) return this.emitExactCapsuleObject(e.value, e.type); - if (e.kind === "chainRecv") { - // The chain's bind slot holds its receiver unchanged until the body - // finishes, owned by the slot itself or by the chain's receiver. - const bound = this.chainSlots.get(e.id); - if (!bound) - throw new InternalCompilerError(`llvm emitter bug: chainRecv "${e.id}" outside its chain`); - const value = this.B.tmp(); - this.B.line(`${value} = load ${this.llType(bound.type)}, ptr ${bound.name}`); - return { name: value, type: e.type }; - } - if (e.kind === "arrayGet" && this.canBorrowReceiver(e)) { - // The array keeps the element alive until the consumer; a missing - // element still traps exactly like the owned read. - const slot = this.B.slot(); - this.B.entryAllocas.push(`${slot} = alloca ptr`); - emitBorrowedArrayRead(this, e, slot); - const value = this.B.tmp(); - this.B.line(`${value} = load ptr, ptr ${slot}`); - return { name: value, type: e.type }; - } - const wrap = this.borrowableNullableWrap(e); - if (wrap === "unit" && e.kind === "unionWrap") - return { name: this.unitInstanceRef(e.unionId, e.tag), type: e.type }; - if (wrap === "ref" && e.kind === "unionWrap") - return { name: this.emitReadReceiver(e.value).name, type: e.type }; - const read = matchMapRead(e, this.boxedUnionsById); - if (read) { - const result = emitStackMapRead(this, read); - if (result.owner) this.ownSlot(result.owner.slot, result.owner.type); - return result.value; - } - // A borrowed-return helper (such as an optional element read) lends its - // result like any borrowed-return call. - if (e.kind === "call" && this.borrowedReturns.has(e.callee) && this.canBorrowReceiver(e)) - return emitBorrowedResultCall(this, e); - const arrayRead = this.optionalArrayReads.get(e); - if (arrayRead) return emitProjectedArrayRead(this, arrayRead); - const narrow = this.checkedNarrows.get(e); - const narrowed = narrow ? emitCheckedNarrowReceiver(this, narrow) : null; - if (narrowed) return narrowed; - if (canStackUnion(e, this.boxedUnionsById)) return emitStackUnion(this, e).value; - if (e.kind === "unionNarrow") { - const union = this.emitUnionProjection(e.value); - emitUnionNarrowGuard(this, union.name, e.unionId, e.tag); - return { name: this.unionPeek(union.name, e.unionId), type: e.type }; - } - if ((e.kind === "downcast" || e.kind === "upcast") && e.value.type.kind === "object") { - // Prefix layouts: a class cast reads the operand's own pointer; a - // downcast verifies its target class (narrow-guards.ts). - const receiver = this.emitReadReceiver(e.value); - if (e.kind === "downcast" && e.type.kind === "object") - emitDowncastGuard(this, receiver.name, e.value.type.className, e.type.className); - return { name: receiver.name, type: e.type }; - } - // A nullable-pointer field has no box to borrow. Projections use - // emitUnionProjection; other borrowers get an owned heap box. - if (this.nullableFieldGet(e)) return this.emitExpr(e); - if (e.kind === "recordGet" || e.kind === "fieldGet") { - const receiver = this.emitReadReceiver(e.obj); - if (e.kind === "recordGet") { - // An inline union field answers a private stack box. - const { ptr, type, inline } = this.recordFieldPtr(receiver.name, e.shapeId, e.field); - return { name: this.loadRecordField(ptr, type, inline), type: e.type }; - } - const { ptr, type, inline } = this.classFieldPtr(receiver.name, e.className, e.field); - if (this.int32Slots.isField(e.className, e.field)) return this.loadInt32Field(ptr, e.type); - return { name: this.loadField(ptr, type, inline), type: e.type }; - } - if (e.kind === "ternary" && this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_)) { - // Checked projections lower to a ternary with an always-throwing - // arm. Keep that check at the use, but borrow the successful arm. - const B = this.B; - const cond = this.emitExpr(e.cond); - const slot = B.slot(); - B.entryAllocas.push(`${slot} = alloca ptr`); - const yes = B.newLabel("read.then"), - no = B.newLabel("read.else"), - join = B.newLabel("read.join"); - B.condBr(cond.name, yes, no); - for (const [label, value] of [ - [yes, e.then], - [no, e.else_], - ] as const) { - B.startBlock(label); - this.frames.push([]); - const result = this.emitReadReceiver(value); - B.line(`store ptr ${result.name}, ptr ${slot}`); - this.releaseTemps(this.frames.pop()!); - B.br(join); - } - B.startBlock(join); - const value = B.tmp(); - B.line(`${value} = load ptr, ptr ${slot}`); - return { name: value, type: e.type }; - } - // Scalar optional values and register results answer a private stack - // box. A ternary of borrowable arms above keeps lending its arm. - if ( - (e.kind === "optChain" || - e.kind === "nullish" || - e.kind === "ternary" || - e.kind === "arrIntrinsic") && - isScalarOptionalSource(e, this.unionSources) - ) - return emitScalarOptional(this, e); - if (e.kind === "call" && this.scalarResultCall(e)) return emitCallExpr(this, e, false, true); - if (e.kind === "call" && this.canBorrowReceiver(e)) return emitBorrowedResultCall(this, e); - return this.emitExpr(e); + return emitReadReceiver(this, e); } /** The frame slot a reassigned stack-object local keeps for its value. */ - private stackObjectSlot(localId: string, type: IrType): string { + stackObjectSlot(localId: string, type: IrType): string { let slot = this.stackObjectSlots.get(localId); if (slot === undefined) { slot = this.B.slot(); @@ -10814,24 +5282,11 @@ export class LlEmitter { } return slot; } - - /** A private stack union source with an independently owned payload. */ canStackReceiver(e: IrExpr): boolean { - return this.isStackUnionSource(e); + return canStackReceiver(this, e); } - - /** True for union sources that emitReadReceiver can produce as a private - * stack box with an independently owned payload (no heap union). */ isStackUnionSource(e: IrExpr): boolean { - if (e.type.kind !== "union") return false; - return ( - canStackUnion(e, this.boxedUnionsById) || - matchMapRead(e, this.boxedUnionsById) !== null || - this.optionalArrayReads.get(e) !== null || - isScalarOptionalSource(e, this.unionSources) || - this.scalarResultCall(e) || - this.inlineFieldGet(e) - ); + return isStackUnionSource(this, e); } /** A read of a record or class field that stores its scalar union @@ -11007,77 +5462,13 @@ export class LlEmitter { this.B.line(`${object} = load ptr, ptr ${slot}`); return { name: object, type }; } - canBorrowReceiver(e: IrExpr): boolean { - switch (e.kind) { - case "chainRecv": - return this.chainSlots.has(e.id); - case "varRef": { - const binding = this.binding(e.localId); - return ( - binding.kind !== "boxed" || - (!binding.local!.tdz && - binding.type.kind === e.type.kind && - e.type.kind !== "caught" && - isRefCounted(e.type)) - ); - } - case "unionNarrow": - // Class casts reinterpret the same pointer (prefix layouts). - case "downcast": - case "upcast": - return this.canBorrowReceiver(e.value); - // An inline union field has no box to lend: projections read it - // through emitReadReceiver's private stack box. - case "fieldGet": - case "recordGet": - return !this.inlineFieldGet(e) && this.canBorrowReceiver(e.obj); - case "ternary": - return this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_); - case "seqExpr": { - const guarded = guardedValue(e); - return guarded !== null && this.canBorrowReceiver(guarded); - } - // The runtime always sets a pending exception; its unreachable - // typed dummy is null and owns no receiver. - case "libCall": - return e.fn === "error.nodeThrow"; - case "unionWrap": { - const wrap = this.borrowableNullableWrap(e); - return wrap === "unit" || (wrap === "ref" && this.canBorrowReceiver(e.value)); - } - // A borrowed-return result stays reachable from its borrowed - // arguments until the consumer runs. Each argument is lent without a - // snapshot, so neither the callee nor a later argument may replace - // what it was read from: a callee that preserves every edge cannot, - // and any other one must leave the argument's locations unwritten. - case "call": { - if (!this.borrowedReturns.has(e.callee)) return false; - if (!e.args.every((arg) => this.referenceEffects.preserves(arg))) return false; - const preserving = this.referenceEffects.functions.has(e.callee); - if (preserving) - return e.args.every((arg) => !isRefCounted(arg.type) || this.canBorrowReceiver(arg)); - if (this.referenceWrites === null) return false; - const lent = borrowableInputs(this, e.args, false, this.referenceWrites.summary(e.callee)); - return e.args.every((arg, i) => !isRefCounted(arg.type) || lent[i] === true); - } - // A required element read: the (borrowed) array owns the element - // while an edge-preserving index computes. - case "arrayGet": - return ( - e.arr.type.kind === "array" && - isRefCounted(e.arr.type.elem) && - this.canBorrowReceiver(e.arr) && - this.referenceEffects.preserves(e.index) - ); - default: - return false; - } + return canBorrowReceiver(this, e); } /** A reference held as one plain pointer that emitReadReceiver can read - * without owning: class instances, records, arrays, strings and - * nullable-pointer unions. Tagged union boxes, closures and dynamic + * without owning: class instances, records, arrays, strings, typed arrays + * and nullable-pointer unions. Tagged union boxes, closures and dynamic * values keep ordinary ownership. */ plainReference(type: IrType): boolean { switch (type.kind) { @@ -11085,6 +5476,7 @@ export class LlEmitter { case "record": case "array": case "string": + case "bytes": return true; case "union": return this.nullableUnions.has(type.unionId); @@ -11092,65 +5484,8 @@ export class LlEmitter { return false; } } - - /** Functions returning a walk of their borrowed parameters return it - * without a reference (+0) from their borrowing body; direct callers - * either consume it as a receiver/walk or retain it at once, and the owned - * adapter retains before releasing its parameters. Candidates preserve - * every heap edge (so the result stays reachable from the arguments), - * borrow every reference parameter and return plain pointers. The set - * grows to a fixpoint because a call to a borrowed-return function with - * walk arguments is itself a walk; walk facts are then recomputed with - * the final set, admitting only parameters whose convention borrows. */ private analyzeBorrowedReturns(): void { - const pointer = (type: IrType): boolean => this.plainReference(type); - const borrowedIds = (fn: IrFunction): Set => { - const indexes = this.callLifetimes.borrowed.get(fn.name); - return new Set(fn.params.filter((_, i) => indexes?.has(i)).map((p) => p.localId)); - }; - const hostFor = (fn: IrFunction): WalkBorrowHost => { - const allowed = borrowedIds(fn); - const host = { - pointerLocal: (local: IrLocal) => pointer(local.type), - borrowsWithoutOwning: (e: IrExpr) => this.borrowsWithoutOwning(e), - borrowedReturn: (callee: string) => this.borrowedReturns.has(callee), - parameterAllowed: (id: string) => allowed.has(id), - }; - return this.referenceEffects.functions.has(fn.name) ? host : this.unwrittenWalkHost(fn, host); - }; - // With ReferenceWrites, a body that writes none of its walks' locations - // qualifies like one that preserves every edge. - const preserving = [...this.fnByName.values()].filter( - (fn) => this.referenceEffects.functions.has(fn.name) || this.referenceWrites !== null, - ); - const candidates = preserving.filter((fn) => { - if (!pointer(fn.returnType) || fn.async || fn.generator || fn.captures) return false; - // A borrowing vtable slot dispatches to the borrowing body (or its - // virtual adapter) directly, and virtual callers own the result. - if (this.implSlotBorrowed(fn.name).size > 0) return false; - const indexes = this.callLifetimes.borrowed.get(fn.name); - return ( - indexes !== undefined && - indexes.size > 0 && - fn.params.every((p, i) => !isRefCounted(p.type) || indexes.has(i)) - ); - }); - for (let changed = true; changed;) { - changed = false; - for (const fn of candidates) { - if (this.borrowedReturns.has(fn.name)) continue; - if (!analyzeWalks(fn, hostFor(fn)).returnsWalk) continue; - this.borrowedReturns.add(fn.name); - // Nested borrowed returns extend the locations a caller depends on. - this.borrowedReturnKeyCache.clear(); - changed = true; - } - } - if (this.borrowedReturns.size === 0) return; - for (const fn of preserving) { - const walks = analyzeWalks(fn, hostFor(fn)).locals; - if (walks.size > 0) this.walkBorrowsByFunction.set(fn.name, walks); - } + return analyzeBorrowedReturns(this); } /** emitReadReceiver produces this node's pointer without acquiring a @@ -11266,12 +5601,19 @@ export class LlEmitter { return childDataThunkFor(this, param); } + childDataDynThunkFor(chunk: "bytes" | "str"): string { + return childDataDynThunkFor(this, chunk); + } + emitterFixedAdapter(cbT: IrType & { kind: "func" }): { fn: string; shim: string } { return emitterFixedAdapter(this, cbT); } - emitterOnceAdapter(boxerT: IrType & { kind: "func" }): { fn: string; shim: string } { - return emitterOnceAdapter(this, boxerT); + emitterOnceAdapter( + boxerT: IrType & { kind: "func" }, + fire?: string, + ): { fn: string; shim: string } { + return emitterOnceAdapter(this, boxerT, fire); } wrapEmitterListener(target: string, adapterFn: string): string { @@ -11462,7 +5804,7 @@ export class LlEmitter { this.B.line(`store i32 ${integer}, ptr ${slot}`); } - private emitBytesSet( + emitBytesSet( elem: IrBytesElem, receiver: string, index: LlValue, @@ -11601,7 +5943,7 @@ export class LlEmitter { /** The adopting await a `return` value resolves through, if any: the value * itself or the operand of its conversion into the declared result. */ -function adoptingAwaitOf(value: IrExpr | null): ExprOf<"awaitExpr"> | null { +export function adoptingAwaitOf(value: IrExpr | null): ExprOf<"awaitExpr"> | null { if (value === null) return null; let found: ExprOf<"awaitExpr"> | null = null; everyExpr(value, { diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index ac025df09..485b7a6b3 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -8,6 +8,7 @@ import { exactInteger, widenInteger } from "./integer-values.js"; import { byteNumberAccess } from "../../ir/byte-numbers.js"; import { emitByteNumber } from "./byte-numbers.js"; import { integerOrNaNRange } from "./wrapping-sums.js"; +import { bytesElementAlias, bytesHeaderAlias } from "./bytes-alias.js"; const BYTES_NUM_KIND: Record = { u8: { kind: 0, le: false }, @@ -93,10 +94,7 @@ export function emitBytesIndex( B.line(`${narrow} = trunc i64 ${wide} to ${host.sizeType}`); return narrow; } - const lenPtr = B.tmp(); - const len = B.tmp(); - B.line(`${lenPtr} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`); - B.line(`${len} = load ${host.sizeType}, ptr ${lenPtr}`); + const len = emitBytesHeaderLength(host, receiver); if (integer) { const wideIndex = widenInteger(host, integer); const wideLength = host.sizeType === "i64" ? len : B.tmp(); @@ -145,11 +143,20 @@ export function emitBytesIndex( return idx; } +/** The element count of a typed array, in the target's size type. */ +export function emitBytesHeaderLength(host: LlvmEmitterContext, receiver: string): string { + const p = host.B.tmp(); + const len = host.B.tmp(); + host.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`); + host.B.line(`${len} = load ${host.sizeType}, ptr ${p}${bytesHeaderAlias(host)}`); + return len; +} + export function emitBytesData(host: LlvmEmitterContext, receiver: string): string { const p = host.B.tmp(); const data = host.B.tmp(); host.B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 3`); - host.B.line(`${data} = load ptr, ptr ${p}`); + host.B.line(`${data} = load ptr, ptr ${p}${bytesHeaderAlias(host)}`); return data; } @@ -160,10 +167,7 @@ export function emitBytesLength( bytes: boolean, ): LlValue { const B = host.B; - const p = B.tmp(); - const len = B.tmp(); - B.line(`${p} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 1`); - B.line(`${len} = load ${host.sizeType}, ptr ${p}`); + const len = emitBytesHeaderLength(host, receiver); const size = BYTES_ELEMENT_SIZE[elem]; const count = bytes && size !== 1 ? B.tmp() : len; if (count !== len) B.line(`${count} = shl ${host.sizeType} ${len}, ${Math.log2(size)}`); @@ -187,7 +191,7 @@ function emitSharedBytesSplit(host: LlvmEmitterContext, receiver: string): strin isShared = B.tmp(), expected = B.tmp(); B.line(`${slot} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 9`); - B.line(`${storage} = load ptr, ptr ${slot}`); + B.line(`${storage} = load ptr, ptr ${slot}${bytesHeaderAlias(host)}`); B.line(`${isShared} = icmp ne ptr ${storage}, null`); B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${isShared}, i1 false)`); const shared = B.newLabel("bytes.shared"), @@ -257,9 +261,15 @@ function emitBytesLoad( inBounds: boolean, invalid: string | undefined, ): LlValue { + if (host.mod.workers) { + const idx = emitBytesIndex(host, receiver, index, expr, invalid, inBounds); + return emitSharedOrLocalLoad(host, elem, receiver, idx); + } + // The header is immutable, so the data pointer loads beside the length, + // ahead of the bounds branch: an unconditional load LICM can hoist. + const data = host.emitBytesData(receiver); const idx = emitBytesIndex(host, receiver, index, expr, invalid, inBounds); - if (host.mod.workers) return emitSharedOrLocalLoad(host, elem, receiver, idx); - return emitBytesLoadAt(host, elem, receiver, idx); + return emitBytesLoadAt(host, elem, data, idx); } /** Worker programs: the element at a checked index of a view that may be @@ -285,7 +295,7 @@ function emitSharedOrLocalLoad( const sharedDone = B.newLabel("bytes.shared.done"); B.br(sharedDone); B.startBlock(local); - const own = emitBytesLoadAt(host, elem, receiver, idx); + const own = emitBytesLoadAt(host, elem, host.emitBytesData(receiver), idx); const localDone = B.newLabel("bytes.local.done"); B.br(localDone); B.startBlock(localDone); @@ -306,15 +316,15 @@ function emitSharedOrLocalLoad( return { name: out, type: F64, uint32: u32 }; } -/** The element at a checked index of a thread-owned view. */ +/** The element at a checked index of a thread-owned view, given its data + * pointer. */ function emitBytesLoadAt( host: LlvmEmitterContext, elem: IrBytesElem, - receiver: string, + data: string, idx: string, ): LlValue { const B = host.B; - const data = host.emitBytesData(receiver); const p = B.tmp(); if (elem === "f16") { // Half-precision bits widen exactly; the runtime converts them. @@ -322,7 +332,7 @@ function emitBytesLoadAt( const raw = B.tmp(); const out = B.tmp(); B.line(`${p} = getelementptr inbounds i16, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${raw} = load i16, ptr ${p}, align 1`); + B.line(`${raw} = load i16, ptr ${p}, align 1${bytesElementAlias(host)}`); B.line(`${out} = call double @scr_f16_to_f64(i16 zeroext ${raw})`); return { name: out, type: F64 }; } @@ -333,7 +343,7 @@ function emitBytesLoadAt( const wide = B.tmp(); const out = B.tmp(); B.line(`${p} = getelementptr inbounds i${bits}, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${raw} = load i${bits}, ptr ${p}, align 1`); + B.line(`${raw} = load i${bits}, ptr ${p}, align 1${bytesElementAlias(host)}`); B.line(`${wide} = ${signed ? "sext" : "zext"} i${bits} ${raw} to i32`); B.line(`${out} = ${signed ? "sitofp" : "uitofp"} i32 ${wide} to double`); return { name: out, type: F64, uint32: wide }; @@ -342,20 +352,20 @@ function emitBytesLoadAt( const raw = B.tmp(); const out = B.tmp(); B.line(`${p} = getelementptr inbounds float, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${raw} = load float, ptr ${p}, align 1`); + B.line(`${raw} = load float, ptr ${p}, align 1${bytesElementAlias(host)}`); B.line(`${out} = fpext float ${raw} to double`); return { name: out, type: F64 }; } if (elem === "f64") { const out = B.tmp(); B.line(`${p} = getelementptr inbounds double, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${out} = load double, ptr ${p}, align 1`); + B.line(`${out} = load double, ptr ${p}, align 1${bytesElementAlias(host)}`); return { name: out, type: F64 }; } const raw = B.tmp(); const out = B.tmp(); B.line(`${p} = getelementptr inbounds i32, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`${raw} = load i32, ptr ${p}, align 1`); + B.line(`${raw} = load i32, ptr ${p}, align 1${bytesElementAlias(host)}`); B.line(`${out} = ${elem === "i32" ? "sitofp" : "uitofp"} i32 ${raw} to double`); return { name: out, type: F64, uint32: raw }; } @@ -465,6 +475,7 @@ export function emitBytesSet( const B = host.B; // Invalid integer-indexed writes are ignored after evaluating the RHS. const done = B.newLabel("bytes.store.done"); + const data = host.emitBytesData(receiver); const idx = emitBytesIndex(host, receiver, index, indexExpr, done, inBounds); if (host.mod.workers) { host.declare(`declare void @scr_bytes_shared_set(ptr, ${host.sizeType}, double)`); @@ -487,7 +498,6 @@ export function emitBytesSet( elem === "f16" || elem === "f32" || elem === "f64" ? null : host.emitToUint32(value.name, expr, value.uint32); - const data = host.emitBytesData(receiver); const p = B.tmp(); if (elem === "f16") { // One round-to-nearest-even step from the double, in the runtime. @@ -495,24 +505,24 @@ export function emitBytesSet( const narrowed = B.tmp(); B.line(`${narrowed} = call zeroext i16 @scr_f16_from_f64(double ${value.name})`); B.line(`${p} = getelementptr inbounds i16, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store i16 ${narrowed}, ptr ${p}, align 1`); + B.line(`store i16 ${narrowed}, ptr ${p}, align 1${bytesElementAlias(host)}`); } else if (BYTES_ELEMENT_SIZE[elem] < 4) { const bits = BYTES_ELEMENT_SIZE[elem] * 8; const byte = B.tmp(); B.line(`${byte} = trunc i32 ${stored!} to i${bits}`); B.line(`${p} = getelementptr inbounds i${bits}, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store i${bits} ${byte}, ptr ${p}, align 1`); + B.line(`store i${bits} ${byte}, ptr ${p}, align 1${bytesElementAlias(host)}`); } else if (elem === "f32") { const narrowed = B.tmp(); B.line(`${narrowed} = fptrunc double ${value.name} to float`); B.line(`${p} = getelementptr inbounds float, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store float ${narrowed}, ptr ${p}, align 1`); + B.line(`store float ${narrowed}, ptr ${p}, align 1${bytesElementAlias(host)}`); } else if (elem === "f64") { B.line(`${p} = getelementptr inbounds double, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store double ${value.name}, ptr ${p}, align 1`); + B.line(`store double ${value.name}, ptr ${p}, align 1${bytesElementAlias(host)}`); } else { B.line(`${p} = getelementptr inbounds i32, ptr ${data}, ${host.sizeType} ${idx}`); - B.line(`store i32 ${stored!}, ptr ${p}, align 1`); + B.line(`store i32 ${stored!}, ptr ${p}, align 1${bytesElementAlias(host)}`); } B.br(done); B.startBlock(done); diff --git a/packages/compiler/src/backend/llvm/expr-callbacks.ts b/packages/compiler/src/backend/llvm/expr-callbacks.ts index d71df333a..b91cdc543 100644 --- a/packages/compiler/src/backend/llvm/expr-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-callbacks.ts @@ -1,6 +1,13 @@ /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; -import { type IrType, isRefCounted, isUnitType, typeEquals, typeKey } from "../../ir/ir.js"; +import { + type IrType, + UNDEFINED_T, + isRefCounted, + isUnitType, + typeEquals, + typeKey, +} from "../../ir/ir.js"; import { mangleGenResThunk, mangleRecordNew, mangleRecordStruct } from "../mangle.js"; import { FN_ATTRS, llFieldType, releaseSym, retainSym, traceArg, vAdapters } from "./shapes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; @@ -48,6 +55,23 @@ export function raceAdapterFor(host: LlvmEmitterContext, from: IrType, to: IrTyp if (sym) return sym; sym = `sc_race_${host.resolveThunks.size}`; host.resolveThunks.set(key, sym); + const toArms = to.kind === "union" ? (host.unionsById.get(to.unionId)?.arms ?? []) : []; + if (from.kind === "void" && !toArms.some((a) => a.kind === "undefinedT")) { + // A Promise entry (the frontend admits void entries without an + // undefined result arm only for those): it can only reject, so its + // fulfillment adapter never runs. + host.declare(`declare void @scr_trap(ptr)`); + const msg = host.cstr("scriptc: a Promise race entry fulfilled\n"); + host.resolveThunkDefs.push( + `define internal void @${sym}(ptr %dst, ptr %src) ${FN_ATTRS} { ; race ${key}`, + `entry:`, + ` call void @scr_trap(ptr ${msg})`, + ` unreachable`, + `}`, + ``, + ); + return sym; + } if (to.kind !== "union") throw new InternalCompilerError("llvm emitter bug: race adapter to a non-union"); const toDef = host.unionsById.get(to.unionId); @@ -68,6 +92,14 @@ export function raceAdapterFor(host: LlvmEmitterContext, from: IrType, to: IrTyp `define internal void @${sym}(ptr %dst, ptr %src) ${FN_ATTRS} { ; race ${key}`, `entry:`, ]; + if (from.kind === "void") { + // A Promise entry fulfills with undefined: the result's + // undefined arm, whatever the entry's payload slot holds. + const tag = tagOf(UNDEFINED_T); + d.push(` ${fulfill(host.unitInstanceRef(to.unionId, tag))}`, ` ret void`, `}`, ``); + host.resolveThunkDefs.push(...d); + return sym; + } if (from.kind !== "union") { // One arm wrap, straight off the payload accessors. const tag = tagOf(from); @@ -739,6 +771,31 @@ export function childDataThunkFor(host: LlvmEmitterContext, param: IrType): stri return sym; } +/** The child-stream 'data' thunk for an unknown/any chunk parameter: the + * runtime's Buffer (or, after setEncoding, string) chunk boxes into a + * checked-dynamic value the listener owns. */ +export function childDataDynThunkFor(host: LlvmEmitterContext, chunk: "bytes" | "str"): string { + const key = `cdd:${chunk}`; + let sym = host.resolveThunks.get(key); + if (sym) return sym; + sym = `sc_cdd_${host.resolveThunks.size}`; + host.resolveThunks.set(key, sym); + const box = chunk === "bytes" ? "scr_dyn_new_buffer" : "scr_dyn_new_str"; + host.declare(`declare ptr @${box}(ptr)`); + host.resolveThunkDefs.push( + `define internal void @${sym}(ptr %cb, ptr %chunk) ${FN_ATTRS} { ; child 'data' → dyn (${chunk})`, + `entry:`, + ` %d = call ptr @${box}(ptr %chunk)`, + ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, + ` %fn = load ptr, ptr %fnp`, + ` call void %fn(ptr %cb, ptr %d)`, + ` ret void`, + `}`, + ``, + ); + return sym; +} + export function emitterFixedAdapter( host: LlvmEmitterContext, cbT: IrType & { kind: "func" }, @@ -807,23 +864,27 @@ export function emitterFixedAdapter( * (the event's tuple to any[]): the runtime-built listener closure holds * the boxer in caps[0], so the adapter calls it with the event's typed * arguments, as emitterFixedAdapter calls a listener, and hands the array - * to scr_events_once_fire. */ + * to scr_events_once_fire (or `fire`, the signal form's entry). */ export function emitterOnceAdapter( host: LlvmEmitterContext, boxerT: IrType & { kind: "func" }, + fire = "scr_events_once_fire", ): { fn: string; shim: string } { const cursor = boxerT.params.length > 4; const shim = cursor ? "scr_ee_inv_args" : `scr_ee_inv_fixed${boxerT.params.length}`; if (cursor) host.declare("declare ptr @scr_ee_arg_next(ptr)"); host.declare(`declare void @${shim}(ptr, ptr)`); - const key = `eeOnce:${typeKey(boxerT)}`; + const key = + fire === "scr_events_once_fire" + ? `eeOnce:${typeKey(boxerT)}` + : `eeOnce:${fire}:${typeKey(boxerT)}`; let sym = host.resolveThunks.get(key); if (sym) return { fn: sym, shim }; sym = `sc_ee_once_${host.resolveThunks.size}`; host.resolveThunks.set(key, sym); host.declare(`declare ptr @scr_box_get_ref(ptr)`); host.declare(`declare void @scr_closure_release(ptr)`); - host.declare(`declare void @scr_events_once_fire(ptr, ptr)`); + host.declare(`declare void @${fire}(ptr, ptr)`); const params = cursor ? "ptr %args" : boxerT.params.map((_, i) => `ptr %a${i}`).join(", "); const d: string[] = [ `define internal void @${sym}(ptr %cb${params ? ", " + params : ""}) ${FN_ATTRS} { ; events.once adapter ${typeKey(boxerT)}`, @@ -854,7 +915,7 @@ export function emitterOnceAdapter( ` %fn = load ptr, ptr %fnp`, ` %arr = call ptr %fn(${passed.join(", ")}) ; the boxed arguments, +1`, ` call void @scr_closure_release(ptr %boxer)`, - ` call void @scr_events_once_fire(ptr %cb, ptr %arr) ; moves %arr`, + ` call void @${fire}(ptr %cb, ptr %arr) ; moves %arr`, ` ret void`, `}`, ``, diff --git a/packages/compiler/src/backend/llvm/expr-containers.ts b/packages/compiler/src/backend/llvm/expr-containers.ts index a04f46f7b..0ca76e2d5 100644 --- a/packages/compiler/src/backend/llvm/expr-containers.ts +++ b/packages/compiler/src/backend/llvm/expr-containers.ts @@ -528,7 +528,7 @@ export function emitStableReceiver( if (b.kind !== "boxed") { if (b.kind === "global") host.checkGlobalTdz(receiver.localId); const value = host.B.tmp(); - host.B.line(`${value} = load ptr, ptr ${b.slot}`); + host.B.line(`${value} = load ptr, ptr ${b.slot}${host.slotAlias(receiver.localId)}`); return { name: value, type: receiver.type }; } } diff --git a/packages/compiler/src/backend/llvm/expr-dynamic.ts b/packages/compiler/src/backend/llvm/expr-dynamic.ts index 8a24fcf23..bc4f1128a 100644 --- a/packages/compiler/src/backend/llvm/expr-dynamic.ts +++ b/packages/compiler/src/backend/llvm/expr-dynamic.ts @@ -535,6 +535,27 @@ export function emitDynamicExpr( e.key.type.kind === "dyn" ? host.dyn.dynComputedKeyGetHelper() : host.dyn.dynKeyGetHelper(); const t = B.tmp(); const generic = `call ptr @${helper}(ptr ${d.name}, ptr ${k.name}, i1 ${e.optional ? "true" : "false"})`; + const inherited = e.key.kind === "strLit" && INHERITED_OBJECT_MEMBERS.has(e.key.value); + // Object.prototype itself answers its own members before the ordinary + // read (which fences reflection on the prototype). + let prototypeJoin: { member: string; from: string; join: string } | null = null; + if (inherited) { + host.declare(`declare ptr @scr_dyn_object_prototype_member(ptr, ptr)`); + const member = B.tmp(); + B.line( + `${member} = call ptr @scr_dyn_object_prototype_member(ptr ${d.name}, ptr ${k.name})`, + ); + const isMember = B.tmp(); + B.line(`${isMember} = icmp ne ptr ${member}, null`); + const from = B.newLabel("dkg.op"); + const read = B.newLabel("dkg.rd"); + const join = B.newLabel("dkg.opj"); + B.condBr(isMember, from, read); + B.startBlock(from); + B.br(join); + B.startBlock(read); + prototypeJoin = { member, from, join }; + } if (e.key.kind === "strLit") { // A constant key on an ordinary object reads through the site's // cache; every other receiver keeps the shared helper. @@ -560,7 +581,27 @@ export function emitDynamicExpr( } else { B.line(`${t} = ${generic}`); } - const out = host.own({ name: t, type: e.type }); + let result = t; + if (prototypeJoin) { + // An undefined read of an Object.prototype member name answers the + // member the receiver inherits (scr_dyn_invoke.c; the frontend + // links that unit for these reads). + host.declare(`declare ptr @scr_dyn_inherited_read(ptr, ptr, ptr)`); + const read = B.tmp(); + B.line( + `${read} = call ptr @scr_dyn_inherited_read(ptr ${d.name}, ptr ${k.name}, ptr ${t})`, + ); + const readEnd = B.newLabel("dkg.rde"); + B.br(readEnd); + B.startBlock(readEnd); + B.br(prototypeJoin.join); + B.startBlock(prototypeJoin.join); + result = B.tmp(); + B.line( + `${result} = phi ptr [ ${prototypeJoin.member}, %${prototypeJoin.from} ], [ ${read}, %${readEnd} ]`, + ); + } + const out = host.own({ name: result, type: e.type }); host.emitPendingCheck(); return out; } @@ -1204,3 +1245,13 @@ function emitUnionEqWrappedScalar( function wrappedStringLiteral(e: IrExpr): string | null { return e.kind === "unionWrap" && e.value.kind === "strLit" ? e.value.value : null; } + +/** Object.prototype member names whose constant-key dynamic reads fall back + * to the inherited member (runtime-features.ts links the unit). */ +export const INHERITED_OBJECT_MEMBERS: ReadonlySet = new Set([ + "constructor", + "valueOf", + "toString", + "hasOwnProperty", + "propertyIsEnumerable", +]); diff --git a/packages/compiler/src/backend/llvm/expr-primitives.ts b/packages/compiler/src/backend/llvm/expr-primitives.ts index 35e759110..0d2cd90e8 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.ts @@ -1,7 +1,7 @@ /* Focused LLVM expression emission extracted from emitter.ts. */ import { InternalCompilerError } from "../../errors.js"; import { matchStringSelfConcat, undefinedArmTag } from "../../ir/analysis.js"; -import { isRefCounted, type IrExpr, type IrType } from "../../ir/ir.js"; +import { F64, isRefCounted, type IrExpr, type IrType } from "../../ir/ir.js"; import { mangleRecordClone, mangleRecordNew } from "../mangle.js"; import { arrNewCall, elemAccess } from "./shapes.js"; import { LlvmUnsupportedError } from "./unsupported.js"; @@ -116,7 +116,7 @@ export function emitLiteralExpr( return host.own({ name: host.adoptHeap(v, e.type), type: e.type }); } const t = B.tmp(); - B.line(`${t} = load ${host.llType(b.type)}, ptr ${b.slot}`); + B.line(`${t} = load ${host.llType(b.type)}, ptr ${b.slot}${host.slotAlias(e.localId)}`); const integerView = host.integerViews.get(e.localId); if (integerView !== undefined) { // Snapshot both representations before later operands can write @@ -558,6 +558,28 @@ export function regexLiteralTemplate( return `@${re.sym}`; } +/** A string equality between `s.charAt(i)` and a literal of exactly one + * UTF-16 code unit: the charAt call and the unit. */ +function charAtUnitTest( + e: ExprOf<"strEq">, +): { charAt: ExprOf<"strIntrinsic">; code: number } | null { + let text: string; + let other: IrExpr; + if (e.right.kind === "strLit") { + text = e.right.value; + other = e.left; + } else if (e.left.kind === "strLit") { + text = e.left.value; + other = e.right; + } else { + return null; + } + if (text.length !== 1) return null; + if (other.kind !== "strIntrinsic" || other.method !== "charAt" || other.args.length !== 1) + return null; + return { charAt: other, code: text.charCodeAt(0) }; +} + export function emitStringExpr( host: LlvmEmitterContext, e: ExprOf< @@ -600,6 +622,26 @@ export function emitStringExpr( return host.own({ name: t, type: e.type }); } case "strEq": { + // `s.charAt(i) === "c"` (and `s[i]`, which lowers to charAt) for a + // one-unit literal compares the code unit: charCodeAt converts the + // index the same way, and an index out of range answers NaN, which + // equals no unit, as "" equals no one-unit literal. + const unit = charAtUnitTest(e); + if (unit !== null) { + const code = host.emitExpr({ + kind: "strIntrinsic", + method: "charCodeAt", + receiver: unit.charAt.receiver, + args: unit.charAt.args, + type: F64, + loc: unit.charAt.loc, + }); + const t = B.tmp(); + B.line( + `${t} = fcmp ${e.negated ? "une" : "oeq"} double ${code.name}, ${f64Lit(unit.code)}`, + ); + return { name: t, type: e.type }; + } const [l, r] = emitStringInputs(host, [e.left, e.right]); const eq = B.tmp(); // A literal side compares its length and bytes inline. diff --git a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts index 8a53b5fa8..bbd0226fb 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-bridges.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-bridges.ts @@ -5,14 +5,17 @@ import { streamTypedRefEligible, undefinedArmTag } from "../../ir/analysis.js"; import { type IrRecordShape, type IrType, + CLASS_ATTRIBUTES, DYN_CLASS_PROPERTIES, RUNTIME_STREAM_CLASSES, SYMBOL_T, classDynViewSupported, + classFieldAttributesName, isClassOwnEnumerableFieldName, isDirentShape, isDynTypedRefType, isRefCounted, + shapeHasAccessorSlots, typeKey, } from "../../ir/ir.js"; import { mangleFunction, mangleGlobal, mangleRecordStruct } from "../mangle.js"; @@ -763,9 +766,15 @@ export function streamTypedRefMaterializeAdapter( !meta.def.instancePrototypeHelper && !meta.def.prototypeDataHelper ) { - host.declare(`declare ptr @scr_stream_dyn_view(ptr)`); - B.line(`${out} = call ptr @scr_stream_dyn_view(ptr %p)`); + // Programs with constructor-function streams view them through + // scr_dyn_builtin_stream.c, which answers their own objects. + const view = host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_view" + : "scr_stream_dyn_view"; + host.declare(`declare ptr @${view}(ptr)`); + B.line(`${out} = call ptr @${view}(ptr %p)`); } else B.line(`${out} = call ptr @scr_dyn_new_obj()`); + const withAttributes = meta.def.fields.some((field) => field.name === CLASS_ATTRIBUTES); if (meta.def.tracksOwnFields) { host.declare(`declare ptr @scr_dyn_copy_property_descriptors(ptr, ptr)`); host.declare(`declare void @scr_dyn_release_v(ptr)`); @@ -835,9 +844,27 @@ export function streamTypedRefMaterializeAdapter( const nullable = host.nullableFields.get(t.className, field.name); if (nullable) fieldValue = nullableFieldUnion(host, B, fieldValue, nullable); const boxed = host.streamTypedRefBoxValue(B, field.type, fieldValue); + // Attributes copied from the bag do not stop the view taking the value. + const setter = withAttributes ? "scr_dyn_obj_set_field" : "scr_dyn_obj_set"; + if (withAttributes) + host.declare(`declare void @scr_dyn_obj_set_field(ptr, ptr, ${host.sizeType}, ptr)`); B.line( - `call void @scr_dyn_obj_set(ptr ${out}, ptr ${host.cstr(field.name)}, ${host.sizeType} ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`, + `call void @${setter}(ptr ${out}, ptr ${host.cstr(field.name)}, ${host.sizeType} ${Buffer.byteLength(field.name, "utf8")}, ptr ${boxed})`, ); + // A field redefined through Object.defineProperty keeps its attributes. + const attributesField = classFieldAttributesName(field.name); + if (meta.def.fields.some((f) => f.name === attributesField)) { + host.declare(`declare void @scr_dyn_class_field_attributes(ptr, ptr, double)`); + const slot = B.tmp(), + bits = B.tmp(); + B.line( + `${slot} = getelementptr inbounds %${classStructSym(t.className)}, ptr %p, i64 0, i32 ${classFieldIndex(meta, attributesField).index}`, + ); + B.line(`${bits} = load double, ptr ${slot}`); + B.line( + `call void @scr_dyn_class_field_attributes(ptr ${out}, ptr ${host.internLiteral(field.name)}, double ${bits})`, + ); + } if (nullable) { host.declare(`declare void @scr_union_release(ptr)`); B.line(`call void @scr_union_release(ptr ${fieldValue})`); @@ -920,6 +947,29 @@ export function streamTypedRefMaterializeAdapter( B.line(`${copied} = call ptr @scr_dyn_copy_property_descriptors(ptr ${out}, ptr ${bag})`); B.line(`call void @scr_dyn_release_v(ptr ${copied})`); } + // An Error subclass instance's own `name` sits where its class's + // construction creates it: after a field, or after every other + // property when no constructor sets it. + const nameAfter = meta.def.errorNameAfter; + if (meta.root.def.name === "%Error" && !meta.def.runtime && nameAfter !== null) { + host.declare(`declare void @scr_error_dyn_place_name(ptr, ptr, ${host.sizeType})`); + B.line( + nameAfter === undefined + ? `call void @scr_error_dyn_place_name(ptr ${out}, ptr null, ${host.sizeType} 0)` + : `call void @scr_error_dyn_place_name(ptr ${out}, ptr ${host.cstr(nameAfter)}, ${host.sizeType} ${Buffer.byteLength(nameAfter, "utf8")})`, + ); + } + // A frozen, sealed or non-extensible instance's view says so. + if (withAttributes) { + host.declare(`declare void @scr_dyn_class_view_attributes(ptr, double)`); + const slot = B.tmp(), + bits = B.tmp(); + B.line( + `${slot} = getelementptr inbounds %${classStructSym(t.className)}, ptr %p, i64 0, i32 ${classFieldIndex(meta, CLASS_ATTRIBUTES).index}`, + ); + B.line(`${bits} = load double, ptr ${slot}`); + B.line(`call void @scr_dyn_class_view_attributes(ptr ${out}, double ${bits})`); + } B.terminate(`ret ptr ${out}`); } else if (t.kind === "record") { const shape = host.recordsById.get(t.shapeId); @@ -933,6 +983,7 @@ export function streamTypedRefMaterializeAdapter( * Declared-field records and tuples cover the live stream values. */ const ordinary = shape.indexValue || + shapeHasAccessorSlots(shape) || shape.fields.some((field) => field.name === "handleEvent" && field.type.kind === "func"); if (ordinary) { const out = B.tmp(); diff --git a/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts b/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts index 3f7961163..9be757a20 100644 --- a/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts +++ b/packages/compiler/src/backend/llvm/expr-stream-callbacks.ts @@ -17,7 +17,7 @@ export function streamDataAdapter( const p = cbT.params[0]; if ( cbT.params.length > 1 || - (p && p.kind !== "bytes" && p.kind !== "string" && p.kind !== "dyn") + (p && p.kind !== "bytes" && p.kind !== "string" && p.kind !== "dyn" && p.kind !== "union") ) { throw new InternalCompilerError( "llvm emitter bug: stream data listener param shape (frontend must fence)", @@ -73,6 +73,46 @@ export function streamDataAdapter( ); finish("%r0"); d.push(`}`, ``); + } else if (p.kind === "union") { + // A `Buffer | string` chunk: wrap whichever slot the stream filled at + // its arm of the (boxed) union. + const def = host.unionsById.get(p.unionId); + const bytesTag = def ? def.arms.findIndex((a) => a.kind === "bytes" && a.elem === "u8") : -1; + const strTag = def ? def.arms.findIndex((a) => a.kind === "string") : -1; + if ( + !def || + host.nullableUnions.has(p.unionId) || + bytesTag < 0 || + strTag < 0 || + def.arms.length !== 2 + ) + throw new InternalCompilerError( + "llvm emitter bug: stream data listener union is not Buffer | string (frontend must fence)", + ); + host.declare(`declare ptr @scr_union_new_ref(i32, ptr, ptr, ptr, ptr)`); + host.declare(`declare ptr @scr_bytes_retain_v(ptr)`); + host.declare(`declare void @scr_bytes_release_v(ptr)`); + host.declare(`declare ptr @scr_str_retain_v(ptr)`); + host.declare(`declare void @scr_str_release_v(ptr)`); + d.push( + ` %uslot = alloca ptr`, + ` %isb = icmp ne ptr %a0, null`, + ` br i1 %isb, label %buf, label %str`, + `buf:`, + ` %rb = call ptr @scr_bytes_retain_v(ptr %a0)`, + ` %ub = call ptr @scr_union_new_ref(i32 ${bytesTag}, ptr %rb, ptr @scr_bytes_retain_v, ptr @scr_bytes_release_v, ptr null)`, + ` store ptr %ub, ptr %uslot`, + ` br label %go`, + `str:`, + ` %rs = call ptr @scr_str_retain_v(ptr %a1)`, + ` %us = call ptr @scr_union_new_ref(i32 ${strTag}, ptr %rs, ptr @scr_str_retain_v, ptr @scr_str_release_v, ptr null)`, + ` store ptr %us, ptr %uslot`, + ` br label %go`, + `go:`, + ` %uv = load ptr, ptr %uslot`, + ); + finish("%uv"); + d.push(`}`, ``); } else { // dyn: box by runtime tag — the JS lane's adapter parameter. host.declare(`declare ptr @scr_dyn_new_buffer(ptr)`); diff --git a/packages/compiler/src/backend/llvm/lib-datagram.ts b/packages/compiler/src/backend/llvm/lib-datagram.ts index b32446bcb..184fdb99e 100644 --- a/packages/compiler/src/backend/llvm/lib-datagram.ts +++ b/packages/compiler/src/backend/llvm/lib-datagram.ts @@ -236,6 +236,10 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L return finishRuntimeCall(host, e, "scr_dns_lookup_dyn", a); case "dns.lookupService": return finishRuntimeCall(host, e, "scr_dns_lookup_service", a); + case "dns.promisesLookup": + return finishRuntimeCall(host, e, "scr_dns_promises_lookup", a); + case "dns.promisesLookupService": + return finishRuntimeCall(host, e, "scr_dns_promises_lookup_service", a); case "dns.getServers": return finishRuntimeCall(host, e, "scr_dns_get_servers", a); case "dns.getDefaultResultOrder": diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 31d7ca5c2..924027ee2 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -83,12 +83,19 @@ export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue } // A signal that is not an AbortSignal throws ERR_INVALID_ARG_TYPE. if (mode & 8) host.emitPendingCheck(); + // readline/promises interfaces are instances of that module's class. + if (mode & 32) { + host.declare(`declare void @scr_rl_mark_promises(double)`); + B.line(`call void @scr_rl_mark_promises(double ${t})`); + } return { name: t, type: e.type }; } - if (e.fn === "rl.question") { + if (e.fn === "rl.question" || e.fn === "rl.questionDyn") { // The answer callback MOVES into the interface's registry; throws - // Node's use-after-close error (the may-throw seed). + // Node's use-after-close error (the may-throw seed), and with a + // checked-dynamic query that is not a string, ERR_INVALID_ARG_TYPE. host.usesTimers = true; + const fn = e.fn === "rl.question" ? "scr_rl_question" : "scr_rl_question_dyn"; const cbT = e.args[2]!.type; if (cbT.kind !== "func") throw new InternalCompilerError("llvm emitter bug: rl.question callback not a func"); @@ -101,29 +108,37 @@ export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue ? "scr_rl_answer_thunk_dyn" : "scr_rl_answer_thunk_str"; host.declare(`declare void @${adapter}(ptr, ptr)`); - host.declare(`declare void @scr_rl_question(double, ptr, ptr, ptr)`); + host.declare(`declare void @${fn}(double, ptr, ptr, ptr)`); B.line( - `call void @scr_rl_question(double ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${args[2]!.name}, ptr @${adapter})`, + `call void @${fn}(double ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${args[2]!.name}, ptr @${adapter})`, ); host.emitPendingCheck(); return { name: "", type: e.type }; } - if (e.fn === "rl.questionAsync" || e.fn === "rl.questionAsyncSignal") { - // The promise question never throws: closed interfaces and aborted - // signals reject the answered promise (+1). + if ( + e.fn === "rl.questionAsync" || + e.fn === "rl.questionAsyncSignal" || + e.fn === "rl.questionAsyncDyn" || + e.fn === "rl.questionAsyncSignalDyn" + ) { + // The promise question never throws: closed interfaces, aborted + // signals and queries that are not strings reject the answered + // promise (+1). host.usesTimers = true; const args = e.args.map((a) => host.emitExpr(a)); + const dyn = e.fn === "rl.questionAsyncDyn" || e.fn === "rl.questionAsyncSignalDyn"; + const fn = dyn ? "scr_rl_question_promise_dyn" : "scr_rl_question_promise"; let signal = "null"; let add = "null"; - if (e.fn === "rl.questionAsyncSignal") { + if (e.fn === "rl.questionAsyncSignal" || e.fn === "rl.questionAsyncSignalDyn") { signal = args[2]!.name; host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`); add = "@scr_events_add_abort_listener"; } - host.declare(`declare ptr @scr_rl_question_promise(double, ptr, ptr, ptr)`); + host.declare(`declare ptr @${fn}(double, ptr, ptr, ptr)`); const t = B.tmp(); B.line( - `${t} = call ptr @scr_rl_question_promise(double ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${signal}, ptr ${add})`, + `${t} = call ptr @${fn}(double ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${signal}, ptr ${add})`, ); return host.own({ name: t, type: e.type }); } @@ -533,7 +548,12 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll mockTimerSym(host, e) ?? (e.fn === "timers.queueMicrotaskDyn" && host.schedulesInContext ? "scr_queue_microtask_dyn_als" - : LIB_FN_SYMS[e.fn]); + : // Programs with constructor-function streams unbox untyped pipe + // destinations and pipeline stages through scr_dyn_builtin_stream.c, + // which also takes those streams. + e.fn === "stream.unboxDyn" && host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_unbox" + : LIB_FN_SYMS[e.fn]); if (sym === undefined) throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc); const args = READ_ONLY_STRING_LIB_FNS.has(e.fn) ? emitBorrowedInputs(host, e.args) @@ -740,7 +760,8 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { } const prefix = e.fn.slice(0, e.fn.indexOf(".")) as LibCallPrefix; // events.once builds typed listener adapters beside the emitter calls. - if (e.fn === "events.once") return host.emitErrorsEventsLibCall(e); + if (e.fn === "events.once" || e.fn === "events.onceSignal") + return host.emitErrorsEventsLibCall(e); switch (prefix) { case "fetch": case "abort": @@ -842,6 +863,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue { case "event": case "events": case "blob": + case "wasm": case "record": case "disposable": case "punycode": diff --git a/packages/compiler/src/backend/llvm/lib-filesystem.ts b/packages/compiler/src/backend/llvm/lib-filesystem.ts index 4798b1f09..358b21c4f 100644 --- a/packages/compiler/src/backend/llvm/lib-filesystem.ts +++ b/packages/compiler/src/backend/llvm/lib-filesystem.ts @@ -1097,6 +1097,30 @@ export function emitPrimitiveLibCall(host: LlvmEmitterContext, e: LibCallExpr): B.line(`${t} = call ptr @scr_str_from_char_code_one(double ${code.name})`); return host.own({ name: t, type: e.type }); } + if ( + e.fn === "string.fromCharCode" && + packed.kind === "arrayLit" && + packed.elems.length <= 16 && + !packed.spreads?.length + ) { + // A short argument list passes its codes in a stack array. + const codes = packed.elems.map((elem) => host.emitExpr(elem)); + const slot = B.slot(); + B.entryAllocas.push(`${slot} = alloca [${Math.max(codes.length, 1)} x double]`); + codes.forEach((code, i) => { + const p = B.tmp(); + B.line( + `${p} = getelementptr inbounds [${Math.max(codes.length, 1)} x double], ptr ${slot}, i64 0, i64 ${i}`, + ); + B.line(`store double ${code.name}, ptr ${p}`); + }); + host.declare(`declare ptr @scr_str_from_char_codes(ptr, ${host.sizeType})`); + const t = B.tmp(); + B.line( + `${t} = call ptr @scr_str_from_char_codes(ptr ${slot}, ${host.sizeType} ${codes.length})`, + ); + return host.own({ name: t, type: e.type }); + } // One packed f64[] (the frontend built it) or one bytes value (the // spread-typed-array form); +1 string. const base = diff --git a/packages/compiler/src/backend/llvm/lib-network.ts b/packages/compiler/src/backend/llvm/lib-network.ts index ea37fc2cf..d49bbde14 100644 --- a/packages/compiler/src/backend/llvm/lib-network.ts +++ b/packages/compiler/src/backend/llvm/lib-network.ts @@ -60,7 +60,54 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr) ...handler, ]); } + if ( + e.fn === "http.requestDyn" || + e.fn === "http.requestDynCb" || + e.fn === "https.requestDyn" || + e.fn === "https.requestDynCb" + ) { + // [options, autoEnd(, cb)]: the runtime walks the options record and + // attaches its signal through events.addAbortListener (the event-target + // unit, linked for these calls by runtime-features). + const args = e.args.map((arg) => host.emitExpr(arg)); + host.usesTimers = true; + const cb = args[2]; + const handler = + cb === undefined + ? [ptr(), ptr()] + : callbackAdapter( + host, + cb, + callbackType(cb).params.length === 0 + ? "scr_http_resp_thunk0" + : "scr_http_resp_thunk_res", + ["ptr", "ptr"], + ); + host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`); + return finishRuntimeCall( + host, + e, + e.fn.startsWith("https.") ? "scr_https_request_dyn" : "scr_http_request_dyn", + [ + abiValue(host, args[0]!), + abiValue(host, args[1]!), + ...handler, + ptr("@scr_events_add_abort_listener"), + ], + ); + } const B = host.B; + if (e.fn === "net.listenPath" || e.fn === "net.listenPathCb") { + const args = e.args.map((a) => host.emitExpr(a)); + let cb = "null"; + if (e.fn === "net.listenPathCb") { + host.toHeap(args[2]!); + cb = args[2]!.name; + } + host.declare(`declare void @scr_net_listen_path(ptr, ptr, ptr)`); + B.line(`call void @scr_net_listen_path(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${cb})`); + return { name: "", type: e.type }; + } if (e.fn === "net.connectOptsChk") { return emitAlwaysThrowLibCall(host, e, "scr_net_connect_opts_chk"); } diff --git a/packages/compiler/src/backend/llvm/lib-process.ts b/packages/compiler/src/backend/llvm/lib-process.ts index eaf7f31cd..097fcb4de 100644 --- a/packages/compiler/src/backend/llvm/lib-process.ts +++ b/packages/compiler/src/backend/llvm/lib-process.ts @@ -305,6 +305,28 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr B.line(`${raw} = call ptr @${get}(ptr ${recv.name}) ; +1 or NULL`); return host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag); } + if (e.fn === "child.stdioStream") { + // The general stream representation's stdio read: the child's bridged + // runtime stream (+1) or NULL for an unpiped slot. + if (e.type.kind !== "union") + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} result is not a union`); + const def = host.unionsById.get(e.type.unionId); + const streamTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1; + const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1; + const fd = e.args[1]; + if (streamTag < 0 || nullTag < 0 || fd?.kind !== "numLit") + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} lacks its arms or slot`); + const recv = host.emitExpr(e.args[0]!); + const key = fd.value === 0 ? "stdin" : fd.value === 1 ? "stdout" : "stderr"; + host.declare(`declare ptr @scr_stream_child_stdio(ptr, ptr, ${host.sizeType})`); + const raw = B.tmp(); + B.line( + `${raw} = call ptr @scr_stream_child_stdio(ptr ${recv.name}, ptr ${host.cstr(key)}, ${host.sizeType} ${key.length}) ; +1 or NULL`, + ); + const out = host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag); + host.emitPendingCheck(); + return out; + } if (e.fn === "writer.writeString" || e.fn === "writer.writeBytes") { const writer = host.emitExpr(e.args[0]!); const data = host.emitExpr(e.args[1]!); @@ -709,27 +731,34 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr host.emitAlwaysPendingUnwind(); return out; } - if (e.fn === "events.once") { - // (recv, name, boxer, errorBoxer, listenForError): scr_events_once - // registers one-shot typed listeners behind adapters that box the - // event's tuple (and the 'error' tuple) into the fulfilled array. The - // runtime retains the boxers; the frame's temporaries release as usual. - // May-throw ('newListener' meta listeners run inside). + if (e.fn === "events.once" || e.fn === "events.onceSignal") { + // (recv, name, boxer, errorBoxer, listenForError[, signal]): + // scr_events_once registers one-shot typed listeners behind adapters + // that box the event's tuple (and the 'error' tuple) into the fulfilled + // array. The runtime retains the boxers; the frame's temporaries + // release as usual. May-throw ('newListener' meta listeners run + // inside). The signal form's adapters end in its own fire entry. const boxerT = e.args[2]!.type; const errorT = e.args[3]!.type; if (boxerT.kind !== "func" || errorT.kind !== "func") - throw new InternalCompilerError("llvm emitter bug: events.once boxer not a func"); + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} boxer not a func`); + const signal = e.fn === "events.onceSignal"; const args = e.args.map((a) => host.emitExpr(a)); - const event = host.emitterOnceAdapter(boxerT); - const error = host.emitterOnceAdapter(errorT); - host.declare(`declare ptr @scr_events_once(ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr)`); + const fire = signal ? "scr_events_once_signal_fire" : undefined; + const event = host.emitterOnceAdapter(boxerT, fire); + const error = host.emitterOnceAdapter(errorT, fire); + const entry = signal ? "scr_events_once_signal" : "scr_events_once"; + host.declare( + `declare ptr @${entry}(ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr${signal ? ", ptr" : ""})`, + ); const errorBoxer = B.tmp(); B.line(`${errorBoxer} = select i1 ${args[4]!.name}, ptr ${args[3]!.name}, ptr null`); const t = B.tmp(); B.line( - `${t} = call ptr @scr_events_once(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ` + + `${t} = call ptr @${entry}(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ` + `ptr ${args[2]!.name}, ptr @${event.fn}, ptr @${event.shim}, ` + - `ptr ${errorBoxer}, ptr @${error.fn}, ptr @${error.shim})`, + `ptr ${errorBoxer}, ptr @${error.fn}, ptr @${error.shim}` + + `${signal ? `, ptr ${args[5]!.name}` : ""})`, ); const out = host.own({ name: t, type: e.type }); host.emitPendingCheck(); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 2d91386b3..8cd40240f 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -127,6 +127,11 @@ export const LIB_FN_SYMS: Record = { "str.encodeUriComponent": "scr_str_encode_uri_component", "error.cause": "scr_error_cause", "error.setCause": "scr_error_set_cause", + "error.setCode": "scr_error_set_code_value", + "error.setCodeDyn": "scr_error_set_code_dyn", + "error.assign": "scr_error_assign_dyn", + "error.hasKey": "scr_error_has_key", + "error.hasCode": "scr_error_has_code", "error.defineCause": "scr_error_define_cause", "error.deleteCause": "scr_error_delete_cause", "error.hasCause": "scr_error_has_cause", @@ -278,6 +283,14 @@ export const LIB_FN_SYMS: Record = { "perf.entryDetail": "scr_perf_entry_detail", "perf.entryKeep": "scr_perf_entry_keep", "perf.entryRef": "scr_perf_entry_ref", + "perf.loopStart": "scr_perf_loop_start", + "perf.idleTime": "scr_perf_idle_time", + "perf.eldNew": "scr_eld_new", + "perf.eldEnable": "scr_eld_enable", + "perf.eldDisable": "scr_eld_disable", + "perf.eldReset": "scr_eld_reset", + "perf.eldPercentile": "scr_eld_percentile", + "perf.eldStat": "scr_eld_stat", "process.memoryUsageTake": "scr_process_memory_usage_take", "process.memoryUsageField": "scr_process_memory_usage_field", "process.rss": "scr_process_rss", @@ -294,9 +307,12 @@ export const LIB_FN_SYMS: Record = { "process.rusage": "scr_process_rusage", "process.activeResources": "scr_active_resources", "process.exiting": "scr_process_exiting", + "process.ipcOpen": "scr_process_ipc_open", "process.exit": "scr_process_exit", "process.setExitCode": "scr_process_set_exit_code", "process.currentExitCode": "scr_process_exit_code_or_zero", + "process.setExitCodeTracked": "scr_process_set_exit_code_tracked", + "process.hasExitCode": "scr_process_has_exit_code", "process.envSet": "scr_env_set", "process.envUnset": "scr_env_unset", "process.envPairs": "scr_env_pairs", @@ -307,12 +323,13 @@ export const LIB_FN_SYMS: Record = { "date.now": "scr_date_now", "date.newNow": "scr_date_now", "date.newMs": "scr_date_new_ms", - "date.newString": "scr_date_parse_get_time", - "date.parse": "scr_date_parse_get_time", + "date.newString": "scr_date_parse", + "date.parse": "scr_date_parse", "date.getTime": "scr_date_get_time", "date.valueOf": "scr_date_get_time", - "date.parseGetTime": "scr_date_parse_get_time", + "date.parseGetTime": "scr_date_parse", "date.utc": "scr_date_utc", + "date.newLocal": "scr_date_new_local", "date.getFullYear": "scr_date_get_full_year_local", "date.getUTCFullYear": "scr_date_get_full_year_utc", "date.getMonth": "scr_date_get_month_local", @@ -723,6 +740,10 @@ export const LIB_FN_SYMS: Record = { "child.unref": "scr_child_unref", "child.ref": "scr_child_ref", "procStream.write": "scr_proc_stream_write", + "stream.stdioWritable": "scr_stream_stdio_writable", + "stream.unboxDyn": "scr_stream_unbox_dyn", + "stream.pipeDyn": "scr_stream_pipe_dyn", + "stream.unpipeDyn": "scr_stream_unpipe_dyn", "process.kill": "scr_process_kill_named", "process.killNum": "scr_process_kill", // fs.promises (scr_bytes_io.c / scr_lib.c): the promise forms settle @@ -827,6 +848,7 @@ export const LIB_FN_SYMS: Record = { "dyn.sameValue": "scr_dyn_same_value", "error.toString": "scr_error_to_string", "error.stack": "scr_error_stack", + "error.setStack": "scr_error_set_stack", "error.stackLimitGet": "scr_stack_limit_get", "error.stackLimitSet": "scr_stack_limit_set", "class.name": "scr_classobj_name", @@ -854,6 +876,24 @@ export const LIB_FN_SYMS: Record = { "event.get": "scr_event_obj_get", "event.set": "scr_event_obj_set", "blob.new": "scr_blob_new", + "wasm.compile": "scr_wasm_compile", + "wasm.validate": "scr_wasm_validate", + "wasm.instantiate": "scr_wasm_instantiate", + "wasm.moduleNew": "scr_wasm_module_new", + "wasm.instanceNew": "scr_wasm_instance_new", + "wasm.is": "scr_wasm_is", + "wasm.errorNew": "scr_wasm_error_new", + "wasm.memoryNew": "scr_wasm_memory_construct", + "wasm.tableNew": "scr_wasm_table_construct", + "wasm.globalNew": "scr_wasm_global_construct", + "wasm.withoutNew": "scr_wasm_construct_without_new", + "wasm.moduleImports": "scr_wasm_module_imports", + "wasm.moduleExports": "scr_wasm_module_exports", + "wasm.moduleCustomSections": "scr_wasm_module_custom_sections", + "wasm.valueOf": "scr_wasm_value_of", + "wasm.function": "scr_wasm_function", + "wasm.namespace": "scr_wasm_namespace", + "wasm.globalView": "scr_wasm_global_view", "blob.fileNew": "scr_file_new", "blob.slice": "scr_blob_slice", "blob.text": "scr_blob_text", @@ -922,16 +962,27 @@ export const LIB_FN_SYMS: Record = { "dyn.promiseAll": "scr_dyn_promise_all", "dyn.promiseAllSettled": "scr_dyn_promise_all_settled", "dyn.promiseAny": "scr_dyn_promise_any", + "dyn.promiseResolve": "scr_dyn_promise_resolve", "dyn.typeof": "scr_dyn_typeof", - "dyn.objectTag": "scr_dyn_object_tag", + "dyn.objectTag": "scr_dyn_object_tag_of", "dyn.freeze": "scr_dyn_freeze", "dyn.isFrozen": "scr_dyn_is_frozen", + "class.defineField": "scr_class_define_field", + "class.definedBits": "scr_class_defined_bits", + "class.definedValue": "scr_class_defined_value", + "class.descriptorHasValue": "scr_class_descriptor_has_value", + "class.throwReadOnly": "scr_class_throw_read_only", + "class.throwNotExtensible": "scr_class_throw_not_extensible", "dyn.nativeSetNew": "scr_dyn_native_set_new", "dyn.nativeSetIs": "scr_dyn_native_set_is", "dyn.nativeMapIs": "scr_dyn_native_map_is", "dyn.nativeUrlIs": "scr_dyn_native_url_is", "dyn.nativeDateIs": "scr_dyn_native_date_is", + "dyn.globalConstructor": "scr_dyn_global_constructor", + "dyn.builtinFunction": "scr_dyn_builtin_function", "date.nativeNew": "scr_dyn_native_date_new", + "date.nativeNewFields": "scr_dyn_native_date_new_fields", + "date.nativeNewValue": "scr_dyn_native_date_new_value", "date.checkedValue": "scr_dyn_native_date_value", "dyn.nativeRegexIs": "scr_dyn_native_regex_is", "dyn.toString": "scr_dyn_to_string_argument", @@ -994,6 +1045,7 @@ export const LIB_FN_SYMS: Record = { // family have non-generic shapes (adapter/variadic) — special-cased in // emitLibCall. "emitter.new": "scr_emitter_new", + "emitter.className": "scr_emitter_class_name", "emitter.ctor": "scr_emitter_init", "emitter.off": "scr_emitter_off", "emitter.offDyn": "scr_emitter_off_dyn", @@ -1039,6 +1091,7 @@ export const LIB_FN_SYMS: Record = { "rl.prompt": "scr_rl_prompt", "rl.promptWith": "scr_rl_prompt_with", "rl.setPrompt": "scr_rl_set_prompt", + "rl.setPromptDyn": "scr_rl_set_prompt_dyn", "rl.getPrompt": "scr_rl_get_prompt", "rl.write": "scr_rl_write", "rl.writeKey": "scr_rl_write_key", @@ -1054,6 +1107,9 @@ export const LIB_FN_SYMS: Record = { "rl.terminal": "scr_rl_terminal", "rl.line": "scr_rl_line", "rl.isTerminal": "scr_rl_is_terminal", + "rl.isPromises": "scr_rl_is_promises", + "rl.inputFromDyn": "scr_stream_rl_from_dyn", + "rl.outputFromDyn": "scr_stream_rl_from_dyn", "rl.cursor": "scr_rl_cursor", "rl.seq": "scr_rl_seq", "rl.moves": "scr_rl_moves", @@ -1089,6 +1145,7 @@ export const LIB_FN_SYMS: Record = { "readable.opFind": "scr_stream_op_find", "readable.opReduce": "scr_stream_op_reduce", "readable.pipe": "scr_stream_pipe", + "readable.pipeGeneral": "scr_stream_pipe_general", "writable.cork": "scr_stream_cork", "writable.uncork": "scr_stream_uncork", "stream.destroyErr": "scr_stream_destroy", @@ -1136,6 +1193,10 @@ export const LIB_FN_SYMS: Record = { "net.sockReadyState": "scr_net_sock_ready_state", "net.sockSetKeepAlive": "scr_net_sock_set_keepalive_ret", "net.sockOnCloseDyn": "scr_net_sock_on_close_dyn", + "net.sockOnLookup": "scr_net_sock_on_lookup", + "net.serverCloseDyn": "scr_net_server_close_dyn", + "net.serverUnixPath": "scr_net_server_unix_path", + "net.serverAddressCheck": "scr_net_server_address_check", "net.sockOnWriteFlushDyn": "scr_net_sock_on_write_flush_dyn", "net.sockOnFinishDyn": "scr_net_sock_on_finish_dyn", "net.isIP": "scr_net_is_ip", @@ -1399,6 +1460,10 @@ export const LIB_FN_SYMS: Record = { "dyn.classPrototype": "scr_dyn_class_prototype", "dyn.classBasePrototype": "scr_dyn_class_base_prototype", "dyn.classInherit": "scr_dyn_class_inherit", + "dyn.builtinClass": "scr_dyn_builtin_class", + "dyn.builtinStreamClass": "scr_dyn_builtin_stream_class", + "dyn.builtinInstanceOf": "scr_dyn_builtin_instanceof", + "dyn.functionInstanceOf": "scr_dyn_function_instanceof", "dyn.classSuper": "scr_dyn_class_super", "dyn.assignPrototype": "scr_dyn_assign_prototype", "dyn.definePrototypeProps": "scr_dyn_define_prototype_props", @@ -1475,6 +1540,8 @@ export function mockTimerSym( export const USES_TIMERS_LIB_FNS = new Set([ "process.emitWarning", + // Constructor-function streams defer their events on the stream ticks. + "dyn.builtinStreamClass", // Decipheriv.setAuthTag reports DEP0182 on the next tick. "crypto.cipherNew", // WebCrypto results settle through the deferred crypto queue. @@ -1571,6 +1638,7 @@ export const USES_TIMERS_LIB_FNS = new Set([ "readable.opReduce", "readable.resume", "readable.pipe", + "readable.pipeGeneral", "readable.unpipe", "writable.write", "writable.writeStr", @@ -1611,6 +1679,8 @@ export const USES_TIMERS_LIB_FNS = new Set([ "sc.arrayBuffer", "net.listen", "net.listenCb", + "net.listenPath", + "net.listenPathCb", "net.listenOpts", "net.listenOptsCb", "net.listenOptsReusePort", @@ -1629,6 +1699,10 @@ export const USES_TIMERS_LIB_FNS = new Set([ "http.requestCb", "http.requestUrl", "http.requestUrlCb", + "http.requestDyn", + "http.requestDynCb", + "https.requestDyn", + "https.requestDynCb", "https.request", "https.requestCb", "https.requestUrl", @@ -1650,4 +1724,5 @@ export const USES_TIMERS_LIB_FNS = new Set([ "timers.queueMicrotaskDyn", "timers.setImmediateFnValue", "timers.immediatePromise", + "dyn.builtinFunction", ]); diff --git a/packages/compiler/src/backend/llvm/lib-stream.ts b/packages/compiler/src/backend/llvm/lib-stream.ts index b62b237df..a319d0257 100644 --- a/packages/compiler/src/backend/llvm/lib-stream.ts +++ b/packages/compiler/src/backend/llvm/lib-stream.ts @@ -36,6 +36,29 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV host.emitPendingCheck(); return out; } + if (e.fn === "stream.dynNative" || e.fn === "stream.dynNativePipe") { + // Checked-dynamic stream slots: the boxed native stream (+1) or NULL. + if (e.type.kind !== "union") + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} result is not a union`); + const def = host.unionsById.get(e.type.unionId); + const streamTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1; + const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1; + if (streamTag < 0 || nullTag < 0) + throw new InternalCompilerError(`llvm emitter bug: ${e.fn} union lacks its arms`); + const value = host.emitExpr(e.args[0]!); + const side = host.emitExpr(e.args[1]!); + // A pipe or unpipe destination in programs with constructor-function + // streams can be one of those streams too (scr_dyn_builtin_stream.c), + // as stream.unboxDyn does there. + const fn = + e.fn === "stream.dynNativePipe" && host.usesDynBuiltinStream + ? "scr_dyn_builtin_stream_dyn_native" + : "scr_stream_dyn_native"; + host.declare(`declare ptr @${fn}(ptr, double)`); + const raw = B.tmp(); + B.line(`${raw} = call ptr @${fn}(ptr ${value.name}, double ${side.name}) ; +1 or NULL`); + return host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag); + } if (e.fn === "stream.onData") { // The callback MOVES into the stream's listener registry; the // adapter is per callback shape — runtime-provided for the @@ -56,6 +79,8 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV host.declare(`declare void @scr_child_stream_thunk0(ptr, ptr)`); } else if (param.kind === "union") { adapter = host.childDataThunkFor(param); + } else if (param.kind === "dyn") { + adapter = host.childDataDynThunkFor("bytes"); } else { adapter = "scr_child_stream_thunk_bytes"; host.declare(`declare void @scr_child_stream_thunk_bytes(ptr, ptr)`); @@ -78,7 +103,8 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV if (e.fn === "stream.onDataStr") { host.usesTimers = true; const cbT = e.args[1]!.type; - if (cbT.kind !== "func" || cbT.params[0]?.kind !== "string") { + const strParam = cbT.kind === "func" ? cbT.params[0]?.kind : undefined; + if (strParam !== "string" && strParam !== "dyn") { throw new InternalCompilerError( "llvm emitter bug: stream.onDataStr callback not a string func", ); @@ -87,10 +113,12 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV const cb = host.emitExpr(e.args[1]!); const once = host.emitExpr(e.args[2]!); host.toHeap(cb); - host.declare(`declare void @scr_child_stream_thunk_str(ptr, ptr)`); + let adapter = "scr_child_stream_thunk_str"; + if (strParam === "dyn") adapter = host.childDataDynThunkFor("str"); + else host.declare(`declare void @scr_child_stream_thunk_str(ptr, ptr)`); host.declare(`declare void @scr_child_stream_on_data_str(ptr, ptr, ptr, i1 zeroext)`); B.line( - `call void @scr_child_stream_on_data_str(ptr ${s0.name}, ptr ${cb.name}, ptr @scr_child_stream_thunk_str, i1 ${once.name})`, + `call void @scr_child_stream_on_data_str(ptr ${s0.name}, ptr ${cb.name}, ptr @${adapter}, i1 ${once.name})`, ); return { name: "", type: e.type }; } diff --git a/packages/compiler/src/backend/llvm/receivers.ts b/packages/compiler/src/backend/llvm/receivers.ts new file mode 100644 index 000000000..50b9485a1 --- /dev/null +++ b/packages/compiler/src/backend/llvm/receivers.ts @@ -0,0 +1,409 @@ +/* LlEmitter's receivers: receiver reads, and the analyses that decide when calls, receivers and returns borrow. */ +import type { LlEmitter } from "./emitter.js"; +import { + type IrExpr, + type IrFunction, + type IrLocal, + type IrType, + isRefCounted, +} from "../../ir/ir.js"; +import type { LlValue } from "./expr-context.js"; +import { guardedValue } from "./borrowed-receivers.js"; +import { InternalCompilerError } from "../../errors.js"; +import { emitBorrowedArrayRead, emitProjectedArrayRead } from "./local-array-reads.js"; +import { emitStackMapRead, matchMapRead } from "./map-read-lifetimes.js"; +import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js"; +import { emitCheckedNarrowReceiver } from "./checked-narrows.js"; +import { canStackUnion, emitStackUnion } from "./stack-unions.js"; +import { emitDowncastGuard, emitUnionNarrowGuard } from "./narrow-guards.js"; +import { emitScalarOptional, isScalarOptionalSource } from "./local-union-storage.js"; +import { borrowableInputs } from "./borrowed-inputs.js"; +import { type WalkBorrowHost, analyzeWalks } from "./walk-borrows.js"; + +/** Read a receiver whose value is consumed immediately by a field or tag + * load. No user code may run between this read and its consumer. Locals + * and their projections already have an owner; other expressions keep + * their ordinary statement-frame ownership. The caller must retain any + * reference result before evaluating another expression. */ +export function emitReadReceiver(emitter: LlEmitter, e: IrExpr): LlValue { + if (!isRefCounted(e.type)) return emitter.emitExpr(e); + if (e.kind === "strLit") return { name: emitter.internLiteral(e.value), type: e.type }; + const guarded = guardedValue(e); + if (guarded !== null) { + // Throwing guards run in place, exactly as the sequence emits them. + for (const s of (e as IrExpr & { kind: "seqExpr" }).stmts) emitter.emitStmt(s); + return emitter.emitReadReceiver(guarded); + } + if (e.kind === "varRef") { + emitter.materializeSplitLocal(e.localId); + const binding = emitter.binding(e.localId); + if (binding.kind === "boxed") { + // A projection consumes this pointer before any user code runs. + // Keep TDZ checks and caught-value conversion on the owned path. + if (!emitter.canBorrowReceiver(e)) return emitter.emitExpr(e); + const box = emitter.loadBox(binding.slot); + const payload = emitter.B.tmp(); + const value = emitter.B.tmp(); + emitter.B.line(`${payload} = getelementptr inbounds %ScrBox, ptr ${box}, i32 0, i32 5`); + emitter.B.line(`${value} = load ptr, ptr ${payload}`); + return { name: value, type: e.type }; + } + if (binding.kind === "global") emitter.checkGlobalTdz(e.localId); + const value = emitter.B.tmp(); + emitter.B.line(`${value} = load ptr, ptr ${binding.slot}${emitter.slotAlias(e.localId)}`); + return { name: value, type: e.type }; + } + if (e.kind === "dynCheck" && e.exact) return emitter.emitExactCapsuleObject(e.value, e.type); + if (e.kind === "chainRecv") { + // The chain's bind slot holds its receiver unchanged until the body + // finishes, owned by the slot itself or by the chain's receiver. + const bound = emitter.chainSlots.get(e.id); + if (!bound) + throw new InternalCompilerError(`llvm emitter bug: chainRecv "${e.id}" outside its chain`); + const value = emitter.B.tmp(); + emitter.B.line(`${value} = load ${emitter.llType(bound.type)}, ptr ${bound.name}`); + return { name: value, type: e.type }; + } + if (e.kind === "arrayGet" && emitter.canBorrowReceiver(e)) { + // The array keeps the element alive until the consumer; a missing + // element still traps exactly like the owned read. + const slot = emitter.B.slot(); + emitter.B.entryAllocas.push(`${slot} = alloca ptr`); + emitBorrowedArrayRead(emitter, e, slot); + const value = emitter.B.tmp(); + emitter.B.line(`${value} = load ptr, ptr ${slot}`); + return { name: value, type: e.type }; + } + const wrap = emitter.borrowableNullableWrap(e); + if (wrap === "unit" && e.kind === "unionWrap") + return { name: emitter.unitInstanceRef(e.unionId, e.tag), type: e.type }; + if (wrap === "ref" && e.kind === "unionWrap") + return { name: emitter.emitReadReceiver(e.value).name, type: e.type }; + const read = matchMapRead(e, emitter.boxedUnionsById); + if (read) { + const result = emitStackMapRead(emitter, read); + if (result.owner) emitter.ownSlot(result.owner.slot, result.owner.type); + return result.value; + } + // A borrowed-return helper (such as an optional element read) lends its + // result like any borrowed-return call. + if (e.kind === "call" && emitter.borrowedReturns.has(e.callee) && emitter.canBorrowReceiver(e)) + return emitBorrowedResultCall(emitter, e); + const arrayRead = emitter.optionalArrayReads.get(e); + if (arrayRead) return emitProjectedArrayRead(emitter, arrayRead); + const narrow = emitter.checkedNarrows.get(e); + const narrowed = narrow ? emitCheckedNarrowReceiver(emitter, narrow) : null; + if (narrowed) return narrowed; + if (canStackUnion(e, emitter.boxedUnionsById)) return emitStackUnion(emitter, e).value; + if (e.kind === "unionNarrow") { + const union = emitter.emitUnionProjection(e.value); + emitUnionNarrowGuard(emitter, union.name, e.unionId, e.tag); + return { name: emitter.unionPeek(union.name, e.unionId), type: e.type }; + } + if ((e.kind === "downcast" || e.kind === "upcast") && e.value.type.kind === "object") { + // Prefix layouts: a class cast reads the operand's own pointer; a + // downcast verifies its target class (narrow-guards.ts). + const receiver = emitter.emitReadReceiver(e.value); + if (e.kind === "downcast" && e.type.kind === "object") + emitDowncastGuard(emitter, receiver.name, e.value.type.className, e.type.className); + return { name: receiver.name, type: e.type }; + } + // A nullable-pointer field has no box to borrow. Projections use + // emitUnionProjection; other borrowers get an owned heap box. + if (emitter.nullableFieldGet(e)) return emitter.emitExpr(e); + if (e.kind === "recordGet" || e.kind === "fieldGet") { + const receiver = emitter.emitReadReceiver(e.obj); + if (e.kind === "recordGet") { + // An inline union field answers a private stack box. + const { ptr, type, inline } = emitter.recordFieldPtr(receiver.name, e.shapeId, e.field); + return { name: emitter.loadRecordField(ptr, type, inline), type: e.type }; + } + const { ptr, type, inline } = emitter.classFieldPtr(receiver.name, e.className, e.field); + if (emitter.int32Slots.isField(e.className, e.field)) + return emitter.loadInt32Field(ptr, e.type); + return { name: emitter.loadField(ptr, type, inline), type: e.type }; + } + if ( + e.kind === "ternary" && + emitter.canBorrowReceiver(e.then) && + emitter.canBorrowReceiver(e.else_) + ) { + // Checked projections lower to a ternary with an always-throwing + // arm. Keep that check at the use, but borrow the successful arm. + const B = emitter.B; + const cond = emitter.emitExpr(e.cond); + const slot = B.slot(); + B.entryAllocas.push(`${slot} = alloca ptr`); + const yes = B.newLabel("read.then"), + no = B.newLabel("read.else"), + join = B.newLabel("read.join"); + B.condBr(cond.name, yes, no); + for (const [label, value] of [ + [yes, e.then], + [no, e.else_], + ] as const) { + B.startBlock(label); + emitter.frames.push([]); + const result = emitter.emitReadReceiver(value); + B.line(`store ptr ${result.name}, ptr ${slot}`); + emitter.releaseTemps(emitter.frames.pop()!); + B.br(join); + } + B.startBlock(join); + const value = B.tmp(); + B.line(`${value} = load ptr, ptr ${slot}`); + return { name: value, type: e.type }; + } + // Scalar optional values and register results answer a private stack + // box. A ternary of borrowable arms above keeps lending its arm. + if ( + (e.kind === "optChain" || + e.kind === "nullish" || + e.kind === "ternary" || + e.kind === "arrIntrinsic") && + isScalarOptionalSource(e, emitter.unionSources) + ) + return emitScalarOptional(emitter, e); + if (e.kind === "call" && emitter.scalarResultCall(e)) + return emitCallExpr(emitter, e, false, true); + if (e.kind === "call" && emitter.canBorrowReceiver(e)) return emitBorrowedResultCall(emitter, e); + return emitter.emitExpr(e); +} + +/** A local stable binding keeps its value alive while later arguments + * and the callee execute. A proven local union box cannot escape or be + * rebound during a call; its independent payload owner keeps it alive. + * Other writable bindings need an owned snapshot. */ +export function canBorrowCallArgument(emitter: LlEmitter, value: IrExpr): boolean { + if (value.kind === "strLit") return true; + // An exact capsule owns its instance while the capsule's binding holds it. + if (value.kind === "dynCheck" && value.exact) + return value.value.kind === "varRef" && emitter.canBorrowCallArgument(value.value); + // An inline union field has no box to lend. + if (emitter.inlineFieldGet(value)) return false; + // Class casts reinterpret the same pointer (prefix layouts). + if ((value.kind === "upcast" || value.kind === "downcast") && value.value.type.kind === "object") + return emitter.canBorrowCallArgument(value.value); + if (value.kind === "seqExpr") { + const guarded = guardedValue(value); + return guarded !== null && emitter.canBorrowCallArgument(guarded); + } + // A nullable-union wrap reinterprets its payload pointer; an immortal + // unit constant needs no owner at all. + const wrap = emitter.borrowableNullableWrap(value); + if (wrap === "unit") return true; + if (wrap === "ref" && value.kind === "unionWrap") + return emitter.canBorrowCallArgument(value.value); + // Checked casts (`x as C`, `x!` on a nullable) lower to a ternary whose + // failing arm always throws; the successful arm projects the same + // stable owner. The throwing arm's typed dummy owns nothing. + if (value.kind === "unionNarrow") return emitter.canBorrowCallArgument(value.value); + if (value.kind === "libCall") return value.fn === "error.nodeThrow"; + // A final field never changes outside constructors: while its object is + // borrowable, so is the field's value. Nullable-pointer fields have no + // box to lend and keep their owning path. + if ( + value.kind === "fieldGet" && + emitter.finalFieldBorrows && + isRefCounted(value.type) && + emitter.finalFields.isFinal(value.className, value.field) && + !emitter.nullableFieldGet(value) + ) + return emitter.canBorrowReceiver(value.obj) && emitter.canBorrowCallArgument(value.obj); + if (value.kind === "ternary") + return ( + emitter.canBorrowReceiver(value.then) && + emitter.canBorrowReceiver(value.else_) && + emitter.canBorrowCallArgument(value.then) && + emitter.canBorrowCallArgument(value.else_) + ); + // A construction-only field of a stable owner keeps its value until the + // owner dies: no later operand or callee can replace and release it. + if ((value.kind === "fieldGet" || value.kind === "recordGet") && emitter.immutableRead(value)) + return emitter.canBorrowCallArgument(value.obj); + // An optional chain's bound receiver stays unchanged until its body + // finishes (see emitReadReceiver). + if (value.kind === "chainRecv") return emitter.chainSlots.has(value.id); + if (value.kind !== "varRef") return false; + const binding = emitter.binding(value.localId); + // A module constant owns its value for the rest of the program. + if (binding.kind === "global") return emitter.stableGlobals.has(value.localId); + // So does the environment for a capture nothing ever rebinds. + if (binding.kind === "boxed") + return emitter.stableCaptureIds.has(value.localId) && emitter.canBorrowReceiver(value); + return ( + binding.kind === "local" && + binding.local !== undefined && + (!binding.local.mutable || + emitter.stableCallBindings.has(value.localId) || + emitter.borrowedParameters.has(value.localId) || + emitter.localUnionStorage.has(value.localId)) && + !binding.local.boxed && + !binding.local.tdz + ); +} + +export function canBorrowReceiver(emitter: LlEmitter, e: IrExpr): boolean { + switch (e.kind) { + case "chainRecv": + return emitter.chainSlots.has(e.id); + case "varRef": { + const binding = emitter.binding(e.localId); + return ( + binding.kind !== "boxed" || + (!binding.local!.tdz && + binding.type.kind === e.type.kind && + e.type.kind !== "caught" && + isRefCounted(e.type)) + ); + } + case "unionNarrow": + // Class casts reinterpret the same pointer (prefix layouts). + case "downcast": + case "upcast": + return emitter.canBorrowReceiver(e.value); + // An inline union field has no box to lend: projections read it + // through emitReadReceiver's private stack box. + case "fieldGet": + case "recordGet": + return !emitter.inlineFieldGet(e) && emitter.canBorrowReceiver(e.obj); + case "ternary": + return emitter.canBorrowReceiver(e.then) && emitter.canBorrowReceiver(e.else_); + case "seqExpr": { + const guarded = guardedValue(e); + return guarded !== null && emitter.canBorrowReceiver(guarded); + } + // The runtime always sets a pending exception; its unreachable + // typed dummy is null and owns no receiver. + case "libCall": + return e.fn === "error.nodeThrow"; + case "unionWrap": { + const wrap = emitter.borrowableNullableWrap(e); + return wrap === "unit" || (wrap === "ref" && emitter.canBorrowReceiver(e.value)); + } + // A borrowed-return result stays reachable from its borrowed + // arguments until the consumer runs. Each argument is lent without a + // snapshot, so neither the callee nor a later argument may replace + // what it was read from: a callee that preserves every edge cannot, + // and any other one must leave the argument's locations unwritten. + case "call": { + if (!emitter.borrowedReturns.has(e.callee)) return false; + if (!e.args.every((arg) => emitter.referenceEffects.preserves(arg))) return false; + const preserving = emitter.referenceEffects.functions.has(e.callee); + if (preserving) + return e.args.every((arg) => !isRefCounted(arg.type) || emitter.canBorrowReceiver(arg)); + if (emitter.referenceWrites === null) return false; + const lent = borrowableInputs( + emitter, + e.args, + false, + emitter.referenceWrites.summary(e.callee), + ); + return e.args.every((arg, i) => !isRefCounted(arg.type) || lent[i] === true); + } + // A required element read: the (borrowed) array owns the element + // while an edge-preserving index computes. + case "arrayGet": + return ( + e.arr.type.kind === "array" && + isRefCounted(e.arr.type.elem) && + emitter.canBorrowReceiver(e.arr) && + emitter.referenceEffects.preserves(e.index) + ); + default: + return false; + } +} + +/** Functions returning a walk of their borrowed parameters return it + * without a reference (+0) from their borrowing body; direct callers + * either consume it as a receiver/walk or retain it at once, and the owned + * adapter retains before releasing its parameters. Candidates preserve + * every heap edge (so the result stays reachable from the arguments), + * borrow every reference parameter and return plain pointers. The set + * grows to a fixpoint because a call to a borrowed-return function with + * walk arguments is itself a walk; walk facts are then recomputed with + * the final set, admitting only parameters whose convention borrows. */ +export function analyzeBorrowedReturns(emitter: LlEmitter): void { + const pointer = (type: IrType): boolean => emitter.plainReference(type); + const borrowedIds = (fn: IrFunction): Set => { + const indexes = emitter.callLifetimes.borrowed.get(fn.name); + return new Set(fn.params.filter((_, i) => indexes?.has(i)).map((p) => p.localId)); + }; + const hostFor = (fn: IrFunction): WalkBorrowHost => { + const allowed = borrowedIds(fn); + const host = { + pointerLocal: (local: IrLocal) => pointer(local.type), + borrowsWithoutOwning: (e: IrExpr) => emitter.borrowsWithoutOwning(e), + borrowedReturn: (callee: string) => emitter.borrowedReturns.has(callee), + parameterAllowed: (id: string) => allowed.has(id), + }; + return emitter.referenceEffects.functions.has(fn.name) + ? host + : emitter.unwrittenWalkHost(fn, host); + }; + // With ReferenceWrites, a body that writes none of its walks' locations + // qualifies like one that preserves every edge. + const preserving = [...emitter.fnByName.values()].filter( + (fn) => emitter.referenceEffects.functions.has(fn.name) || emitter.referenceWrites !== null, + ); + const candidates = preserving.filter((fn) => { + if (!pointer(fn.returnType) || fn.async || fn.generator || fn.captures) return false; + // A borrowing vtable slot dispatches to the borrowing body (or its + // virtual adapter) directly, and virtual callers own the result. + if (emitter.implSlotBorrowed(fn.name).size > 0) return false; + const indexes = emitter.callLifetimes.borrowed.get(fn.name); + return ( + indexes !== undefined && + indexes.size > 0 && + fn.params.every((p, i) => !isRefCounted(p.type) || indexes.has(i)) + ); + }); + for (let changed = true; changed;) { + changed = false; + for (const fn of candidates) { + if (emitter.borrowedReturns.has(fn.name)) continue; + if (!analyzeWalks(fn, hostFor(fn)).returnsWalk) continue; + emitter.borrowedReturns.add(fn.name); + // Nested borrowed returns extend the locations a caller depends on. + emitter.borrowedReturnKeyCache.clear(); + changed = true; + } + } + if (emitter.borrowedReturns.size === 0) return; + for (const fn of preserving) { + const walks = analyzeWalks(fn, hostFor(fn)).locals; + if (walks.size > 0) emitter.walkBorrowsByFunction.set(fn.name, walks); + } +} + +export function fieldAliasMetadata(emitter: LlEmitter): string[] { + if (emitter.fieldAliasTags.size === 0) return []; + const lines = ['!0 = !{!"scriptc native fields"}']; + for (const tag of emitter.fieldAliasTags.values()) { + lines.push( + `!${tag - 1} = !{!"field.${tag}", !0, i64 0}`, + `!${tag} = !{!${tag - 1}, !${tag - 1}, i64 0}`, + ); + } + return lines; +} + +/** A private stack union source with an independently owned payload. */ +export function canStackReceiver(emitter: LlEmitter, e: IrExpr): boolean { + return emitter.isStackUnionSource(e); +} + +/** True for union sources that emitReadReceiver can produce as a private + * stack box with an independently owned payload (no heap union). */ +export function isStackUnionSource(emitter: LlEmitter, e: IrExpr): boolean { + if (e.type.kind !== "union") return false; + return ( + canStackUnion(e, emitter.boxedUnionsById) || + matchMapRead(e, emitter.boxedUnionsById) !== null || + emitter.optionalArrayReads.get(e) !== null || + isScalarOptionalSource(e, emitter.unionSources) || + emitter.scalarResultCall(e) || + emitter.inlineFieldGet(e) + ); +} diff --git a/packages/compiler/src/backend/llvm/wasm-modules.ts b/packages/compiler/src/backend/llvm/wasm-modules.ts new file mode 100644 index 000000000..8a7045687 --- /dev/null +++ b/packages/compiler/src/backend/llvm/wasm-modules.ts @@ -0,0 +1,77 @@ +/* The WebAssembly modules translated into a program (IrModule.wasmModules): + * each module's translation (wasm/translate.ts) and the registry the + * runtime looks modules up in (scr_wasm_registry, scr_wasm_abi.h). The + * translations join the program module, so the native helper optimizes, + * partitions and caches them with it. Programs that do not use the + * WebAssembly API carry no wasmModules and emit nothing here. */ +import type { IrModule } from "../../ir/ir.js"; +import { decodeBase64 } from "../../wasm/base64.js"; +import { translateWasmModule, WasmTranslateError } from "../../wasm/translate.js"; + +function hexBytes(hex: string): Uint8Array { + const out = new Uint8Array(hex.length / 2); + for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(2 * i, 2 * i + 2), 16); + return out; +} + +/** Whether translated memories check bounds explicitly instead of relying + * on guard pages and the runtime's fault handler: sanitizer builds (whose + * runtime owns SIGSEGV), 32-bit targets, and Windows, which has no + * fault handler yet. */ +export function wasmBoundsChecks(options: { + sanitize: boolean; + pointerBits: 32 | 64; + targetTriple: string; +}): boolean { + if (options.sanitize || options.pointerBits === 32) return true; + const target = options.targetTriple || process.platform; + return /windows|mingw|cygwin|win32|msvc/.test(target); +} + +/** The top-level lines of every translated module plus the registry. + * `declared` holds the declarations the module already carries; runtime + * entry points it lacks are declared here. A module the translator + * cannot translate is left out of the registry, so loading it at run + * time rejects with the CompileError for untranslated bytes. */ +export function emitWasmModules( + mod: IrModule, + declared: ReadonlySet, + boundsChecks: boolean, +): string[] { + const modules = mod.wasmModules; + if (modules === undefined) return []; + const out: string[] = []; + const decls = new Set(declared); + const descriptors: string[] = []; + for (const module of modules) { + const bytes = decodeBase64(module.bytes); + if (bytes === null) continue; + let translation; + try { + translation = translateWasmModule(bytes, hexBytes(module.sha256), { + boundsChecks, + customSections: mod.wasmCustomSections === true, + }); + } catch (error) { + if (error instanceof WasmTranslateError) continue; + throw error; + } + for (const decl of translation.decls) { + if (decls.has(decl)) continue; + decls.add(decl); + out.push(decl); + } + for (const part of translation.text) out.push(part); + descriptors.push(translation.descriptor); + } + if (descriptors.length === 0) { + out.push(`@scr_wasm_registry = constant { i64, ptr } { i64 0, ptr null }`); + } else { + out.push( + `@sc_wasm_registry_modules = private constant [${descriptors.length} x ptr] [${descriptors.map((d) => `ptr ${d}`).join(", ")}]`, + `@scr_wasm_registry = constant { i64, ptr } { i64 ${descriptors.length}, ptr @sc_wasm_registry_modules }`, + ); + } + out.push(""); + return out; +} diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index 86146259d..84f448e93 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -25,6 +25,9 @@ export interface NativeLinkFeatures { fileHandle: boolean; fetch: boolean; eventTarget?: boolean; + /** The WebAssembly API (scr_wasm.c). Absent when unused, so programs + * without it keep their link identity. */ + wasm?: boolean; /** The program assigns process.emitWarning: links the * SCR_WARNING_REPLACE variant of the dyn-async unit. */ warningReplace?: boolean; @@ -40,18 +43,28 @@ export interface NativeLinkFeatures { /** Dynamic-tier jsval operations: the scr_js_* units. Absent (not false) * for programs without them, so their link features stay unchanged. */ dynTier?: boolean; + /** Checked-dynamic stream slots: the scr_stream_dyn.c unit. Absent (not + * false) for programs without them. */ + streamDyn?: boolean; + /** events.once with options: scr_events_once_signal.c. Absent (not + * false) for programs without it. */ + eventsOnceSignal?: boolean; events: boolean; emitter: boolean; symbol: boolean; bigint: boolean; searchParams: boolean; qs: boolean; + dynBuiltinClass: boolean; + dynBuiltinStream: boolean; parseArgs: boolean; stream: boolean; net: boolean; http: boolean; http2: boolean; dgram: boolean; + /** node:dns/promises: scr_dns_promises.c. */ + dnsPromises: boolean; watch: boolean; foreignFfi: boolean; nodeTest: boolean; @@ -59,6 +72,8 @@ export interface NativeLinkFeatures { /** crypto.createCipheriv/createDecipheriv: scr_cipher.c plus the mbedTLS * archive. */ cipher: boolean; + /** perf_hooks event-loop metrics: scr_perf_loop.c. */ + perfLoop: boolean; tlsCa: boolean; /** crypto.subtle importKey/sign/verify/digest and CryptoKey values: * scr_webcrypto.c. */ diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index 33b13960e..a9c93d3df 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -113,11 +113,19 @@ export interface CcOptions { * the IR): compiles the scr_js_* units (two-tier design I3). Programs * without them link no scr_js_ symbol. */ dynTier?: boolean; + /** Checked-dynamic stream slots (stream.dynNative / stream.pipeDyn): + * compiles scr_stream_dyn.c. */ + streamDyn?: boolean; + /** events.once with options (events.onceSignal): compiles + * scr_events_once_signal.c. */ + eventsOnceSignal?: boolean; /** The program constructs EventTarget/Event/CustomEvent values or * calls events.addAbortListener (the event.* libCalls), or uses the * native fetch AbortSignal built on the same engine: compiles * scr_event_target.c. */ eventTarget?: boolean; + /** The WebAssembly API: compiles scr_wasm.c. */ + wasm?: boolean; /** SCR_WARNING_REPLACE: process.emitWarning replacement support. */ warningReplace?: boolean; /** SCR_STDIO_EXTENDED: stdin encoding, string data listeners, resize. */ @@ -150,6 +158,15 @@ export interface CcOptions { * line (escape-only programs ride the always-linked component encoder * and never flip this). */ qs?: boolean; + /** The program uses EventEmitter or a stream class as an untyped + * constructor value (a constructor function's base): compiles + * scr_dyn_builtin_class.c, which also references the inspection, + * dyn-invoke and symbol units. */ + dynBuiltinClass?: boolean; + /** The program uses a stream class as an untyped constructor value: + * compiles scr_dyn_builtin_stream.c, the constructors' stream half, which + * also references the stream unit. */ + dynBuiltinStream?: boolean; /** The program uses native util parsing, comparison, or styling. Optional * utility units also require their symbol, inspection, and warning features. */ parseArgs?: boolean; @@ -177,6 +194,11 @@ export interface CcOptions { * on the IR): compiles scr_dgram.c into the binary — the net gating * precedent, so dgram-free binaries keep their exact link line. */ dgram?: boolean; + /** The program uses node:dns/promises (the dns.promisesLookup libCalls): + * compiles scr_dns_promises.c over the dgram unit, so callback-only + * binaries keep their exact link line. */ + dnsPromises?: boolean; + /** The program uses fs.watch (moduleUsesFsWatch on the IR): compiles * scr_watch.c into the binary — the net gating precedent, so watch-free * binaries keep their exact link line. */ @@ -209,6 +231,8 @@ export interface CcOptions { cipher?: boolean; /** crypto.subtle importKey/sign/verify/digest: compiles scr_webcrypto.c. */ webcrypto?: boolean; + /** perf_hooks event-loop metrics: compiles scr_perf_loop.c. */ + perfLoop?: boolean; /** Internal compiler hook: called only after a strict native artifact hit * or a successful stable build has installed `outPath`. The executable * frontend cache uses it to publish its stamp after native dependencies diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index e632795c9..949f03839 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -98,6 +98,247 @@ import { CacheInputsChangedError } from "./session.js"; import { ensureRuntimeObjects, stageRuntimeObjects, clearCcCaches } from "./runtime-objects.js"; import { localizeLibraryObjects } from "./object-merge.js"; +/** What the runtime-unit table reads: the build options, the target driver, + * the units other options imply, and the vendor inputs resolved for this + * build. */ +interface RuntimeUnitContext { + opts: CcOptions; + driver: ReturnType; + rt: (path: string) => string; + rtDir: string; + regex: boolean; + nativeFetch: boolean; + net: boolean; + http: boolean; + tls: boolean; + tlsCa: boolean; + cipher: boolean; + lreObjects: readonly string[]; + zlibObjects: readonly string[]; + tlsArchive: string | null; +} + +/** An optional runtime input: runtime sources compiled when `when` holds, + * or command-line arguments computed from the build. */ +interface RuntimeUnit { + when: (u: RuntimeUnitContext) => boolean | undefined; + sources: readonly string[]; + args?: (u: RuntimeUnitContext) => readonly string[]; +} + +const unit = (when: RuntimeUnit["when"], ...sources: string[]): RuntimeUnit => ({ when, sources }); +const unitArgs = (args: (u: RuntimeUnitContext) => readonly string[]): RuntimeUnit => ({ + when: () => true, + sources: [], + args, +}); + +/** The optional runtime inputs, in link order. The order is part of the + * executable: a new unit goes next to the units it references. */ +const RUNTIME_UNITS: readonly RuntimeUnit[] = [ + unit( + (u) => u.opts.workers, + "scr_worker.c", + "scr_worker_events.c", + "scr_mailbox.c", + "scr_message.c", + ), + unit((u) => u.opts.copying, "scr_copying.c"), + unit((u) => u.opts.fileHandle, "scr_file_handle.c"), + // win32 targets compile the libc-shim TU (stpcpy, arc4random_buf, + // gmtime_r, strcasestr — the _WIN32 block in scr_runtime.h declares + // them) and link advapi32 (the CSPRNG RtlGenRandom/SystemFunction036, + // GetUserNameA), iphlpapi (GetAdaptersAddresses behind + // os.networkInterfaces), and ws2_32 (inet_ntop there; the socket + // units ride the same import). Never present on the default path, so + // the historical line cannot change. + unitArgs((u) => + targetPlatform(u.driver) === "win32" + ? [u.rt(join(u.rtDir, "scr_win.c")), "-ladvapi32", "-liphlpapi", "-lws2_32"] + : [], + ), + // musl deliberately has no libc-identification predefine; resolveCc's + // SCR_MUSL flag and this target-selected TU travel together. + unit((u) => isMuslTarget(u.driver), "scr_musl.c"), + // scr_glob.c (path.matchesGlob) compiles its segments through the + // regex unit and scr_idna.c (node:url's IDNA) normalizes through it, + // so both ride the same switch; section GC drops them from binaries + // that never use them. + unitArgs((u) => + u.regex + ? [ + "-I", + vendorEngineDir(), + u.rt(join(u.rtDir, "scr_regex.c")), + u.rt(join(u.rtDir, "scr_glob.c")), + u.rt(join(u.rtDir, "scr_idna.c")), + ...u.lreObjects, + ] + : [], + ), + unit((u) => u.opts.assert || u.regex || u.opts.symbol, "scr_assert.c"), + unit((u) => u.opts.inspect, "scr_inspect.c", "scr_console_native.c"), + unit((u) => u.opts.dynInvoke || u.opts.workers || u.nativeFetch, "scr_dyn_invoke.c"), + unit((u) => u.opts.dc, "scr_dc.c"), + unit((u) => u.opts.dynTier, "scr_js_value.c"), + unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"), + unit((u) => u.opts.wasm, "scr_wasm.c"), + unit( + (u) => + u.opts.dynAsync || + u.opts.dynInvoke || + u.opts.workers || + u.opts.dc || + u.opts.fileHandle || + u.nativeFetch, + "scr_async_dyn.c", + ), + // The performance timeline (every perf_hooks user-timing call selects + // dynAsync, whose unit carries the timeline's buffer warning). + unit((u) => u.opts.dynAsync, "scr_perf.c"), + // perf_hooks event-loop metrics, only for programs that read them. + unit((u) => u.opts.perfLoop, "scr_perf_loop.c"), + // Blob and File (every blob.* call selects dynAsync, whose unit boxes + // their reads as dyn promises). + unit((u) => u.opts.dynAsync, "scr_blob.c"), + // The zlib UNIT (scr_zlib.c) gates on zlib.* IR use; the LINK (system + // libz on the default host-clang build, vendored objects on Zig builds) + // also serves the native fetch's gzip decoder — spread exactly once. + unitArgs((u) => + u.opts.zlib + ? isZigDriver(u.driver) + ? ["-I", vendorZlibDir(), u.rt(join(u.rtDir, "scr_zlib.c")), ...u.zlibObjects] + : [u.rt(join(u.rtDir, "scr_zlib.c"))] + : u.nativeFetch + ? isZigDriver(u.driver) + ? ["-I", vendorZlibDir(), ...u.zlibObjects] + : [] + : [], + ), + unit((u) => u.opts.events, "scr_events.c", "scr_readline.c"), + // readline's terminal mode: rl.terminal sets the regex unit it + // measures with. + unit((u) => u.opts.events && u.regex, "scr_readline_tty.c"), + unit((u) => u.opts.emitter || u.opts.workers, "scr_events_emitter.c"), + // events.once with options registers through the emitter and + // event-target units. + unit((u) => u.opts.eventsOnceSignal === true, "scr_events_once_signal.c"), + // The checked-dynamic HANDLE support unit (listener gate + runtime + // adapter closures): every referencing unit is one of the emitter or + // net families (http implies net), so handle-free binaries keep + // their exact size class. + unit((u) => u.opts.emitter || u.opts.workers || u.net, "scr_dyn_handle.c"), + unit((u) => u.opts.symbol, "scr_symbol.c"), + unit((u) => u.opts.assert && u.opts.bigint, "scr_bigint_assert.c"), + unit((u) => u.opts.qs, "scr_qs.c"), + // The legacy url unit references the regex, querystring and dyn-async + // units; the url.parse family's IR sets all three. + unit((u) => u.regex && u.opts.qs && u.opts.dynAsync, "scr_url_legacy.c"), + unit((u) => u.opts.parseArgs, "scr_util.c"), + unit((u) => u.opts.parseArgs && u.opts.symbol, "scr_util_compare.c"), + unit((u) => u.opts.parseArgs && u.opts.inspect && u.opts.dynAsync, "scr_util_style.c"), + unit( + (u) => u.opts.parseArgs && u.opts.inspect && u.opts.dynAsync && u.opts.dynInvoke && u.regex, + "scr_util_callable.c", + ), + unit( + (u) => + u.opts.dynBuiltinClass && + u.opts.inspect && + u.opts.dynInvoke && + u.opts.symbol && + (u.opts.emitter || u.opts.workers), + "scr_dyn_builtin_class.c", + ), + unit( + (u) => + u.opts.dynBuiltinStream && + u.opts.dynBuiltinClass && + u.opts.stream && + u.opts.inspect && + u.opts.dynInvoke && + u.opts.symbol, + "scr_dyn_builtin_stream.c", + ), + unit((u) => u.opts.stream, "scr_stream.c", "scr_fs_stream.c"), + // The zlib stream classes reference both the zlib engine and the + // stream unit. + unit((u) => u.opts.zlib && u.opts.stream, "scr_zlib_stream.c"), + unit((u) => u.opts.stream && u.opts.dynAsync, "scr_stream_ops.c"), + // Checked-dynamic stream slots test values and call JavaScript stream + // members through the dyn-invoke unit. + unit((u) => u.opts.streamDyn === true, "scr_stream_dyn.c"), + // FileHandle streams reference both the stream unit and FileHandle. + unit((u) => u.opts.stream && u.opts.fileHandle, "scr_fs_stream_fh.c"), + // The readiness-poller backends (scr_platform.h): kqueue on macOS/BSD, + // epoll on Linux, WSAPoll on Windows — each TU is empty off its + // platform, so all three link whenever a poller-using unit does and + // the others cost nothing (ws2_32 rides the unconditional win32 libs + // above). + unit((u) => u.net || u.opts.dgram, "scr_loop_kqueue.c", "scr_loop_epoll.c", "scr_loop_wsapoll.c"), + unit((u) => u.net, "scr_net.c"), + unit((u) => u.http, "scr_http.c"), + unit((u) => u.opts.http2, "scr_http2.c"), + unit((u) => u.opts.dgram, "scr_dgram.c"), + unit((u) => u.opts.dgram && u.opts.dnsPromises, "scr_dns_promises.c"), + unit((u) => u.opts.watch, "scr_watch.c"), + unit((u) => u.opts.foreignFfi, "scr_ffi_queue.c"), + unit((u) => u.opts.nodeTest, "scr_test.c"), + unit((u) => u.opts.webcrypto, "scr_webcrypto.c"), + // The CA-store unit rides its own gate OR the tls one: scr_tls.c + // references its default-set override unconditionally. + unit((u) => u.tlsCa, "scr_tls_ca.c"), + unitArgs((u) => + u.tlsArchive + ? [ + "-I", + join(vendorTlsDir(), "include"), + ...(u.tls ? [u.rt(join(u.rtDir, "scr_tls.c"))] : []), + ...(u.cipher ? [u.rt(join(u.rtDir, "scr_cipher.c"))] : []), + u.tlsArchive, + // mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h). + // The unconditional win32 libs above do not carry it. + // Never present on the default path, so the historical TLS + // link line cannot change. + ...(targetPlatform(u.driver) === "win32" ? ["-lbcrypt"] : []), + ] + : [], + ), + // scr_tls_ca.c enumerates and PEM-encodes Windows system-store entries. + // This is independent of the mbedTLS archive: getCACertificates-only + // programs need crypt32 too, while TLS programs imply the CA unit. + unitArgs((u) => (u.tlsCa && targetPlatform(u.driver) === "win32" ? ["-lcrypt32"] : [])), + // Fetch uses the native network, TLS, and HTTP runtime. + unit((u) => u.nativeFetch, "scr_fetch.c"), +]; + +/** The arguments of the optional runtime inputs, in link order. */ +function runtimeUnitArgs(u: RuntimeUnitContext): string[] { + return RUNTIME_UNITS.flatMap((entry) => + entry.args + ? [...entry.args(u)] + : entry.when(u) + ? entry.sources.map((source) => u.rt(join(u.rtDir, source))) + : [], + ); +} + +/** The runtime's optional compile-time definitions, in command-line order. + * Both the program build and the runtime-object flags use this list. */ +const RUNTIME_DEFINES: readonly { + define: string; + when: (opts: CcOptions) => boolean | undefined; +}[] = [ + { define: "SCR_TEXT_DECODER_LEGACY", when: (opts) => opts.textDecoderLegacy }, + { define: "SCR_WARNING_REPLACE", when: (opts) => opts.warningReplace }, + { define: "SCR_STDIO_EXTENDED", when: (opts) => opts.stdioExtended }, + { define: "SCR_YIN", when: (opts) => opts.youngNursery }, +]; + +function runtimeDefineArgs(opts: CcOptions): string[] { + return RUNTIME_DEFINES.filter((d) => d.when(opts)).map((d) => `-D${d.define}`); +} + export async function compileCInternal( opts: CcOptions, cacheWarmOnly: boolean, @@ -444,10 +685,7 @@ export async function compileCInternal( ? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"] : [optimization === "dev" ? "-O0" : "-O2"]), ...executableSectionFlags.compile, - ...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []), - ...(opts.warningReplace ? ["-DSCR_WARNING_REPLACE"] : []), - ...(opts.stdioExtended ? ["-DSCR_STDIO_EXTENDED"] : []), - ...(opts.youngNursery ? ["-DSCR_YIN"] : []), + ...runtimeDefineArgs(opts), "-fno-math-errno", // The runtime object model uses type-punned C: a hierarchy // upcast is a raw pointer cast, so one object's header (rc, vt) and @@ -465,146 +703,22 @@ export async function compileCInternal( "-I", rtDir, ...runtimeSources.map((f) => rt(join(rtDir, f))), - ...(opts.workers - ? ["scr_worker.c", "scr_worker_events.c", "scr_mailbox.c", "scr_message.c"].map((f) => - rt(join(rtDir, f)), - ) - : []), - ...(opts.copying ? [rt(join(rtDir, "scr_copying.c"))] : []), - ...(opts.fileHandle ? [rt(join(rtDir, "scr_file_handle.c"))] : []), - // win32 targets compile the libc-shim TU (stpcpy, arc4random_buf, - // gmtime_r, strcasestr — the _WIN32 block in scr_runtime.h declares - // them) and link advapi32 (the CSPRNG RtlGenRandom/SystemFunction036, - // GetUserNameA), iphlpapi (GetAdaptersAddresses behind - // os.networkInterfaces), and ws2_32 (inet_ntop there; the socket - // units ride the same import). Never present on the default path, so - // the historical line cannot change. - ...(targetPlatform(driver) === "win32" - ? [rt(join(rtDir, "scr_win.c")), "-ladvapi32", "-liphlpapi", "-lws2_32"] - : []), - // musl deliberately has no libc-identification predefine; resolveCc's - // SCR_MUSL flag and this target-selected TU travel together. - ...(isMuslTarget(driver) ? [rt(join(rtDir, "scr_musl.c"))] : []), - // scr_glob.c (path.matchesGlob) compiles its segments through the - // regex unit and scr_idna.c (node:url's IDNA) normalizes through it, - // so both ride the same switch; section GC drops them from binaries - // that never use them. - ...(regex - ? [ - "-I", - vendorEngineDir(), - rt(join(rtDir, "scr_regex.c")), - rt(join(rtDir, "scr_glob.c")), - rt(join(rtDir, "scr_idna.c")), - ...lreObjects, - ] - : []), - ...(opts.assert || regex || opts.symbol ? [rt(join(rtDir, "scr_assert.c"))] : []), - ...(opts.inspect - ? [rt(join(rtDir, "scr_inspect.c")), rt(join(rtDir, "scr_console_native.c"))] - : []), - ...(opts.dynInvoke || opts.workers || nativeFetch ? [rt(join(rtDir, "scr_dyn_invoke.c"))] : []), - ...(opts.dc ? [rt(join(rtDir, "scr_dc.c"))] : []), - ...(opts.dynTier === true ? [rt(join(rtDir, "scr_js_value.c"))] : []), - ...(opts.eventTarget || nativeFetch ? [rt(join(rtDir, "scr_event_target.c"))] : []), - ...(opts.dynAsync || opts.dynInvoke || opts.workers || opts.dc || opts.fileHandle || nativeFetch - ? [rt(join(rtDir, "scr_async_dyn.c"))] - : []), - // The performance timeline (every perf_hooks user-timing call selects - // dynAsync, whose unit carries the timeline's buffer warning). - ...(opts.dynAsync ? [rt(join(rtDir, "scr_perf.c"))] : []), - // Blob and File (every blob.* call selects dynAsync, whose unit boxes - // their reads as dyn promises). - ...(opts.dynAsync ? [rt(join(rtDir, "scr_blob.c"))] : []), - // The zlib UNIT (scr_zlib.c) gates on zlib.* IR use; the LINK (system - // libz on the default host-clang build, vendored objects on Zig builds) - // also serves the native fetch's gzip decoder — spread exactly once. - ...(opts.zlib - ? isZigDriver(driver) - ? ["-I", vendorZlibDir(), rt(join(rtDir, "scr_zlib.c")), ...zlibObjects] - : [rt(join(rtDir, "scr_zlib.c"))] - : nativeFetch - ? isZigDriver(driver) - ? ["-I", vendorZlibDir(), ...zlibObjects] - : [] - : []), - - // through the native installation hooks — the emitted main calls its - // installer exactly then. - ...(opts.events ? [rt(join(rtDir, "scr_events.c")), rt(join(rtDir, "scr_readline.c"))] : []), - // readline's terminal mode: rl.terminal sets the regex unit it - // measures with. - ...(opts.events && regex ? [rt(join(rtDir, "scr_readline_tty.c"))] : []), - ...(opts.emitter || opts.workers ? [rt(join(rtDir, "scr_events_emitter.c"))] : []), - // The checked-dynamic HANDLE support unit (listener gate + runtime - // adapter closures): every referencing unit is one of the emitter or - // net families (http implies net), so handle-free binaries keep - // their exact size class. - ...(opts.emitter || opts.workers || net ? [rt(join(rtDir, "scr_dyn_handle.c"))] : []), - ...(opts.symbol ? [rt(join(rtDir, "scr_symbol.c"))] : []), - ...(opts.assert && opts.bigint ? [rt(join(rtDir, "scr_bigint_assert.c"))] : []), - ...(opts.qs ? [rt(join(rtDir, "scr_qs.c"))] : []), - // The legacy url unit references the regex, querystring and dyn-async - // units; the url.parse family's IR sets all three. - ...(regex && opts.qs && opts.dynAsync ? [rt(join(rtDir, "scr_url_legacy.c"))] : []), - ...(opts.parseArgs ? [rt(join(rtDir, "scr_util.c"))] : []), - ...(opts.parseArgs && opts.symbol ? [rt(join(rtDir, "scr_util_compare.c"))] : []), - ...(opts.parseArgs && opts.inspect && opts.dynAsync - ? [rt(join(rtDir, "scr_util_style.c"))] - : []), - ...(opts.parseArgs && opts.inspect && opts.dynAsync && opts.dynInvoke && regex - ? [rt(join(rtDir, "scr_util_callable.c"))] - : []), - ...(opts.stream ? [rt(join(rtDir, "scr_stream.c")), rt(join(rtDir, "scr_fs_stream.c"))] : []), - // The zlib stream classes reference both the zlib engine and the - // stream unit. - ...(opts.zlib && opts.stream ? [rt(join(rtDir, "scr_zlib_stream.c"))] : []), - ...(opts.stream && opts.dynAsync ? [rt(join(rtDir, "scr_stream_ops.c"))] : []), - // FileHandle streams reference both the stream unit and FileHandle. - ...(opts.stream && opts.fileHandle ? [rt(join(rtDir, "scr_fs_stream_fh.c"))] : []), - // The readiness-poller backends (scr_platform.h): kqueue on macOS/BSD, - // epoll on Linux, WSAPoll on Windows — each TU is empty off its - // platform, so all three link whenever a poller-using unit does and - // the others cost nothing (ws2_32 rides the unconditional win32 libs - // above). - ...(net || opts.dgram - ? [ - rt(join(rtDir, "scr_loop_kqueue.c")), - rt(join(rtDir, "scr_loop_epoll.c")), - rt(join(rtDir, "scr_loop_wsapoll.c")), - ] - : []), - ...(net ? [rt(join(rtDir, "scr_net.c"))] : []), - ...(http ? [rt(join(rtDir, "scr_http.c"))] : []), - ...((opts.http2 ?? false) ? [rt(join(rtDir, "scr_http2.c"))] : []), - ...(opts.dgram ? [rt(join(rtDir, "scr_dgram.c"))] : []), - ...(opts.watch ? [rt(join(rtDir, "scr_watch.c"))] : []), - ...(opts.foreignFfi ? [rt(join(rtDir, "scr_ffi_queue.c"))] : []), - ...(opts.nodeTest ? [rt(join(rtDir, "scr_test.c"))] : []), - ...(opts.webcrypto ? [rt(join(rtDir, "scr_webcrypto.c"))] : []), - // The CA-store unit rides its own gate OR the tls one: scr_tls.c - // references its default-set override unconditionally. - ...(tlsCa ? [rt(join(rtDir, "scr_tls_ca.c"))] : []), - ...(tlsArchive - ? [ - "-I", - join(vendorTlsDir(), "include"), - ...(tls ? [rt(join(rtDir, "scr_tls.c"))] : []), - ...(cipher ? [rt(join(rtDir, "scr_cipher.c"))] : []), - tlsArchive, - // mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h). - // The unconditional win32 libs above do not carry it. - // Never present on the default path, so the historical TLS - // link line cannot change. - ...(targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []), - ] - : []), - // scr_tls_ca.c enumerates and PEM-encodes Windows system-store entries. - // This is independent of the mbedTLS archive: getCACertificates-only - // programs need crypt32 too, while TLS programs imply the CA unit. - ...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []), - // Fetch uses the native network, TLS, and HTTP runtime. - ...(nativeFetch ? [rt(join(rtDir, "scr_fetch.c"))] : []), + ...runtimeUnitArgs({ + opts, + driver, + rt, + rtDir, + regex, + nativeFetch, + net, + http, + tls, + tlsCa, + cipher, + lreObjects, + zlibObjects, + tlsArchive, + }), // A .ll program TU (the LLVM backend) deliberately carries no target // triple (byte-stable output; clang supplies the host/SCRIPTC_TARGET // triple exactly as it does for .c) — silence the -Woverride-module @@ -647,10 +761,7 @@ export async function compileCInternal( ? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"] : [optimization === "dev" ? "-O0" : "-O2"]), ...executableSectionFlags.compile, - ...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []), - ...(opts.warningReplace ? ["-DSCR_WARNING_REPLACE"] : []), - ...(opts.stdioExtended ? ["-DSCR_STDIO_EXTENDED"] : []), - ...(opts.youngNursery ? ["-DSCR_YIN"] : []), + ...runtimeDefineArgs(opts), "-fno-math-errno", "-fno-strict-aliasing", // the emitted object model type-puns — see buildArgs "-Wno-deprecated-declarations", diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 6a660e00c..5c9d417c5 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -82,7 +82,10 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures { workers: boolean; youngNursery: boolean; eventTarget: boolean; + wasm: boolean; dynTier: boolean; + streamDyn: boolean; + eventsOnceSignal: boolean; warningReplace: boolean; stdioExtended: boolean; nativeFetch: boolean; @@ -272,7 +275,10 @@ export function effectiveRuntimeFeatures( workers: features.workers === true, youngNursery: features.youngNursery === true, eventTarget: features.eventTarget === true, + wasm: features.wasm === true, dynTier: features.dynTier === true, + streamDyn: features.streamDyn === true, + eventsOnceSignal: features.eventsOnceSignal === true, warningReplace: features.warningReplace === true, stdioExtended: features.stdioExtended === true, nativeFetch, diff --git a/packages/compiler/src/backend/runtime-pack-targets.test.ts b/packages/compiler/src/backend/runtime-pack-targets.test.ts index fd57cb49d..96503e2db 100644 --- a/packages/compiler/src/backend/runtime-pack-targets.test.ts +++ b/packages/compiler/src/backend/runtime-pack-targets.test.ts @@ -107,12 +107,15 @@ const features = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, @@ -120,6 +123,7 @@ const features = { tlsCa: false, cipher: false, webcrypto: false, + perfLoop: false, }; test.each([ diff --git a/packages/compiler/src/backend/runtime-pack.test.ts b/packages/compiler/src/backend/runtime-pack.test.ts index 61d1dba5e..cd342f85b 100644 --- a/packages/compiler/src/backend/runtime-pack.test.ts +++ b/packages/compiler/src/backend/runtime-pack.test.ts @@ -44,12 +44,15 @@ const BASE: NativeLinkFeatures = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, @@ -57,6 +60,7 @@ const BASE: NativeLinkFeatures = { tlsCa: false, cipher: false, webcrypto: false, + perfLoop: false, }; async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) { diff --git a/packages/compiler/src/backend/stack-observations.test.ts b/packages/compiler/src/backend/stack-observations.test.ts index a95f0140f..d381c5307 100644 --- a/packages/compiler/src/backend/stack-observations.test.ts +++ b/packages/compiler/src/backend/stack-observations.test.ts @@ -1,5 +1,6 @@ import { expect, test } from "vitest"; import { + DYN, F64, STRING, VOID, @@ -49,6 +50,131 @@ test("a reachable stack read is observed", () => { expect(observesStacks(mod([fn("%main", [call("parse")]), fn("parse", [stackRead])]))).toBe(true); }); +test("a stack read on a checked-dynamic value is observed", () => { + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const read = (key: string): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { + loc, + kind: "dynKeyGet", + value, + key: { loc, kind: "strLit", value: key, type: STRING }, + type: DYN, + }, + }); + expect(observesStacks(mod([fn("%main", [read("stack")])]))).toBe(true); + expect(observesStacks(mod([fn("%main", [read("message")])]))).toBe(false); +}); + +test("a computed key reads the stack when the program spells it", () => { + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const key: IrExpr = { loc, kind: "varRef", localId: "k", type: STRING }; + const computed: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "dynKeyGet", value, key, type: DYN }, + }; + const spelled: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "strLit", value: "stack", type: STRING }, + }; + expect(observesStacks(mod([fn("%main", [computed, spelled])]))).toBe(true); + expect(observesStacks(mod([fn("%main", [computed])]))).toBe(false); +}); + +test('a computed key that cannot be "stack" reads no stack', () => { + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const spelled: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "strLit", value: "stack", type: STRING }, + }; + const read = (key: IrExpr): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "dynKeyGet", value, key, type: DYN }, + }); + const index: IrExpr = { loc, kind: "varRef", localId: "i", type: F64 }; + const indexText: IrExpr = { loc, kind: "toString", operand: index, type: STRING }; + // A number key, and a key converted from one. + expect(observesStacks(mod([fn("%main", [read(index), spelled])]))).toBe(false); + expect(observesStacks(mod([fn("%main", [read(indexText), spelled])]))).toBe(false); + // An immutable local answers with its initializer (the shape of the + // dispatch helpers' formatted index keys). + const key: IrExpr = { loc, kind: "varRef", localId: "key", type: STRING }; + const local = (init: IrExpr, mutable = false): IrFunction => ({ + ...fn("%main", [{ loc, kind: "varDecl", localId: "key", init }, read(key), spelled]), + locals: [{ id: "key", name: "key", type: STRING, mutable }], + }); + expect(observesStacks(mod([local(indexText)]))).toBe(false); + expect(observesStacks(mod([local(indexText, true)]))).toBe(true); + // A parameter of a function called by name answers with its arguments. + const helper: IrFunction = { + ...fn("readKey", [read(key)]), + params: [{ localId: "key", name: "key", type: STRING }], + locals: [{ id: "key", name: "key", type: STRING, mutable: false }], + }; + const callWith = (arg: IrExpr): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee: "readKey", args: [arg], type: DYN }, + }); + const numbers = mod([fn("%main", [callWith(indexText), spelled]), helper]); + expect(observesStacks(numbers)).toBe(false); + const stack = mod([ + fn("%main", [ + callWith(indexText), + callWith({ loc, kind: "strLit", value: "stack", type: STRING }), + ]), + helper, + ]); + expect(observesStacks(stack)).toBe(true); +}); + +test("a key forwarded through functions with several call sites is searched once", () => { + // f0 calls f1 twice, f1 calls f2 twice, and so on, each passing its + // parameter on: 2^40 call paths reach the read in f40. + const depth = 40; + const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN }; + const key: IrExpr = { loc, kind: "varRef", localId: "key", type: STRING }; + const forward = (callee: string): IrStmt => ({ + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee, args: [key], type: VOID }, + }); + const chain = (root: IrExpr): IrModule => { + const functions: IrFunction[] = []; + for (let i = 0; i <= depth; i++) { + const body: IrStmt[] = + i < depth + ? [forward(`f${i + 1}`), forward(`f${i + 1}`)] + : [{ loc, kind: "exprStmt", expr: { loc, kind: "dynKeyGet", value, key, type: DYN } }]; + functions.push({ + ...fn(`f${i}`, body), + params: [{ localId: "key", name: "key", type: STRING }], + locals: [{ id: "key", name: "key", type: STRING, mutable: false }], + }); + } + const spelled: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "strLit", value: "stack", type: STRING }, + }; + const start: IrStmt = { + loc, + kind: "exprStmt", + expr: { loc, kind: "call", callee: "f0", args: [root], type: VOID }, + }; + return mod([fn("%main", [start, spelled]), ...functions]); + }; + expect(observesStacks(chain({ loc, kind: "strLit", value: "message", type: STRING }))).toBe( + false, + ); + expect(observesStacks(chain({ loc, kind: "strLit", value: "stack", type: STRING }))).toBe(true); +}); + test("programs without stack reads are not", () => { expect(observesStacks(mod([fn("%main", [call("helper")]), fn("helper", [ret])]))).toBe(false); }); diff --git a/packages/compiler/src/backend/stack-observations.ts b/packages/compiler/src/backend/stack-observations.ts index 7debf0751..fa67c071f 100644 --- a/packages/compiler/src/backend/stack-observations.ts +++ b/packages/compiler/src/backend/stack-observations.ts @@ -3,7 +3,7 @@ * it answers true. */ import { exprFunctionReference, moduleFunctionReferences } from "../ir/function-references.js"; import { MAY_THROW_LIB_FNS } from "../ir/builtin-effects.js"; -import type { IrExpr, IrFunction, IrModule, IrStmt } from "../ir/ir.js"; +import type { IrExpr, IrFunction, IrModule, IrStmt, IrType } from "../ir/ir.js"; import { MAY_THROW_ARR_METHODS } from "../ir/ir.js"; import { everyExprChild, everyStmtChild, everyStmtList } from "../ir/traverse.js"; import { unreachableStatements } from "./may-throw.js"; @@ -13,12 +13,153 @@ 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); +/** Key types whose property names are never `stack`: a number, bigint or + * boolean key names its decimal or `true`/`false` property. */ +const NON_STACK_KEY_TYPES: ReadonlySet = new Set(["f64", "bigint", "bool"]); + +/** Whether a computed key can be the string "stack", the question a read + * on a checked-dynamic value asks before it counts as a stack read. + * - A string literal answers by its value; a key of a number, bigint or + * boolean type, or one converted from such a value, cannot be "stack". + * - An immutable local answers with its one initializer, and a parameter of + * a function the program only calls by name answers with the arguments + * its call sites pass. + * - Any other key can be "stack" when the program spells the literal + * "stack" somewhere, since the literal can reach the key through fields, + * containers or calls; a key the program assembles from other strings or + * from data it reads keeps the static answer. */ +class StackKeys { + private readonly spelled: boolean; + private readonly calls = new Map(); + private readonly named: Set; + private readonly inits = new Map>(); + /** Settled answers by function and local: true where a search reached a + * key that can be "stack", false where a search that found none passed. */ + private readonly answers = new Map(); + + constructor(mod: IrModule) { + this.spelled = spellsStack(mod); + this.named = new Set(moduleFunctionReferences(mod)); + // Class methods, accessors and constructors are called through dispatch. + const classNames = new Set((mod.classes ?? []).map((cls) => cls.name)); + for (const fn of mod.functions) { + if (isClassMember(fn.name, classNames)) this.named.add(fn.name); + everyStmtList(fn.body, { + stmt: () => true, + expr: (expr) => { + if (expr.kind === "call") { + const sites = this.calls.get(expr.callee) ?? []; + sites.push({ caller: fn, args: expr.args }); + this.calls.set(expr.callee, sites); + } + const referenced = exprFunctionReference(expr); + if (referenced !== undefined) this.named.add(referenced); + return true; + }, + }); + } + } + + mayBeStack(fn: IrFunction, key: IrExpr): boolean { + const visited = new Set(); + const answer = this.keyMayBeStack(fn, key, visited); + // A search that found no way to "stack" explored everything it visited + // to the end, so none of it can be "stack" either. + if (!answer) for (const visit of visited) this.answers.set(visit, false); + return answer; + } + + private keyMayBeStack(fn: IrFunction, key: IrExpr, visited: Set): boolean { + if (key.kind === "strLit") return key.value === "stack"; + if (!this.spelled) return false; + if (NON_STACK_KEY_TYPES.has(key.type.kind)) return false; + if (key.kind === "toString" && NON_STACK_KEY_TYPES.has(key.operand.type.kind)) return false; + if (key.kind === "dynFrom" && NON_STACK_KEY_TYPES.has(key.value.type.kind)) return false; + if (key.kind !== "varRef") return true; + const visit = `${fn.name}\0${key.localId}`; + const known = this.answers.get(visit); + if (known !== undefined) return known; + // Each local is searched once per question: a cycle, or a second path + // to a local already searched, adds no value of its own. + if (visited.has(visit)) return false; + visited.add(visit); + const answer = this.localMayBeStack(fn, key.localId, visited); + if (answer) this.answers.set(visit, true); + return answer; + } + + private localMayBeStack(fn: IrFunction, localId: string, visited: Set): boolean { + const local = fn.locals.find((candidate) => candidate.id === localId); + if (local === undefined || local.mutable) return true; + const index = fn.params.findIndex((param) => param.localId === localId); + if (index !== -1) { + const sites = this.calls.get(fn.name); + if (this.named.has(fn.name) || sites === undefined) return true; + return sites.some( + (site) => + site.args.length !== fn.params.length || + this.keyMayBeStack(site.caller, site.args[index]!, visited), + ); + } + const init = this.initializerOf(fn, localId); + return init === null || this.keyMayBeStack(fn, init, visited); + } + + /** The one initializer of an immutable local, or null when it has none + * or more than one declaration. */ + private initializerOf(fn: IrFunction, localId: string): IrExpr | null { + let inits = this.inits.get(fn); + if (inits === undefined) { + inits = new Map(); + everyStmtList(fn.body, { + stmt: (stmt) => { + if (stmt.kind === "varDecl") + inits!.set(stmt.localId, inits!.has(stmt.localId) ? null : (stmt.init ?? null)); + return true; + }, + expr: () => true, + }); + this.inits.set(fn, inits); + } + return inits.get(localId) ?? null; + } } -function containsStackRead(fn: IrFunction): boolean { - return !everyStmtList(fn.body, { stmt: () => true, expr: (expr) => !readsStack(expr) }); +/** Whether `name` follows the `%.` convention of class + * methods, accessors and constructors for one of `classNames`. */ +function isClassMember(name: string, classNames: ReadonlySet): boolean { + if (!name.startsWith("%")) return false; + for (let dot = name.indexOf("."); dot !== -1; dot = name.indexOf(".", dot + 1)) { + if (classNames.has(name.slice(1, dot))) return true; + } + return false; +} + +/** A typed `error.stack` read, or a read on a checked-dynamic value that + * can reach an error's own `stack` accessor: one whose key can be "stack" + * (StackKeys). */ +function readsStack(fn: IrFunction, expr: IrExpr, keys: StackKeys): boolean { + if (expr.kind === "libCall") return STACK_READS.has(expr.fn); + if (expr.kind !== "dynKeyGet") return false; + return keys.mayBeStack(fn, expr.key); +} + +/** Whether the module contains the string literal "stack". */ +function spellsStack(mod: IrModule): boolean { + return mod.functions.some( + (fn) => + !everyStmtList(fn.body, { + stmt: () => true, + expr: (expr) => !(expr.kind === "strLit" && expr.value === "stack"), + }), + ); +} + +function containsStackRead(fn: IrFunction, keys: StackKeys): boolean { + return !everyStmtList(fn.body, { + stmt: () => true, + expr: (expr) => !readsStack(fn, expr, keys), + }); } /** Visit every expression of `body` that execution can reach: statements @@ -43,21 +184,14 @@ function visitLiveExprs(body: IrStmt[], visit: (expr: IrExpr) => void): void { * and those reads must not cost every call of the program a frame. * Programs without any stack read answer false without the walk. */ export function observesStacks(mod: IrModule): boolean { - if (!mod.functions.some(containsStackRead)) return false; + const keys = new StackKeys(mod); + if (!mod.functions.some((fn) => containsStackRead(fn, keys))) return false; const byName = new Map(mod.functions.map((fn) => [fn.name, fn])); const classNames = new Set((mod.classes ?? []).map((cls) => cls.name)); const pending: string[] = [...moduleFunctionReferences(mod)]; // Class methods, accessors and constructors are reachable through // dispatch the IR does not name per site: all of them are roots. - for (const fn of mod.functions) { - if (!fn.name.startsWith("%")) continue; - for (let dot = fn.name.indexOf("."); dot !== -1; dot = fn.name.indexOf(".", dot + 1)) { - if (classNames.has(fn.name.slice(1, dot))) { - pending.push(fn.name); - break; - } - } - } + for (const fn of mod.functions) if (isClassMember(fn.name, classNames)) pending.push(fn.name); const reached = new Set(); let observed = false; while (pending.length > 0 && !observed) { @@ -67,7 +201,7 @@ export function observesStacks(mod: IrModule): boolean { const fn = byName.get(name); if (fn === undefined) continue; visitLiveExprs(fn.body, (expr) => { - if (readsStack(expr)) observed = true; + if (readsStack(fn, expr, keys)) observed = true; if (expr.kind === "call" && !reached.has(expr.callee)) pending.push(expr.callee); const referenced = exprFunctionReference(expr); if (referenced !== undefined && !reached.has(referenced)) pending.push(referenced); diff --git a/packages/compiler/src/backend/target-diagnostics.ts b/packages/compiler/src/backend/target-diagnostics.ts index 365d5e913..d130ce95e 100644 --- a/packages/compiler/src/backend/target-diagnostics.ts +++ b/packages/compiler/src/backend/target-diagnostics.ts @@ -192,6 +192,14 @@ export function moduleWasiUnavailableSurface( found = { surface: "OS signals (WASI Preview 1 has no signal API)", loc }; return false; } + if (node.fn.startsWith("wasm.")) { + found = { + surface: + "the WebAssembly API (a wasm32-wasi program cannot load translated modules into its own linear memory yet)", + loc, + }; + return false; + } if (node.fn === "crypto.cipherNew") { found = { surface: diff --git a/packages/compiler/src/backend/warm-cache.ts b/packages/compiler/src/backend/warm-cache.ts index 2cd111060..389fa19e9 100644 --- a/packages/compiler/src/backend/warm-cache.ts +++ b/packages/compiler/src/backend/warm-cache.ts @@ -50,18 +50,22 @@ export async function warmNativeCaches( bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, tls: false, cipher: false, webcrypto: false, + perfLoop: false, tlsCa: false, }, }); diff --git a/packages/compiler/src/coverage/report.ts b/packages/compiler/src/coverage/report.ts index 6570f23d1..89733e255 100644 --- a/packages/compiler/src/coverage/report.ts +++ b/packages/compiler/src/coverage/report.ts @@ -221,13 +221,13 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions out.push(""); } - // The WebAssembly modules the build can see: what translating them would - // take (wasm/discover.ts). No module compiles to native code yet; the - // sites that load one are blockers or deferred fences above. + // The WebAssembly modules the build can see (wasm/discover.ts). A build + // translates the valid ones to native code (wasm/translate.ts) when the + // program uses the WebAssembly API; the others fail to load at run time. const wasm = input.wasmModules ?? []; if (wasm.length > 0) { out.push( - ` ${c(DIM, "WebAssembly modules:")} ${c(DIM, "(found at build time; none compile to native code yet)")}`, + ` ${c(DIM, "WebAssembly modules:")} ${c(DIM, "(found at build time; valid ones compile to native code)")}`, ); for (const m of wasm) { const name = m.path !== undefined ? displayPath(m.path, opts.root) : "inline module"; diff --git a/packages/compiler/src/coverage/surface-manifest.ts b/packages/compiler/src/coverage/surface-manifest.ts index d82367611..9867a5617 100644 --- a/packages/compiler/src/coverage/surface-manifest.ts +++ b/packages/compiler/src/coverage/surface-manifest.ts @@ -444,16 +444,17 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes } } - // ── the WebAssembly global: every reached use refuses (webAssemblyDiag) ─ + // ── the WebAssembly global (builtins/webassembly.ts, wasm/translate.ts): + // the core API lowers over modules translated at build time; the rest + // refuses at each use (webAssemblyDiag) ─ { const refusal = webAssemblyDiag("WebAssembly", { file: "", start: 0, end: 0 }); add({ id: "stdlib.webassembly", kind: "stdlib", name: "globalThis.WebAssembly", - status: "unsupported", - code: refusal.code, - ...(refusal.hint !== undefined ? { note: refusal.hint } : {}), + status: "static", + note: `compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, instanceof, the error classes, exports, imports and Module transfer to workers, over the modules translated at build time; the rest refuses with ${refusal.code} (${refusal.hint ?? ""})`, }); } diff --git a/packages/compiler/src/diagnostics/diagnostic.ts b/packages/compiler/src/diagnostics/diagnostic.ts index 4acd6bc50..e4d4006ca 100644 --- a/packages/compiler/src/diagnostics/diagnostic.ts +++ b/packages/compiler/src/diagnostics/diagnostic.ts @@ -59,8 +59,9 @@ * order that differs from insertion order where the program * observes it (SC6002), localeCompare over text the built-in * collation does not cover (SC6003), a call feeding an instance - * to a cast to an unrelated class (SC6004), and Date strings the native parser does not - * accept (SC6005) + * to a cast to an unrelated class (SC6004); SC6005 (Date strings + * outside the native parser's grammar) is retired — the runtime + * parses date strings with V8's grammar * SC9xxx internal compiler errors (still source-anchored) */ import type { SrcLoc } from "../ir/ir.js"; @@ -200,18 +201,6 @@ export function unrelatedClassCastDivergenceDiag( }; } -/** SC6005 — a Date string literal outside the grammar the native parser - * accepts (ISO 8601 with an explicit offset, date-only forms, and the - * certificate-time shape): Node parses many more forms. */ -export function dateLiteralDivergenceDiag(text: string, loc: SrcLoc): ScrDiagnostic { - return { - code: "SC6005", - message: `the date string '${text}' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', and date-only forms)`, - loc, - hint: "write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC", - }; -} - /* ── SC5xxx: native FFI ───────────────────────────────────────────────── */ /** SC5001 — the outbound native-FFI manifest is malformed or unreadable. */ @@ -282,11 +271,11 @@ export const UNSUPPORTED: Record = { "imports (#alias via the imports field, self-name references via " + "exports), statically compiled npm packages, " + "and the built-in fs, fs/promises, path, os, url, crypto, zlib, " + - "child_process, net, http, tls, https, http2, dgram, dns, util, " + + "child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, " + "util/types, string_decoder, querystring, readline, events, stream, " + - "stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, " + + "stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, " + "cluster, tty, async_hooks, timers, timers/promises, " + - "diagnostics_channel, perf_hooks, module, and console modules (bare or " + + "diagnostics_channel, perf_hooks, module, v8, and console modules (bare or " + "node:-prefixed) and node:test (node:-prefixed only, like in Node) " + "are supported", }, @@ -751,17 +740,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'). */ +/** A REACHED use of a part of the WebAssembly API scriptc does not + * implement yet (SC2020): the streaming functions, Tag and Exception, the + * classes as values. compile, instantiate, validate, the Module, Instance, + * Memory, Table and Global constructors, Module.imports/exports/ + * customSections and the error classes lower (builtins/webassembly.ts). + * `surface` names what was used ('WebAssembly.compileStreaming', + * 'WebAssembly.Memory'). */ export function webAssemblyDiag(surface: string, loc: SrcLoc): ScrDiagnostic { return { code: "SC2020", - message: `'${surface}' is not supported yet: scriptc does not compile WebAssembly modules to native code yet`, + message: `'${surface}' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API`, loc, milestone: "later", - hint: "WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations)", + hint: "the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations)", }; } diff --git a/packages/compiler/src/executable/executable-cache.test.ts b/packages/compiler/src/executable/executable-cache.test.ts index 1b42c3eca..33da0610f 100644 --- a/packages/compiler/src/executable/executable-cache.test.ts +++ b/packages/compiler/src/executable/executable-cache.test.ts @@ -45,12 +45,15 @@ const native: EarlyExecutableNativeFeatures = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, @@ -58,6 +61,7 @@ const native: EarlyExecutableNativeFeatures = { tlsCa: false, cipher: false, webcrypto: false, + perfLoop: false, }; async function fixture(): Promise<{ @@ -199,7 +203,7 @@ test("early executable cache misses on source and resolution changes", async () expect(await readEarlyExecutableCache(f.root, f.options)).toBeNull(); }); -test("early executable keys isolate compile modes, paths, implementation, and FFI manifests", async () => { +test("early executable keys isolate compile modes, paths, implementation, checker, and FFI manifests", async () => { const f = await fixture(); const tracker = new FrontendInputTracker(); tracker.run(() => trackedReadFile(f.source)); @@ -216,6 +220,7 @@ test("early executable keys isolate compile modes, paths, implementation, and FF { ...f.options, optimization: "dev" }, { ...f.options, optimization: "speed" }, { ...f.options, detachedExit: true }, + { ...f.options, checker: "tsc-ts-executable-identity" }, { ...f.options, npmStatic: "auto" }, { ...f.options, npmStatic: ["commander"] }, { ...f.options, outPath: join(f.options.outDir, "other") }, diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 8c67c1006..064b1f867 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -56,6 +56,7 @@ export interface EarlyExecutableNativeFeatures { fileHandle: boolean; fetch: boolean; eventTarget?: boolean; + wasm?: boolean; warningReplace?: boolean; stdioExtended?: boolean; zlib: boolean; @@ -65,23 +66,29 @@ export interface EarlyExecutableNativeFeatures { dc: boolean; dynAsync: boolean; dynTier?: boolean; + streamDyn?: boolean; + eventsOnceSignal?: boolean; events: boolean; emitter: boolean; symbol: boolean; bigint: boolean; searchParams: boolean; qs: boolean; + dynBuiltinClass: boolean; + dynBuiltinStream: boolean; parseArgs: boolean; stream: boolean; net: boolean; http: boolean; http2: boolean; dgram: boolean; + dnsPromises: boolean; watch: boolean; foreignFfi: boolean; nodeTest: boolean; tls: boolean; cipher: boolean; + perfLoop: boolean; tlsCa: boolean; webcrypto: boolean; } @@ -116,6 +123,9 @@ export interface EarlyExecutableCacheOptions { windowsSubsystem?: "gui"; /** Omitted unless --detached-exit is set. */ detachedExit?: true; + /** Omitted for the default tsgo checker; with SCRIPTC_CHECKER=tsc-ts, the + * tsc-ts executable's file identity. */ + checker?: string; /** Omitted unless SCRIPTC_YOUNG_NURSERY=1 (backend/young-nursery.ts). */ youngNursery?: true; npmStatic: readonly string[] | "auto" | null; @@ -185,12 +195,15 @@ const BOOLEAN_NATIVE_KEYS = [ "bigint", "searchParams", "qs", + "dynBuiltinClass", + "dynBuiltinStream", "parseArgs", "stream", "net", "http", "http2", "dgram", + "dnsPromises", "watch", "foreignFfi", "nodeTest", @@ -198,6 +211,7 @@ const BOOLEAN_NATIVE_KEYS = [ "tlsCa", "cipher", "webcrypto", + "perfLoop", ] as const satisfies readonly (keyof EarlyExecutableNativeFeatures)[]; function validNativeFeatures(value: unknown): value is EarlyExecutableNativeFeatures { @@ -230,6 +244,7 @@ function cacheKey(options: EarlyExecutableCacheOptions): string { ...(options.strip ? ["strip"] : []), ...(options.windowsSubsystem === "gui" ? ["windows-subsystem-gui"] : []), ...(options.detachedExit ? ["detached-exit"] : []), + ...(options.checker !== undefined ? [`checker:${options.checker}`] : []), ...(options.youngNursery ? ["young-nursery"] : []), options.npmStatic === null ? "" @@ -270,6 +285,7 @@ function routeKey(options: Omit = { "graceful-fs": "fs" }; + +/** The builtin module an npm package specifier is served as, or null. */ +export function npmBuiltinAliasOf(spec: string): string | null { + return Object.hasOwn(NPM_BUILTIN_ALIASES, spec) ? (NPM_BUILTIN_ALIASES[spec] ?? null) : null; } /** Both specifier spellings of every supported builtin (prefix-only ones - * keep just the node: form) — the ambient-module allowlist preflight uses. */ -export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODULES.flatMap((m) => - PREFIX_ONLY_BUILTIN_MODULES.has(m) ? [`node:${m}`] : [m, `node:${m}`], -); + * keep just the node: form) — the ambient-module allowlist preflight uses + * — plus the npm packages served as builtins. */ +export const SUPPORTED_NODE_MODULES: readonly string[] = [ + ...SUPPORTED_BUILTIN_MODULES.flatMap((m) => + PREFIX_ONLY_BUILTIN_MODULES.has(m) ? [`node:${m}`] : [m, `node:${m}`], + ), + ...Object.keys(NPM_BUILTIN_ALIASES), +]; /* ── out-of-scope builtins ─────────────────────────────────────────────── * Builtin modules a compiled binary is not going to serve — not "yet", @@ -92,8 +114,8 @@ export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODUL * so 'node:sqlite' keys as 'sqlite'. Exact-match only: an npm package * whose name collides with none of these keys keeps the generic wording. * Everything absent from this table stays the plain "not supported yet" - * story — genuinely pending surface (console's Console class, stream/web) - * must not read as permanently refused. */ + * story — genuinely pending surface (console's Console class) must not + * read as permanently refused. */ /** Node's own HTTP/TLS/stream implementation internals, requireable by * legacy convention (underscore-prefixed lib/ modules). They expose @@ -102,11 +124,15 @@ export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODUL const NODE_INTERNAL_REASON = "a Node-internal module: it exposes Node's own implementation objects, which the scriptc runtime does not replicate"; -const V8_REASON = - "it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe"; +/** node:v8 is importable (packages require it at top level and touch it + * only on optional paths), and getHeapStatistics answers allocator-derived + * figures; every other member observes the engine itself. Member fences + * carry this reason instead of a module-level refusal. */ +export const V8_MEMBER_REASON = + "by design: this member observes V8 engine internals — heap snapshots and spaces, GC and CPU profiles, flags, serialize/deserialize — and a compiled binary embeds no V8 engine to observe"; const OUT_OF_SCOPE_BUILTIN_REASONS: Record = { - v8: V8_REASON, + vm: "it compiles and runs JavaScript source at runtime, and a compiled binary embeds no JavaScript engine to run it", inspector: "it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect", "inspector/promises": @@ -142,3 +168,10 @@ export function unsupportedModuleFeatureOf(spec: string): string { : undefined; return reason === undefined ? `the '${spec}' module` : `the '${spec}' module (${reason})`; } + +/** The ESNext temporal global's name. Node 24 does not define it: programs + * that name it type-check against TS 7's lib slice (program.ts adds it only + * for them) and lower to Node's answers. Spelled in two pieces so the + * compiler's own sources never trip program.ts's scan when scriptc + * compiles itself. */ +export const TEMPORAL_GLOBAL = "Tempo" + "ral"; diff --git a/packages/compiler/src/frontend/cjs-syntax.ts b/packages/compiler/src/frontend/cjs-syntax.ts index 41191432a..99195d994 100644 --- a/packages/compiler/src/frontend/cjs-syntax.ts +++ b/packages/compiler/src/frontend/cjs-syntax.ts @@ -1,4 +1,5 @@ import * as ts from "./ts7/syntax.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; export interface CjsLexedExports { /** Names Node's lexer detects in THIS file (no reexport resolution). */ @@ -30,7 +31,7 @@ function identRunEnd(text: string, pos: number): number { * a `a`-escaped name parses to the same AST as its plain spelling, * but merve reads bytes and stops at the backslash. */ function sourceSpellsIdentifier(node: ts.Node, sf: ts.SourceFile): boolean { - const ch = sf.text[node.getStart(sf)]; + const ch = sf.text[tokenStartOfNode(node)]; return ch !== undefined && isIdentStartChar(ch); } @@ -39,16 +40,16 @@ function sourceSpellsIdentifier(node: ts.Node, sf: ts.SourceFile): boolean { * defineProperty getter's `return IDENT.IDENT` because `this` lexes as an * identifier run — probed), escapes and private names do not. */ function isIdentTokenNode(node: ts.Node, sf: ts.SourceFile): boolean { - const start = node.getStart(sf); + const start = tokenStartOfNode(node); const ch = sf.text[start]; - return ch !== undefined && isIdentStartChar(ch) && identRunEnd(sf.text, start) === node.getEnd(); + return ch !== undefined && isIdentStartChar(ch) && identRunEnd(sf.text, start) === node.end; } /** The identifier run at the node's START — the whole name when its * spelling is plain, the byte prefix merve consumed when a unicode escape * interrupts the run mid-name. */ function identPrefixOf(node: ts.Node, sf: ts.SourceFile): string { - const start = node.getStart(sf); + const start = tokenStartOfNode(node); return sf.text.slice(start, identRunEnd(sf.text, start)); } @@ -84,20 +85,17 @@ export function isModuleExports(e: ts.Expression): boolean { * follows — so the recognizer descends the leftmost-child chain while it * still starts where `e` starts. A ParenthesizedExpression breaks the * chain naturally (its child starts after the `(`), matching merve. */ -function leadingRequireOf( - e: ts.Expression, - sf: ts.SourceFile, -): { spec: string; end: number } | null { - const start = e.getStart(sf); +function leadingRequireOf(e: ts.Expression): { spec: string; end: number } | null { + const start = tokenStartOfNode(e); let cur: ts.Node = e; for (;;) { const spec = bareRequireSpecOf(cur); - if (spec !== null && cur.getStart(sf) === start) return { spec, end: cur.getEnd() }; + if (spec !== null && tokenStartOfNode(cur) === start) return { spec, end: cur.end }; let first: ts.Node | undefined; ts.forEachChild(cur, (c) => { first ??= c; }); - if (first === undefined || first.getStart(sf) !== start) return null; + if (first === undefined || tokenStartOfNode(first) !== start) return null; cur = first; } } @@ -179,13 +177,13 @@ function scanTableLiteral( continue; // whitespace before `,` is skipped on the shorthand path } if (ts.isSpreadAssignment(prop)) { - const req = leadingRequireOf(prop.expression, sf); + const req = leadingRequireOf(prop.expression); let consumedEnd: number; if (req !== null) { reexports.push(req.spec); consumedEnd = req.end; } else if (sourceSpellsIdentifier(prop.expression, sf)) { - consumedEnd = identRunEnd(text, prop.expression.getStart(sf)); + consumedEnd = identRunEnd(text, tokenStartOfNode(prop.expression)); } else { return; // `...{}` and friends stop the scan } @@ -213,7 +211,7 @@ function scanTableLiteral( } if (key === null) return; // numeric / computed / template keys stop the scan const value = prop.initializer; - const req = leadingRequireOf(value, sf); + const req = leadingRequireOf(value); if (req !== null) { // A require VALUE adds the key, records the reexport, and stops // the scan UNCONDITIONALLY — probed: `{ a: require('x'), b: v }` @@ -223,7 +221,7 @@ function scanTableLiteral( reexports.push(req.spec); return; } - const start = value.getStart(sf); + const start = tokenStartOfNode(value); const ch0 = text[start]; if (ch0 === undefined || !isIdentStartChar(ch0)) return; // literal values stop WITHOUT adding the key const consumedEnd = identRunEnd(text, start); @@ -351,8 +349,8 @@ function starExportSpecOf(call: ts.CallExpression, sf: ts.SourceFile): string | if (!sourceSpellsIdentifier(nameNode, sf)) return null; const arg0 = call.arguments.nodes[0]; if (arg0 === undefined) return null; - if (!byteAdjacentParen(sf, nameNode.getEnd(), arg0.getStart(sf))) return null; - return leadingRequireOf(arg0, sf)?.spec ?? null; + if (!byteAdjacentParen(sf, nameNode.end, tokenStartOfNode(arg0))) return null; + return leadingRequireOf(arg0)?.spec ?? null; } /** True when the bytes between a callee name ending at `nameEnd` and its @@ -384,7 +382,7 @@ function starAssignOf(stmt: ts.VariableStatement, sf: ts.SourceFile): [string, s init.arguments.nodes.length >= 1 ) { const arg0 = init.arguments.nodes[0]!; - if (!byteAdjacentParen(sf, init.expression.getEnd(), arg0.getStart(sf))) return null; + if (!byteAdjacentParen(sf, init.expression.end, tokenStartOfNode(arg0))) return null; const spec = bareRequireSpecOf(arg0); if (spec !== null) return [decl.name.text, spec]; } @@ -717,15 +715,16 @@ export function cjsLexedExportsOfFile(sf: ts.SourceFile): CjsLexedExports { ) { exports.add(lhs.argumentExpression.text); } else if (isModuleExports(lhs)) { - events.push({ pos: n.getStart(sf), kind: "assign", rhs: n.right }); + events.push({ pos: tokenStartOfNode(n), kind: "assign", rhs: n.right }); } } else if (ts.isCallExpression(n)) { scanDefineProperty(n, sf, exports); if (braceDepth === 0) { const starSpec = starExportSpecOf(n, sf); - if (starSpec !== null) events.push({ pos: n.getStart(sf), kind: "spec", spec: starSpec }); + if (starSpec !== null) + events.push({ pos: tokenStartOfNode(n), kind: "spec", spec: starSpec }); const loopId = starLoopIdOf(n, sf); - if (loopId !== null) events.push({ pos: n.getStart(sf), kind: "loop", id1: loopId }); + if (loopId !== null) events.push({ pos: tokenStartOfNode(n), kind: "loop", id1: loopId }); } } else if (ts.isVariableStatement(n) && braceDepth === 0) { const assign = starAssignOf(n, sf); @@ -745,7 +744,7 @@ export function cjsLexedExportsOfFile(sf: ts.SourceFile): CjsLexedExports { for (const spec of starAssigns.get(ev.id1) ?? []) reexports.push(spec); } else { reexports = []; // every `module.exports =` clears — stars included (probed) - const req = leadingRequireOf(ev.rhs, sf); + const req = leadingRequireOf(ev.rhs); if (req !== null) { reexports.push(req.spec); } else if (ts.isObjectLiteralExpression(ev.rhs)) { diff --git a/packages/compiler/src/frontend/contract-types.ts b/packages/compiler/src/frontend/contract-types.ts index c30788fa1..4ded2a1a0 100644 --- a/packages/compiler/src/frontend/contract-types.ts +++ b/packages/compiler/src/frontend/contract-types.ts @@ -130,7 +130,7 @@ function markerNamed( function nameText(node: ts.Node | undefined): string { if (node === undefined) return ""; if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node)) return node.text; - return node.getText(); + return ts.getTextOfNode(node); } function contractDiag( diff --git a/packages/compiler/src/frontend/experimental-import.ts b/packages/compiler/src/frontend/experimental-import.ts index 8fc2238bf..3b35a196a 100644 --- a/packages/compiler/src/frontend/experimental-import.ts +++ b/packages/compiler/src/frontend/experimental-import.ts @@ -73,7 +73,7 @@ export function lifetimeRegionMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "LifetimeRegion" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; @@ -105,7 +105,7 @@ export function internTableMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "InternTable" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; @@ -135,7 +135,7 @@ export function threadSlotsMemberOf( if ( !ts.isClassDeclaration(owner) || owner.name?.text !== "ThreadSlots" || - !isExperimentalDeclarationFile(owner.getSourceFile(), known) + !isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known) ) return null; const name = access.name.text; diff --git a/packages/compiler/src/frontend/fork-target.test.ts b/packages/compiler/src/frontend/fork-target.test.ts index 7a3e23c9b..5e56cea5d 100644 --- a/packages/compiler/src/frontend/fork-target.test.ts +++ b/packages/compiler/src/frontend/fork-target.test.ts @@ -7,6 +7,7 @@ import { fallbackDtsPath } from "./dts-paths.js"; import { Ts7Host } from "./ts7/program-host.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; test("fork discovery batches only candidate bindings and preserves aliases and shadowing", () => { const directory = mkdtempSync(join(tmpdir(), "scriptc-fork-discovery-")); @@ -49,7 +50,7 @@ test("fork discovery batches only candidate bindings and preserves aliases and s ["first.ts", "second.ts", "third.ts"].map((name) => join(directory, name)), ); expect(batch).toHaveBeenCalledTimes(1); - expect(batch.mock.calls[0]![0].map((node) => node.getText())).toEqual([ + expect(batch.mock.calls[0]![0].map((node) => getTextOfNode(node))).toEqual([ "start", "child", "launch", diff --git a/packages/compiler/src/frontend/fork-target.ts b/packages/compiler/src/frontend/fork-target.ts index 592bbb2d6..38c8f5f07 100644 --- a/packages/compiler/src/frontend/fork-target.ts +++ b/packages/compiler/src/frontend/fork-target.ts @@ -12,7 +12,7 @@ export function isNodeGlobal(program: ts.Program, expression: ts.Identifier): bo return ( declarations.length > 0 && declarations.every((declaration) => { - const source = declaration.getSourceFile(); + const source = ts.getSourceFileOfNode(declaration); return ( source.isDeclarationFile && (tsgoPath(source.fileName) === fallbackDtsPath() || isNodeTypesPath(source.fileName)) @@ -61,7 +61,7 @@ export function isBuiltinMemberImport( if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = checker.getAliasedSymbol(symbol); const targetDecl = target.declarations[0]; - if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) { + if (targetDecl !== undefined && ts.getSourceFileOfNode(targetDecl).isDeclarationFile) { for ( let parent: ts.Node | undefined = targetDecl.parent; parent !== undefined && !ts.isSourceFile(parent); @@ -69,7 +69,8 @@ export function isBuiltinMemberImport( ) { if (ts.isModuleDeclaration(parent) && ts.isStringLiteral(parent.name)) { return ( - canonicalBuiltinModule(parent.name.text) === moduleName && target.name === memberName + canonicalBuiltinModule(parent.name.text) === moduleName && + ts.symbolName(target) === memberName ); } } @@ -188,7 +189,7 @@ function staticString( ): string | null { const current = strip(expr); if (ts.isStringLiteralLike(current)) return current.text; - if (importMetaUrl(current)) return pathToFileURL(current.getSourceFile().fileName).href; + if (importMetaUrl(current)) return pathToFileURL(ts.getSourceFileOfNode(current).fileName).href; if (ts.isTemplateExpression(current)) { let value = current.head.text; for (const span of current.templateSpans.nodes) { diff --git a/packages/compiler/src/frontend/helper-tokens.test.ts b/packages/compiler/src/frontend/helper-tokens.test.ts index c6355cef9..522b39545 100644 --- a/packages/compiler/src/frontend/helper-tokens.test.ts +++ b/packages/compiler/src/frontend/helper-tokens.test.ts @@ -27,6 +27,8 @@ test.each([ ['"default"', '"default;s3:foo;"'], ['"default"', '"default\\0"'], ["mod", "mod other"], + ["cb[0]", "cb[1]"], + ["cb[1]", "cb[10]"], ])("retains a semantic difference between %s and %s", (a, b) => { expect(helperTokens(a)).not.toBe(helperTokens(b)); }); @@ -44,8 +46,12 @@ test.each([ "'\\8'", "`template`", "/regexp/", - "0", "0x10", + "07", + "1.5", + "1e3", + "1n", + "1_000", "a / b", "\\u0061", "café", diff --git a/packages/compiler/src/frontend/helper-tokens.ts b/packages/compiler/src/frontend/helper-tokens.ts index d7a968755..908cc0d22 100644 --- a/packages/compiler/src/frontend/helper-tokens.ts +++ b/packages/compiler/src/frontend/helper-tokens.ts @@ -1,8 +1,8 @@ /** Token fingerprints for the fixed bundler helpers we recognize. This is * deliberately not a JavaScript parser: regexes, templates, escaped names, - * numbers, and unknown punctuation refuse the helper. None occur in the - * canonical helpers. Every accepted token is represented in the result; - * comments and whitespace alone may disappear. */ + * numbers other than plain decimal integers, and unknown punctuation refuse + * the helper. None occur in the canonical helpers. Every accepted token is + * represented in the result; comments and whitespace alone may disappear. */ const operators = [ ">>>=", "===", @@ -157,6 +157,15 @@ export function helperTokens(text: string): string | null { result += fingerprint("s", token.value); i = token.end; previous = "string"; + } else if (/[0-9]/.test(ch)) { + // Plain decimal integers only (esbuild's `cb[keys(cb)[0]]`): a + // legacy octal, fraction, exponent, radix prefix, BigInt suffix or + // numeric separator refuses rather than aliasing another value. + const start = i++; + while (i < text.length && /[0-9]/.test(text.charAt(i))) i++; + if (/[A-Za-z0-9_$.\\]/.test(text.charAt(i)) || (ch === "0" && i - start > 1)) return null; + previous = text.slice(start, i); + result += fingerprint("n", previous); } else if (/[A-Za-z_$]/.test(ch)) { const start = i++; while (i < text.length && /[A-Za-z0-9_$]/.test(text.charAt(i))) i++; diff --git a/packages/compiler/src/frontend/inferred-js-diagnostics.ts b/packages/compiler/src/frontend/inferred-js-diagnostics.ts index 7095cea5d..b21d19cb0 100644 --- a/packages/compiler/src/frontend/inferred-js-diagnostics.ts +++ b/packages/compiler/src/frontend/inferred-js-diagnostics.ts @@ -16,7 +16,7 @@ export function inferredJsDiagnosticSuppressed( const checker = program.getTypeChecker(); let suppressed = false; ts.walkPreorder(file, (node) => { - if (node.getEnd() < diagnostic.pos || node.getStart(file) > diagnostic.end) return "skip"; + if (node.end < diagnostic.pos || ts.tokenStartOfNode(node) > diagnostic.end) return "skip"; if ( diagnostic.code === 2565 && isJsSourceFileName(file.fileName) && @@ -30,7 +30,7 @@ export function inferredJsDiagnosticSuppressed( ts.isPropertyDeclaration(decl) && !decl.type && !decl.initializer && - !/@type\b/.test(file.text.slice(decl.pos, decl.getStart(file))), + !/@type\b/.test(file.text.slice(decl.pos, ts.tokenStartOfNode(decl))), ) ) { // Bare JS fields exist with value undefined before the first write; @@ -44,14 +44,14 @@ export function inferredJsDiagnosticSuppressed( const declaration = signature && signature.declaration; if ( declaration && - isJsSourceFileName(declaration.getSourceFile().fileName) && + isJsSourceFileName(ts.getSourceFileOfNode(declaration).fileName) && (ts.isFunctionDeclaration(declaration) || ts.isFunctionExpression(declaration) || ts.isMethodDeclaration(declaration)) && node.arguments.nodes.length > declaration.parameters.nodes.length && - node.arguments.nodes[declaration.parameters.nodes.length]?.getStart(file) === + ts.tokenStartOfNode(node.arguments.nodes[declaration.parameters.nodes.length]!) === diagnostic.pos && - node.arguments.nodes[node.arguments.nodes.length - 1]?.getEnd() === diagnostic.end && + node.arguments.nodes[node.arguments.nodes.length - 1]!.end === diagnostic.end && bodyReadsArguments(declaration) ) { suppressed = true; @@ -68,7 +68,7 @@ export function inferredJsDiagnosticSuppressed( const iterable = checker.getTypeAtLocation(node.expression); const iterator = checker .getPropertiesOfType(iterable) - .find((property) => property.name.startsWith("__@iterator@")); + .find((property) => ts.symbolName(property).startsWith("__@iterator@")); if (!iterator) return undefined; const signatures = checker.getCallSignatures( checker.getTypeOfSymbolAtLocation(iterator, node.expression), @@ -88,7 +88,7 @@ export function inferredJsDiagnosticSuppressed( declaration && ts.isMethodDeclaration(declaration) && declaration.type === undefined && - isJsSourceFileName(declaration.getSourceFile().fileName) && + isJsSourceFileName(ts.getSourceFileOfNode(declaration).fileName) && declaration.body?.statements?.nodes.length === 1 && ts.isThrowStatement(declaration.body.statements.nodes[0]!) ) { diff --git a/packages/compiler/src/frontend/input-tracker.ts b/packages/compiler/src/frontend/input-tracker.ts index 304a21cfa..fa2e92568 100644 --- a/packages/compiler/src/frontend/input-tracker.ts +++ b/packages/compiler/src/frontend/input-tracker.ts @@ -164,8 +164,28 @@ export function markFrontendInputsUnstable(): void { (synchronousTracker ?? activeTracker.getStore())?.markUnstable(); } +/** Compiler-synthesized module sources (a bundle's CommonJS chunks split + * into sibling modules, npm-static-bundled-chunks.ts). They answer like + * regular files but are never recorded: each derives from the content of + * a recorded bundle read, which already keys every cache that sees it. */ +const virtualSources = new Map(); + +export function setVirtualSource(path: string, text: string): void { + virtualSources.set(resolve(path), text); +} + +export function isVirtualSource(path: string): boolean { + return virtualSources.has(resolve(path)); +} + +export function clearVirtualSources(): void { + virtualSources.clear(); +} + export function trackedReadFile(path: string): string | null { path = resolve(path); + const virtual = virtualSources.get(path); + if (virtual !== undefined) return virtual; try { const text = readFileSync(path, "utf8"); record({ op: "file", path, digest: frontendSourceDigest(text) }); @@ -178,6 +198,7 @@ export function trackedReadFile(path: string): string | null { export function trackedFileExists(path: string): boolean { path = resolve(path); + if (virtualSources.has(path)) return true; const kind = pathKind(path); record({ op: "kind", path, kind }); return kind === "file"; @@ -185,6 +206,7 @@ export function trackedFileExists(path: string): boolean { export function trackedDirectoryExists(path: string): boolean { path = resolve(path); + if (virtualSources.has(path)) return false; const kind = pathKind(path); record({ op: "kind", path, kind }); return kind === "directory"; @@ -192,6 +214,7 @@ export function trackedDirectoryExists(path: string): boolean { export function trackedExists(path: string): boolean { path = resolve(path); + if (virtualSources.has(path)) return true; const kind = pathKind(path); record({ op: "kind", path, kind }); return kind !== "missing"; @@ -199,6 +222,7 @@ export function trackedExists(path: string): boolean { export function trackedRealpath(path: string): string | null { path = resolve(path); + if (virtualSources.has(path)) return path; try { const target = systemRealpath(path); record({ op: "realpath", path, target }); @@ -223,9 +247,13 @@ export function trackedAccessibleEntries( } files.sort(); directories.sort(); - const answer = { files, directories }; - record({ op: "entries", path, files, directories }); - return answer; + record({ op: "entries", path, files: [...files], directories }); + for (const virtual of virtualSources.keys()) { + if (dirname(virtual) === path && !files.includes(basename(virtual))) + files.push(basename(virtual)); + } + files.sort(); + return { files, directories }; } catch { // Enumeration can fail even while the path remains a directory. Preserve // the failed operation itself so a permission/ACL repair invalidates the diff --git a/packages/compiler/src/frontend/lib-contract.ts b/packages/compiler/src/frontend/lib-contract.ts index 0432d06e2..28532ecd8 100644 --- a/packages/compiler/src/frontend/lib-contract.ts +++ b/packages/compiler/src/frontend/lib-contract.ts @@ -115,7 +115,7 @@ const CONTRACT_GLOBAL_TYPES = new Set(["Array", "ReadonlyArray", "Uint8Array"]); type TypeBindings = Map>; function locOf(file: ts.SourceFile, node: ts.Node): SrcLoc { - return { file: file.fileName, start: node.getStart(), end: node.end }; + return { file: file.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } function isExported(stmt: ts.Node): boolean { @@ -248,7 +248,7 @@ function typeShape( const lit = node.literal; if (ts.isStringLiteral(lit)) return { k: "stringLit", text: lit.text }; if (lit.kind === ts.SyntaxKind.NullKeyword) return { k: "absent", unit: "null" }; - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; } if (ts.isArrayTypeNode(node)) return { k: "array", elem: typeShape(file, node.elementType, bindings) }; @@ -280,15 +280,19 @@ function typeShape( if (ts.isConditionalTypeNode(node)) { return { k: "unsupported", - text: `a conditional type (${node.getText(file)})`, + text: `a conditional type (${ts.getTextOfNode(node)})`, computed: "conditional", }; } if (ts.isMappedTypeNode(node)) { - return { k: "unsupported", text: `a mapped type (${node.getText(file)})`, computed: "mapped" }; + return { + k: "unsupported", + text: `a mapped type (${ts.getTextOfNode(node)})`, + computed: "mapped", + }; } if (ts.isTypeReferenceNode(node)) { - if (!ts.isIdentifier(node.typeName)) return { k: "unsupported", text: node.getText(file) }; + if (!ts.isIdentifier(node.typeName)) return { k: "unsupported", text: ts.getTextOfNode(node) }; const name = node.typeName.text; const global = CONTRACT_GLOBAL_TYPES.has(name) && isUnshadowedGlobalType(file, node, name, bindings); @@ -302,10 +306,10 @@ function typeShape( return { k: "array", elem: typeShape(file, node.typeArguments.nodes[0]!, bindings) }; } if ((node.typeArguments?.nodes.length ?? 0) > 0) - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; return { k: "ref", name }; } - return { k: "unsupported", text: node.getText(file) }; + return { k: "unsupported", text: ts.getTextOfNode(node) }; } /** Unwrap `expr as const` / parenthesized initializers. */ @@ -362,7 +366,7 @@ function envMsgArray(expr: ts.Expression): { env: string; msg: string }[] | null /** One mergeable declaration site, `file:line` (1-based line). */ function siteOf(file: ts.SourceFile, node: ts.Node): string { - return `${file.fileName}:${ts.getLineAndCharacterOfPosition(file, node.getStart()).line + 1}`; + return `${file.fileName}:${ts.getLineAndCharacterOfPosition(file, ts.tokenStartOfNode(node)).line + 1}`; } /** Everything the sidecar projection needs from the program's syntax diff --git a/packages/compiler/src/frontend/lib-exports.ts b/packages/compiler/src/frontend/lib-exports.ts index 738dd2f19..6a42074bd 100644 --- a/packages/compiler/src/frontend/lib-exports.ts +++ b/packages/compiler/src/frontend/lib-exports.ts @@ -35,7 +35,7 @@ export function entryFunctionExports(entry: ts.SourceFile): Map 0, async: mods?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true, generator: stmt.asteriskToken !== undefined, diff --git a/packages/compiler/src/frontend/lowering/array-values.ts b/packages/compiler/src/frontend/lowering/array-values.ts index 8f2b28a3f..0ca8503df 100644 --- a/packages/compiler/src/frontend/lowering/array-values.ts +++ b/packages/compiler/src/frontend/lowering/array-values.ts @@ -8,6 +8,7 @@ import { type IrType, isIntegerBytesElem, type SrcLoc, + STRING, UNDEFINED_T, typeEquals, typeKey, @@ -84,29 +85,54 @@ export function arrayValueRead( }; } -/** Read a stabilized union of native arrays without copying either the - * receiver or its elements. Every arm contributes its missing-value case. */ +/** Read one position of a stabilized union of native arrays and tuples + * (`const [a, b] = re.exec(s) || ["", ""]`) without copying the receiver + * or its elements. Every array arm contributes its missing-value case; a + * tuple arm answers its field, or undefined past its end. Tuple arms need + * a constant position. With `stringChars` (the string iterator's code + * points as a fresh string[]), a string arm (`re.exec(s) || ""`) answers + * its code point at the position, or undefined past its end. */ export function unionArrayValueRead( lowerer: Lowerer, value: IrExpr, index: IrExpr, loc: SrcLoc, + stringChars?: (text: IrExpr) => IrExpr, ): IrExpr | null { if (value.type.kind !== "union") return null; const unionId = value.type.unionId; const arms = lowerer.unions.get(unionId)?.arms; if (!arms?.length) return null; + const position = index.kind === "numLit" ? String(index.value) : null; + // A tuple arm's answer at the position: its field, or undefined past + // its end. Null when the arm is neither an array nor a tuple. + const tupleField = (arm: IrType): { name: string; type: IrType } | "past" | null => { + if (arm.kind !== "record" || position === null) return null; + const shape = lowerer.shapes.get(arm.shapeId); + if (!shape?.tuple) return null; + return shape.fields.find((f) => f.name === position) ?? "past"; + }; // Keep the collection's IrType union layout and narrow each element at // its use; an inferred array predicate changes the collection's ABI. - for (const arm of arms) if (arm.kind !== "array") return null; + const stringArm = (arm: IrType): boolean => arm.kind === "string" && stringChars !== undefined; + for (const arm of arms) + if (arm.kind !== "array" && !stringArm(arm) && tupleField(arm) === null) return null; const answers = new Map(); for (const arm of arms) { - if (arm.kind !== "array") return null; - const answer = arrayValueType(lowerer, arm.elem); + const field = arm.kind === "array" || stringArm(arm) ? null : tupleField(arm); + const answer = + arm.kind === "array" + ? arrayValueType(lowerer, arm.elem) + : stringArm(arm) + ? arrayValueType(lowerer, STRING) + : field === "past" + ? UNDEFINED_T + : field!.type; const parts = answer.kind === "union" ? lowerer.unions.get(answer.unionId)!.arms : [answer]; for (const part of parts) answers.set(typeKey(part), part); } const joined = [...answers.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + if (joined.length === 1 && joined[0]!.kind === "undefinedT") return null; if ( joined.length > 1 && (!unionContainerArmsOk(joined) || @@ -118,12 +144,39 @@ export function unionArrayValueRead( let result: IrExpr | null = null; for (let tag = arms.length - 1; tag >= 0; tag--) { const arm = arms[tag]!; - if (arm.kind !== "array") return null; const receiver: IrExpr = { kind: "unionNarrow", unionId, tag, value, type: arm, loc }; - const read = lowerer.coerceToExpected( - arrayValueRead(lowerer, receiver, index, arm.elem, loc), - type, - ); + let read: IrExpr; + if (arm.kind === "array") { + read = lowerer.coerceToExpected( + arrayValueRead(lowerer, receiver, index, arm.elem, loc), + type, + ); + } else if (stringArm(arm)) { + read = lowerer.coerceToExpected( + arrayValueRead(lowerer, stringChars!(receiver), index, STRING, loc), + type, + ); + } else { + const field = tupleField(arm); + if (field === null) return null; + if (field === "past") { + const missing = lowerer.wrappedUndefined(type, loc); + if (!missing) return null; + read = missing; + } else { + read = lowerer.coerceToExpected( + { + kind: "recordGet", + obj: receiver, + shapeId: (arm as IrType & { kind: "record" }).shapeId, + field: field.name, + type: field.type, + loc, + }, + type, + ); + } + } result = result === null ? read diff --git a/packages/compiler/src/frontend/lowering/assert-instances.ts b/packages/compiler/src/frontend/lowering/assert-instances.ts index ee8a34ea2..1166c6afa 100644 --- a/packages/compiler/src/frontend/lowering/assert-instances.ts +++ b/packages/compiler/src/frontend/lowering/assert-instances.ts @@ -194,7 +194,7 @@ function onlyMethodCallReferences(lowerer: Lowerer, name: ts.Identifier): boolea } ts.forEachChild(node, visit); }; - visit(name.getSourceFile()); + visit(ts.getSourceFileOfNode(name)); return ok; } diff --git a/packages/compiler/src/frontend/lowering/assertion-members.ts b/packages/compiler/src/frontend/lowering/assertion-members.ts new file mode 100644 index 000000000..484963fa8 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/assertion-members.ts @@ -0,0 +1,168 @@ +/* Members a type assertion attaches to record values. */ +import * as ts from "../ts7/adapter.js"; +import type { Lowerer } from "./lowerer.js"; +import { type IrType, typeEquals } from "../../ir/ir.js"; +import { hostsAll } from "./expressions/hosted-narrowing.js"; + +/** `route as Route & { order: number; internal: boolean }` followed by + * `view.internal = true` adds a property to the very object `route` + * names: Node has no layouts, so every later read of `route` (its JSON, + * its keys, a spread) sees the member. Native records are closed structs, + * and the asserted intersection maps to a different struct, so neither a + * conversion (a copy that severs the write from `route`) nor an erasure + * (a struct without the slot) is faithful. + * + * This pass finds such assertions before any body lowers and gives each + * record arm of the operand's type the asserted members it does not + * declare, as optional fields. Values the program builds hold them absent, + * which is invisible to every key surface (`in`, Object.keys, JSON, + * inspect, spread). The asserted type then refines the stored arms + * instead of naming new ones (hosted-narrowing.ts), so the assertion keeps + * the value and its writes land in the stored object. + * + * Only the plain form qualifies: one intersection whose constituents are + * the operand's own type and type literals of required property + * signatures, over plain data types (object types, aliases and interfaces; + * not classes or the standard library's types). Arms that are tuples or + * carry an index signature, members whose type does not map, and members + * two assertions type differently are left alone: those assertions keep + * their exact-shape diagnostics. Assertions whose operand already declares + * every member are skipped before any type maps, so programs without the + * form intern exactly the types they did before. */ +export function extendAssertedRecordMembers( + lowerer: Lowerer, + files: readonly ts.SourceFile[], +): void { + const additions = new Map>(); + const conflicts = new Set(); + const consider = (node: ts.AsExpression | ts.TypeAssertion): void => { + let typeNode: ts.TypeNode = node.type; + while (ts.isParenthesizedTypeNode(typeNode)) typeNode = typeNode.type; + if (!ts.isIntersectionTypeNode(typeNode)) return; + const parts = typeNode.types.nodes; + const literals = parts.filter((p) => ts.isTypeLiteralNode(p)); + const bases = parts.filter((p) => !ts.isTypeLiteralNode(p)); + if (literals.length === 0 || bases.length !== 1) return; + const memberNodes: { name: string; type: ts.TypeNode }[] = []; + for (const literal of literals) { + if (!ts.isTypeLiteralNode(literal)) return; + for (const member of literal.members.nodes) { + // A required member: the form that writes through the view. An + // optional one only reads, and keeps its exact-shape fence. + if (!ts.isPropertySignatureDeclaration(member) || !member.type || member.questionToken) + return; + const name = ts.isIdentifier(member.name) + ? member.name.text + : ts.isStringLiteral(member.name) + ? member.name.text + : null; + if (name === null || name.startsWith("%")) return; + memberNodes.push({ name, type: member.type }); + } + } + // Checker-only gate first: an assertion every constituent of whose + // operand type already declares the members (`e as Expr & { kind: + // "mul" }`, the refinement idiom) attaches nothing, and mapping its + // types here would intern them out of the usual order. + const operandTs = lowerer.typeOf(node.expression); + const constituents = operandTs.isUnionType() ? ts.constituentTypes(operandTs) : [operandTs]; + const declares = (t: ts.Type, name: string): boolean => + lowerer.checker.getPropertyOfType(t, name) !== undefined; + if (constituents.every((t) => memberNodes.every((m) => declares(t, m.name)))) return; + // Plain data types only: object literal types, aliases and interfaces + // (not classes, and not the standard library's types). + const dataType = (t: ts.Type): boolean => { + const symbol = t.getAliasSymbol() ?? t.getSymbol(); + return ( + !!symbol && + (symbol.flags & ts.SymbolFlags.Class) === 0 && + symbol.declarations.length > 0 && + symbol.declarations.every( + (d) => + !lowerer.isStdlibFile(ts.getSourceFileOfNode(d)) && + (ts.isTypeLiteralNode(d) || + ts.isInterfaceDeclaration(d) || + ts.isTypeAliasDeclaration(d)), + ) + ); + }; + if (!constituents.every(dataType)) return; + const operand = lowerer.mapTypeOf(operandTs); + const base = lowerer.mapTypeOf(lowerer.checker.getTypeFromTypeNode(bases[0]!)); + if (!operand || !base || !typeEquals(operand, base)) return; + const arms = + operand.kind === "union" ? (lowerer.unions.get(operand.unionId)?.arms ?? []) : [operand]; + if (arms.length === 0) return; + const shapeIds: string[] = []; + for (const arm of arms) { + if (arm.kind !== "record") return; + const shape = lowerer.shapes.get(arm.shapeId); + if (!shape || shape.tuple || shape.indexValue) return; + shapeIds.push(arm.shapeId); + } + const members: { name: string; type: IrType }[] = []; + for (const { name, type } of memberNodes) { + if (shapeIds.every((id) => lowerer.shapes.get(id)!.fields.some((f) => f.name === name))) + continue; + const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeFromTypeNode(type)); + if (!mapped || mapped.kind === "void") return; + const optional = lowerer.withUndefinedArmOf(mapped); + if (!optional) return; + members.push({ name, type: optional }); + } + for (const shapeId of shapeIds) { + const shape = lowerer.shapes.get(shapeId)!; + for (const member of members) { + if (shape.fields.some((f) => f.name === member.name)) continue; + let added = additions.get(shapeId); + if (!added) additions.set(shapeId, (added = new Map())); + const prior = added.get(member.name); + if (prior && !typeEquals(prior, member.type)) conflicts.add(`${shapeId}:${member.name}`); + else added.set(member.name, member.type); + } + } + }; + const visit = (node: ts.Node): void => { + if (ts.isAsExpression(node) || ts.isTypeAssertion(node)) consider(node); + ts.forEachChild(node, visit); + }; + for (const sf of files) { + if (sf.isDeclarationFile) continue; + // Cheap text gate: the form needs an intersection with a type literal. + if (!/&\s*\{/.test(sf.text)) continue; + visit(sf); + } + for (const [shapeId, added] of additions) { + const extra = [...added] + .filter(([name]) => !conflicts.has(`${shapeId}:${name}`)) + .map(([name, type]) => ({ name, type })); + lowerer.shapes.extendShape(shapeId, extra); + } + if (additions.size > 0) keepStoredTypeOfAssertedGlobals(lowerer, files); +} + +/** A module-level `const view = value as T & { member: X }` binding keeps + * the stored type of `value` once the members are attached, exactly as a + * local binding does (lowerVarDecl): the assertion keeps the object, and + * writes through the binding must reach it. Globals took their checker + * (or annotated) type during collection, before the members existed; an + * annotation the stored type hosts gives way to it the same way. */ +function keepStoredTypeOfAssertedGlobals(lowerer: Lowerer, files: readonly ts.SourceFile[]): void { + for (const sf of files) { + if (sf.isDeclarationFile) continue; + for (const statement of sf.statements.nodes) { + if (!ts.isVariableStatement(statement)) continue; + if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) continue; + for (const decl of statement.declarationList.declarations.nodes) { + if (!ts.isIdentifier(decl.name) || !decl.initializer) continue; + let init: ts.Expression = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (!ts.isAsExpression(init) && !ts.isTypeAssertion(init)) continue; + const global = lowerer.globalOf(decl.name); + const stored = lowerer.mapTypeOf(lowerer.typeOf(init.expression)); + if (!global || !stored || (stored.kind !== "union" && stored.kind !== "record")) continue; + if (hostsAll(lowerer, stored, global.type)) global.type = stored; + } + } + } +} diff --git a/packages/compiler/src/frontend/lowering/binding-analysis.ts b/packages/compiler/src/frontend/lowering/binding-analysis.ts index 88b0f91dc..83b9aa6f9 100644 --- a/packages/compiler/src/frontend/lowering/binding-analysis.ts +++ b/packages/compiler/src/frontend/lowering/binding-analysis.ts @@ -16,7 +16,7 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t let written = false; // Text pre-check keeps the file walk cheap: only same-named // identifiers pay a symbol resolution. - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; const scanTarget = (t: ts.Expression): void => { @@ -31,7 +31,7 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t if (ts.isArrayLiteralExpression(e) || ts.isObjectLiteralExpression(e)) { const walk = (n: ts.Node): void => { if (namesSym(n)) written = true; - else n.forEachChild(walk); + else ts.forEachChild(n, walk); }; walk(e); } @@ -54,9 +54,9 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t ) { scanTarget(n.initializer as ts.Expression); } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - decl.getSourceFile().forEachChild(visit); + ts.forEachChild(ts.getSourceFileOfNode(decl), visit); lowerer.neverReassignedCache.set(sym, !written); return !written; } @@ -123,7 +123,7 @@ export function nullishValueUnitOf( !decl || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name) || - decl.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(decl).isDeclarationFile || decl.initializer === undefined || !ts.isVariableStatement(decl.parent?.parent) ) { @@ -154,7 +154,7 @@ export function nullishValueUnitOf( /** True when any identifier resolving to `sym` sits inside a class * heritage clause anywhere in the program. */ function usedInHeritageClause(lowerer: Lowerer, sym: ts.Symbol): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); let found = false; const visit = (n: ts.Node): void => { if (found) return; @@ -171,12 +171,12 @@ function usedInHeritageClause(lowerer: Lowerer, sym: ts.Symbol): boolean { } return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const file of lowerer.program.getImplementationSourceFiles()) { if (found) break; if (file.isDeclarationFile) continue; - file.forEachChild(visit); + ts.forEachChild(file, visit); } return found; } @@ -236,7 +236,7 @@ export function nullishGenericBindingUnitOf( * value knowable. Compound assignments, ++/--, for-in/of cursors, and * destructuring targets all disqualify. */ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; let ok = true; @@ -253,7 +253,7 @@ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): bool } else if (ts.isArrayLiteralExpression(lhs) || ts.isObjectLiteralExpression(lhs)) { const walk = (m: ts.Node): void => { if (namesSym(m)) ok = false; - else m.forEachChild(walk); + else ts.forEachChild(m, walk); }; walk(lhs); } @@ -274,9 +274,9 @@ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): bool t = (t as ts.ParenthesizedExpression).expression; if (namesSym(t)) ok = false; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - decl.getSourceFile().forEachChild(visit); + ts.forEachChild(ts.getSourceFileOfNode(decl), visit); return ok; } @@ -332,7 +332,7 @@ export function deadUnmappableBinding( // the no-storage family never claims it (catch bindings sit outside a // variable statement too and stay out the same way). if (!ts.isVariableStatement(decl.parent?.parent)) return false; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (sf.isDeclarationFile || isJsSourceFile(sf)) return false; if (decl.initializer !== undefined && !sideEffectFreeValueExpr(lowerer, decl.initializer)) return false; @@ -344,7 +344,7 @@ export function deadUnmappableBinding( // bug), so only survivors of the syntactic filters pay it. Mappable // types keep their real storage. if (lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(sym)) !== null) return false; - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym; let dead = true; @@ -353,7 +353,7 @@ export function deadUnmappableBinding( if (ts.isIdentifier(n) && n.text === symText) { // Declaration-name occurrences are not reads. if (n.parent !== undefined && ts.isVariableDeclaration(n.parent) && n.parent.name === n) { - n.forEachChild(visit); + ts.forEachChild(n, visit); return; } // A plain-assignment LHS is a WRITE — dead only when the RHS @@ -375,12 +375,12 @@ export function deadUnmappableBinding( if (namesSym(n)) dead = false; return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const file of lowerer.program.getImplementationSourceFiles()) { if (!dead) break; if (file.isDeclarationFile) continue; - file.forEachChild(visit); + ts.forEachChild(file, visit); } if (dead) lowerer.deadBindings.add(sym); return dead; diff --git a/packages/compiler/src/frontend/lowering/builtin-calls.ts b/packages/compiler/src/frontend/lowering/builtin-calls.ts index 7e1852600..d2e4b53d5 100644 --- a/packages/compiler/src/frontend/lowering/builtin-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtin-calls.ts @@ -1,4 +1,5 @@ import { lowerReadlineCursorCall } from "./builtins/readline.js"; +import { lowerMonitorEventLoopDelayCall } from "./builtins/performance.js"; import { lowerEnvironmentData, lowerReceiveMessageOnPort, @@ -12,8 +13,11 @@ import { lowerEventsEmitterHelper, lowerEventsOnce } from "./lower-event-emitter import type { Lowerer } from "./lowerer.js"; import { lowerTimersMemberCall } from "./lower-timers.js"; import { lowerStreamModuleCall } from "./lower-stream.js"; +import { lowerCallbackPromisify } from "./builtins/promisify-callback.js"; import { lowerNodeModuleCall } from "./builtins/module-resolution.js"; import { builtinModuleFnOf, builtinFenceHintOf } from "./surfaces.js"; +import { resultIsDiscarded } from "./call-position.js"; +import { locOf, requireSpecOf } from "../program.js"; /** `events.addAbortListener(signal, listener)`: the runtime registers the * once/resist-stop-propagation abort listener (or queues the call for an @@ -53,6 +57,38 @@ export function lowerAddAbortListener( return { kind: "dynObjLit", fields: [{ key, value: dispose }], type: DYN, loc }; } +/** graceful-fs's `gracefulify(fsModule)` (graceful-fs is served as node:fs — + * builtin-modules.ts): it patches a module object with the retry queue, + * which statically lowered fs calls never consult, so the call statement + * is a no-op. The argument still evaluates unless it is a plain reference + * (the patched module, typically `require("fs")` or a namespace binding). + * The returned module object has no representation; other positions + * fence. */ +function lowerGracefulify(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { + const args = expr.arguments.nodes; + if (!resultIsDiscarded(expr) || args.length !== 1 || ts.isSpreadElement(args[0]!)) { + lowerer.noLowering( + "gracefulify", + expr, + "graceful-fs compiles as node:fs; gracefulify(fsModule) lowers as a statement whose result is unused", + ); + } + let arg: ts.Expression = args[0]!; + while (ts.isParenthesizedExpression(arg)) arg = arg.expression; + const plain = + ts.isIdentifier(arg) || + (ts.isCallExpression(arg) && requireSpecOf(arg) !== null) || + (ts.isPropertyAccessExpression(arg) && ts.isIdentifier(arg.expression)); + if (plain) return { kind: "seqExpr", stmts: [], result: dynUndefinedExpr(loc), type: DYN, loc }; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc: locOf(arg) }], + result: dynUndefinedExpr(loc), + type: DYN, + loc, + }; +} + /** Named imports, namespace members and callable aliases share validation * and dispatch. Resolve diagnostic provenance only if no handler accepts. */ export function lowerBuiltinCall( @@ -69,6 +105,9 @@ export function lowerBuiltinCall( return lowerAddAbortListener(lowerer, expr, loc); } if (bi.module === "events" && bi.member === "once") return lowerEventsOnce(lowerer, expr, loc); + if (bi.module === "perf_hooks" && bi.member === "monitorEventLoopDelay") { + return lowerMonitorEventLoopDelayCall(lowerer, expr); + } if (bi.module === "events") { const helper = lowerEventsEmitterHelper(lowerer, expr, bi.member, loc); if (helper) return helper; @@ -87,6 +126,10 @@ export function lowerBuiltinCall( if (testServed) return testServed; const utilServed = lowerer.lowerUtilModuleCall(expr, bi, loc); if (utilServed) return utilServed; + if (bi.module === "util" && bi.member === "promisify") { + const promisified = lowerCallbackPromisify(lowerer, expr, loc); + if (promisified) return promisified; + } const streamServed = lowerStreamModuleCall(lowerer, expr, bi, loc); if (streamServed) return streamServed; const fsTs = lowerer.lowerFsToUnixTimestampCall(expr, bi, loc); @@ -121,6 +164,8 @@ export function lowerBuiltinCall( if (bi.module === "worker_threads" && bi.member === "markAsUncloneable") { return lowerUncloneable(lowerer, expr); } + if (bi.module === "fs" && bi.member === "gracefulify") + return lowerGracefulify(lowerer, expr, loc); const builtinFn = builtinModuleFnOf(lowerer, bi.module, bi.member); if (!builtinFn) { lowerer.noLowering( diff --git a/packages/compiler/src/frontend/lowering/builtin-function-fences.ts b/packages/compiler/src/frontend/lowering/builtin-function-fences.ts index e6221384b..312596c1f 100644 --- a/packages/compiler/src/frontend/lowering/builtin-function-fences.ts +++ b/packages/compiler/src/frontend/lowering/builtin-function-fences.ts @@ -25,7 +25,7 @@ export function builtinFunctionFenceValue( `library functions as values ('${display}' has no function-value lowering; call it directly)`, ); lowerer.runtimeFences.push(diag); - const line = ts.getLineAndCharacterOfPosition(node.getSourceFile(), loc.start).line + 1; + const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(node), loc.start).line + 1; lowerer.liftedFns.push({ name, params: [], diff --git a/packages/compiler/src/frontend/lowering/builtins/async-context.ts b/packages/compiler/src/frontend/lowering/builtins/async-context.ts index e6de99b09..18bd93a86 100644 --- a/packages/compiler/src/frontend/lowering/builtins/async-context.ts +++ b/packages/compiler/src/frontend/lowering/builtins/async-context.ts @@ -35,7 +35,7 @@ export function lowerDiagnosticsSubscriber(lowerer: Lowerer, node: ts.Expression if (ts.isIdentifier(node) && node.text === "setImmediate") { const sym = lowerer.checker.getSymbolAtLocation(node); const decls = sym ? sym.declarations : []; - if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d)))) { return { kind: "libCall", fn: "timers.setImmediateFnValue", diff --git a/packages/compiler/src/frontend/lowering/builtins/blob.ts b/packages/compiler/src/frontend/lowering/builtins/blob.ts index 3fc4ec173..94c8c7ffe 100644 --- a/packages/compiler/src/frontend/lowering/builtins/blob.ts +++ b/packages/compiler/src/frontend/lowering/builtins/blob.ts @@ -52,10 +52,10 @@ export function blobClassOf(lowerer: Lowerer, node: ts.Expression): BlobClass | function blobReceiverClass(lowerer: Lowerer, node: ts.Expression): BlobClass | null { const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node)); const sym = type.getAliasSymbol() ?? type.getSymbol(); - if (!sym || (sym.name !== "Blob" && sym.name !== "File") || !lowerer.isStdlibSymbol(sym)) { - return null; - } - return sym.name; + if (!sym) return null; + const name = ts.symbolName(sym); + if ((name !== "Blob" && name !== "File") || !lowerer.isStdlibSymbol(sym)) return null; + return name; } function memberNameOf(access: ts.PropertyAccessExpression | ts.ElementAccessExpression) { diff --git a/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts new file mode 100644 index 000000000..eaa08c9dd --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/builtin-class-dyn.ts @@ -0,0 +1,159 @@ +import { numLit } from "../../../ir/build.js"; +import { BOOL, DYN, RUNTIME_EMITTER_CLASS, type IrExpr, type SrcLoc } from "../../../ir/ir.js"; +import * as ts from "../../ts7/adapter.js"; +import { locOf } from "../../program.js"; +import type { Lowerer } from "../lowerer.js"; + +/* EventEmitter and the stream classes as untyped constructor values, the + * bases of JavaScript constructor functions: + * + * function Bus() { EventEmitter.call(this); } + * util.inherits(Bus, EventEmitter); + * + * A compiled `class Bus extends EventEmitter` gets a native emitter prefix, + * but `new Bus()` on a constructor function builds an untyped object, so + * the base is the runtime's dyn constructor (scr_dyn_builtin_class.c): a + * function whose prototype carries Node's EventEmitter methods over the + * object's own `_events` table. The stream classes have prototype chains + * for util.inherits and instanceof; constructing over them refuses where + * it runs. These values appear only where a constructor function uses its + * base: util.inherits, `Base.call(this, …)` and `Base.apply(this, …)`, + * `Base.prototype` and `instanceof Base` on an untyped value. */ + +/** scr_dyn_builtin_class's kind for a runtime class, or null. */ +export function builtinClassKind(className: string): number | null { + switch (className) { + case RUNTIME_EMITTER_CLASS: + return 0; + case "%Readable": + return 2; + case "%Writable": + return 3; + case "%Duplex": + return 4; + case "%Transform": + return 5; + case "%PassThrough": + return 6; + default: + return null; + } +} + +/** The classes by builtin module member. JavaScript sources bind + * `require("events").EventEmitter` as a module member rather than a class + * value, and `require("stream")` is the legacy Stream constructor. */ +const MODULE_CLASS_KINDS: Readonly>>> = { + events: { EventEmitter: 0, default: 0 }, + stream: { Stream: 1, Readable: 2, Writable: 3, Duplex: 4, Transform: 5, PassThrough: 6 }, +}; + +/** The kind of a builtin module member that is one of those classes. */ +export function builtinMemberClassKind(module: string, member: string): number | null { + return MODULE_CLASS_KINDS[module]?.[member] ?? null; +} + +function unparenthesized(node: ts.Expression): ts.Expression { + let current = node; + while (ts.isParenthesizedExpression(current)) current = current.expression; + return current; +} + +/** The kind of a node that names one of those classes: a class value, a + * builtin module member, or the events and stream modules themselves. */ +export function builtinClassKindOf(lowerer: Lowerer, node: ts.Expression): number | null { + const expr = unparenthesized(node); + const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (mapped?.kind === "classval") return builtinClassKind(mapped.className); + const member = ts.isIdentifier(expr) + ? lowerer.builtinImportOf(expr) + : ts.isPropertyAccessExpression(expr) + ? lowerer.builtinMemberOf(expr) + : null; + if (member) return MODULE_CLASS_KINDS[member.module]?.[member.member] ?? null; + const module = lowerer.builtinNamespaceModuleOf(expr); + return module === "events" ? 0 : module === "stream" ? 1 : null; +} + +/** Whether evaluating the node can be skipped: a binding or a member of + * one, or of a builtin require. */ +function referenceOnly(node: ts.Expression): boolean { + const expr = unparenthesized(node); + if (ts.isIdentifier(expr)) return true; + if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) { + const base = unparenthesized(expr.expression); + return ( + ts.isIdentifier(base) || + (ts.isCallExpression(base) && + ts.isIdentifier(base.expression) && + base.expression.text === "require" && + base.arguments.nodes.length === 1 && + ts.isStringLiteral(base.arguments.nodes[0]!)) + ); + } + return false; +} + +/** The untyped constructor named by a node, or null when it names none of + * those classes. */ +export function builtinClassDynValueOf(lowerer: Lowerer, node: ts.Expression): IrExpr | null { + const kind = builtinClassKindOf(lowerer, node); + if (kind === null) return null; + const loc = locOf(node); + const value = builtinClassValue(kind, loc); + if (referenceOnly(node)) return value; + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(node), loc }], + result: value, + type: DYN, + loc, + }; +} + +/** EventEmitter and the legacy Stream are dyn.builtinClass; the stream + * classes, which carry native stream state for constructor functions, are + * dyn.builtinStreamClass, so only programs that use them link the stream + * half. */ +export function builtinClassValue(kind: number, loc: SrcLoc): IrExpr { + return { + kind: "libCall", + fn: kind <= 1 ? "dyn.builtinClass" : "dyn.builtinStreamClass", + args: [numLit(kind, loc)], + type: DYN, + loc, + }; +} + +/** `Base.call(this, …)` / `Base.apply(this, args)` on one of those class + * values: Function.prototype.call/apply over the untyped constructor. */ +export function lowerBuiltinClassCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + const callee = call.expression; + if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken) return null; + const method = callee.name.text; + if (method !== "call" && method !== "apply") return null; + if (call.arguments.nodes.some(ts.isSpreadElement)) return null; + const receiver = builtinClassDynValueOf(lowerer, callee.expression); + if (receiver === null) return null; + const loc = locOf(call); + return { + kind: "dynInvoke", + recv: receiver, + method, + calleeName: ts.getTextOfNode(callee), + args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), + type: DYN, + loc, + }; +} + +/** `value instanceof Base` for an untyped value. */ +export function builtinClassInstanceOf(value: IrExpr, kind: number, loc: SrcLoc): IrExpr { + return { + kind: "libCall", + fn: "dyn.builtinInstanceOf", + args: [value, numLit(kind, loc)], + type: BOOL, + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts b/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts index 1669978af..661fec528 100644 --- a/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts +++ b/packages/compiler/src/frontend/lowering/builtins/callable-aliases.ts @@ -1,11 +1,12 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; -import { isJsSourceFile } from "../../program.js"; +import { isJsSourceFile, locOf } from "../../program.js"; import { builtinModuleFnOf } from "../surfaces.js"; import { registerHttpClientFnBinding } from "../lower-server.js"; import { isPromisifiedZlibMember } from "./compression.js"; import { isPromisifiedCryptoMember } from "./crypto.js"; import { isPromisifiedDnsMember } from "../lower-dgram.js"; +import { callbackPromisifyShape } from "./promisify-callback.js"; /** Compile-time util.promisify projections. The immutable result binding * owns no runtime slot: calls rewrite to the target's promise lowering, @@ -49,7 +50,7 @@ export function registerBuiltinCallableAlias( // These checked native functions retain a runtime slot in JavaScript so // aliases can escape without narrowing their arguments to string. if ( - isJsSourceFile(nameNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(nameNode)) && target.module === "util" && ["stripVTControlCharacters", "toUSVString", "isDeepStrictEqual", "styleText"].includes( target.member, @@ -164,11 +165,14 @@ export function registerPromisifiedBuiltinDecl( : target?.module === "dns" && isPromisifiedDnsMember(target.member) ? { kind: "promisified-dns" as const, member: target.member } : null; + // A typed callback-last function value: the call lowers to a native + // promisified function, and the binding holds it like any closure. + if (!projection && callbackPromisifyShape(lowerer, e, locOf(e)) !== null) return false; if (!projection) { lowerer.noLowering( "util.promisify of this target", argNode ?? e, - "child_process.exec, child_process.execFile, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions (randomBytes, pbkdf2, scrypt, hkdf, randomFill, randomInt, generateKey), dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart are the supported targets", + "child_process.exec, child_process.execFile, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions (randomBytes, pbkdf2, scrypt, hkdf, randomFill, randomInt, generateKey), dns.lookup, dns.lookupService, the fs callback functions with an fs/promises counterpart, and typed callback-last functions whose callback takes (error?: Error | null, value?) are the supported targets", ); } const symbol = lowerer.checker.getSymbolAtLocation(nameNode); diff --git a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts index a435bf740..af7270ea9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts +++ b/packages/compiler/src/frontend/lowering/builtins/child-process-methods.ts @@ -1,5 +1,6 @@ import { boolLit, strLit } from "../../../ir/build.js"; import * as ts from "../../ts7/adapter.js"; +import { requestGeneralStreamSurface } from "../general-streams.js"; import { type Lowerer } from "../lowerer.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { isChildSurfaceMember } from "../surfaces.js"; @@ -121,22 +122,32 @@ export function lowerChildMethodCall( } const listenerNode = stripTypeCasts(call.arguments.nodes[1]!); if ( - isJsSourceFile(listenerNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && (ts.isArrowFunction(listenerNode) || ts.isFunctionExpression(listenerNode)) && listenerNode.parameters.nodes.some((param) => param.dotDotDotToken) && listenerNode.parameters.nodes.every((param) => !param.type) && !/@(?:param|type)\b/.test( - listenerNode.getSourceFile().text.slice(listenerNode.pos, listenerNode.getStart()), + ts + .getSourceFileOfNode(listenerNode) + .text.slice(listenerNode.pos, ts.tokenStartOfNode(listenerNode)), ) ) { for (const param of listenerNode.parameters.nodes) lowerer.checkedCallbackParams.add(param); } - const cb = lowerer.lowerExpr(call.arguments.nodes[1]!); + const narrowed = + event === "exit" || event === "close" + ? narrowedExitListenerParams(lowerer, listenerNode) + : []; + const cb = + narrowed.length > 0 + ? lowerWithCheckedParams(lowerer, listenerNode, narrowed) + : lowerer.lowerExpr(call.arguments.nodes[1]!); if ( - (isJsSourceFile(listenerNode.getSourceFile()) && cb.type.kind === "dyn") || + narrowed.length > 0 || + (isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && cb.type.kind === "dyn") || (cb.type.kind === "func" && (cb.type.rest || - (isJsSourceFile(listenerNode.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && cb.type.params.some((param) => param.kind === "dyn")))) ) { if ( @@ -301,10 +312,12 @@ export function lowerChildMethodCall( /** Method calls on piped child-output stream receivers (child.stdout / * child.stderr — the childStream kind): on/once("data" | "end"). - * 'data' listeners take zero parameters, `(chunk: Buffer)`, or a - * `Buffer | string`-union chunk (the ngrok appendOutput shape — the - * runtime only ever fires Buffers; the compiler-emitted adapter wraps - * the chunk at the union's Buffer arm); 'end' listeners take none. + * 'data' listeners take zero parameters, `(chunk: Buffer)`, `(chunk: + * string)`, an unknown/any chunk (boxed as a checked-dynamic value; see + * childStreamEncodedAt), or a `Buffer | string`-union chunk (the ngrok + * appendOutput shape — the runtime only ever fires Buffers; the + * compiler-emitted adapter wraps the chunk at the union's Buffer arm); + * 'end' listeners take none. * Statement position only (Node returns the stream for chaining; here * the result is void). Chained receivers (`child.stdout?.on(...)`) * ride the optional-chain re-dispatch (chainBlocked). Everything else @@ -335,6 +348,7 @@ export function lowerChildStreamMethodCall( const evT = lowerer.typeOf(call.arguments.nodes[0]!); const event = evT.isStringLiteralType() ? evT.value : null; if (event !== "data" && event !== "end") { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `stream.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments.nodes[0]!, @@ -342,6 +356,7 @@ export function lowerChildStreamMethodCall( ); } if (!ts.isExpressionStatement(call.parent)) { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.unsupported( "SC1090", call, @@ -376,23 +391,32 @@ export function lowerChildStreamMethodCall( if ( param !== undefined && param.kind !== "string" && + param.kind !== "dyn" && !(param.kind === "bytes" && param.elem === "u8") && !unionOk(param) ) { lowerer.unsupported( "SC1090", call.arguments.nodes[1]!, - `data listeners whose parameter is not 'Buffer', 'string', or a Buffer-armed union (got '${lowerer.fmt(param)}')`, + `data listeners whose parameter is not 'Buffer', 'string', 'unknown', or a Buffer-armed union (got '${lowerer.fmt(param)}')`, ); } + // An unknown/any chunk is a Buffer, or a string once setEncoding ran. + // The registration takes whichever the enclosing code establishes; a + // later setEncoding that contradicts it throws the runtime's + // listener-type TypeError instead of delivering the other kind. + const strings = + param?.kind === "string" || + (param?.kind === "dyn" && childStreamEncodedAt(lowerer, access.expression, call)); return { kind: "libCall", - fn: param?.kind === "string" ? "stream.onDataStr" : "stream.onData", + fn: strings ? "stream.onDataStr" : "stream.onData", args: [receiver, cb, once], type: VOID, loc, }; } + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `ReadableStream.${name}`, call, @@ -418,6 +442,7 @@ export function lowerChildWriterMethodCall( if (name === "write") { if (call.arguments.nodes.length !== 1 || call.arguments.nodes.some(ts.isSpreadElement)) { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `child stdin write with ${call.arguments.nodes.length} arguments`, call, @@ -461,6 +486,7 @@ export function lowerChildWriterMethodCall( const eventType = lowerer.typeOf(call.arguments.nodes[0]!); const event = eventType.isStringLiteralType() ? eventType.value : null; if (event !== "drain" && event !== "finish" && event !== "error") { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `child stdin ${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, call.arguments.nodes[0]!, @@ -507,6 +533,7 @@ export function lowerChildWriterMethodCall( return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc }; } + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `Writable.${name}`, call, @@ -514,3 +541,255 @@ export function lowerChildWriterMethodCall( lowerer.checker.getSymbolAtLocation(access.name), ); } + +/** A receiver spelled as a plain access path (`child.stdout`, `out`): its + * root symbol and member names, or null for other forms. A `const` alias + * of an access path (`const out = child.stdout`) resolves to that path, + * and so does a method call declared to return `this` + * (`child.stdout.setEncoding("utf8")`), so every spelling of one stream + * gets the same key. */ +function accessPathOf( + lowerer: Lowerer, + expr: ts.Expression, + depth = 0, +): { root: ts.Symbol; path: string } | null { + if (depth > 16) return null; + if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) { + return accessPathOf(lowerer, expr.expression, depth + 1); + } + if (ts.isIdentifier(expr)) { + const root = lowerer.checker.getSymbolAtLocation(expr); + if (!root) return null; + const init = constInitializerOf(root); + const aliased = init === null ? null : accessPathOf(lowerer, init, depth + 1); + return aliased ?? { root, path: "" }; + } + if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) { + const base = accessPathOf(lowerer, expr.expression, depth + 1); + return base === null ? null : { root: base.root, path: `${base.path}.${expr.name.text}` }; + } + if (returnsReceiver(lowerer, expr)) { + return accessPathOf( + lowerer, + (expr.expression as ts.PropertyAccessExpression).expression, + depth + 1, + ); + } + return null; +} + +/** A method call (`recv.m(...)`, not optional) whose declaration returns + * `this`, so its value is the receiver itself. */ +function returnsReceiver(lowerer: Lowerer, expr: ts.Expression): expr is ts.CallExpression { + if ( + !ts.isCallExpression(expr) || + expr.questionDotToken || + !ts.isPropertyAccessExpression(expr.expression) || + expr.expression.questionDotToken + ) + return false; + const declaration = lowerer.checker.getResolvedSignature(expr)?.declaration; + return declaration?.type?.kind === ts.SyntaxKind.ThisType; +} + +/** A call of a child_process factory (spawn, fork, exec, execFile), each + * of which returns a new ChildProcess. */ +function createsChildProcess(lowerer: Lowerer, call: ts.CallExpression): boolean { + const declaration = lowerer.checker.getResolvedSignature(call)?.declaration; + if ( + declaration === undefined || + !ts.isFunctionDeclaration(declaration) || + declaration.name === undefined || + !["spawn", "fork", "exec", "execFile"].includes(declaration.name.text) + ) + return false; + for (let node: ts.Node | undefined = declaration.parent; node; node = node.parent) { + if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { + return node.name.text === "child_process" || node.name.text === "node:child_process"; + } + } + return false; +} + +/** The statement that always evaluates `call` when it runs: the call is + * the statement's expression, a variable's initializer, or the receiver of + * a method chain that is one of those. Null for calls under a condition, + * an operator, an argument list or any other construct. */ +function unconditionalStatementOf(call: ts.CallExpression): ts.Statement | null { + let node: ts.Node = call; + for (;;) { + const parent: ts.Node | undefined = node.parent; + if (parent === undefined) return null; + if ( + ts.isParenthesizedExpression(parent) || + ts.isNonNullExpression(parent) || + (ts.isPropertyAccessExpression(parent) && + parent.expression === node && + !parent.questionDotToken) || + (ts.isCallExpression(parent) && parent.expression === node && !parent.questionDotToken) + ) { + node = parent; + continue; + } + if (ts.isExpressionStatement(parent)) return parent; + if (ts.isVariableDeclaration(parent) && parent.initializer === node) { + const list = parent.parent; + const statement = list?.parent; + return list !== undefined && + ts.isVariableDeclarationList(list) && + statement !== undefined && + ts.isVariableStatement(statement) + ? statement + : null; + } + return null; + } +} + +/** The initializer of a symbol declared once as a `const` variable, else null. */ +function constInitializerOf(symbol: ts.Symbol): ts.Expression | null { + const decls = symbol.declarations; + if (decls.length !== 1) return null; + const decl = decls[0]!; + if ( + !ts.isVariableDeclaration(decl) || + !ts.isIdentifier(decl.name) || + decl.initializer === undefined || + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) === 0 + ) + return null; + return decl.initializer; +} + +/** Whether two resolved access paths name different objects: the same root + * with different members, or two `const` roots each initialized by `new` + * or a child_process factory (such as two `spawn()` results). Any other + * call may return an object another binding already holds. */ +function distinctPaths( + lowerer: Lowerer, + a: { root: ts.Symbol; path: string }, + b: { root: ts.Symbol; path: string }, +): boolean { + if (a.root === b.root) return a.path !== b.path; + const fresh = (root: ts.Symbol): boolean => { + let init = constInitializerOf(root); + while (init !== null && ts.isParenthesizedExpression(init)) init = init.expression; + if (init === null) return false; + return ( + ts.isNewExpression(init) || (ts.isCallExpression(init) && createsChildProcess(lowerer, init)) + ); + }; + return fresh(a.root) && fresh(b.root); +} + +/** Whether an unknown-typed 'data' listener registered by `call` receives + * strings: a `setEncoding` statement on the same receiver path precedes + * the registration in its own block or an enclosing block of the same + * function. With no setEncoding on that path in the function, chunks are + * Buffers. Any other placement leaves the encoding state unclear, which + * is reported. */ +function childStreamEncodedAt( + lowerer: Lowerer, + receiver: ts.Expression, + call: ts.CallExpression, +): boolean { + const key = accessPathOf(lowerer, receiver); + const statement: ts.Node = call.parent!; + let scope: ts.Node | undefined = statement; + while (scope && !ts.isFunctionLike(scope) && !ts.isSourceFile(scope)) scope = scope.parent; + const unclear = (): never => + lowerer.unsupported( + "SC1090", + call.arguments.nodes[1]!, + "untyped child-stream 'data' listeners whose setEncoding state is unclear at their registration sites (declare the chunk as 'Buffer' or 'string')", + ); + if (key === null || scope === undefined) return unclear(); + const preceding = new Set(); + for (let node: ts.Node = statement; node !== scope;) { + const parent: ts.Node | undefined = node.parent; + if (parent === undefined) break; + if (ts.isBlock(parent) || ts.isSourceFile(parent)) { + for (const sibling of parent.statements.nodes) { + if (sibling === node) break; + preceding.add(sibling); + } + } + node = parent; + } + const receiverType = lowerer.checker.getNonNullableType(lowerer.typeOf(receiver)); + let before = false; + let elsewhere = false; + const visit = (node: ts.Node): void => { + if (node !== scope && ts.isFunctionLike(node)) return; + if ( + ts.isCallExpression(node) && + ts.isPropertyAccessExpression(node.expression) && + node.expression.name.text === "setEncoding" + ) { + const target = node.expression.expression; + const path = accessPathOf(lowerer, target); + if (path !== null && path.root === key.root && path.path === key.path) { + const statement = unconditionalStatementOf(node); + if (statement !== null && preceding.has(statement)) before = true; + else elsewhere = true; + } else if ( + (path === null || !distinctPaths(lowerer, path, key)) && + lowerer.checker.isTypeAssignableTo(receiverType, lowerer.typeOf(target)) + ) { + // Another spelling that can hold this stream (a parameter, a `let` + // copy, a call result): its setEncoding may be this stream's. + elsewhere = true; + } + } + ts.forEachChild(node, visit); + }; + visit(scope); + if (elsewhere) return unclear(); + return before; +} + +/** An inline 'exit'/'close' listener may declare `(code: number, signal: + * string)`: TypeScript accepts it through ChildProcess's catch-all `on` + * overload, while Node passes `number | null` and `Signals | null`. Those + * parameters bind as checked-dynamic values (the socket 'data' listener + * precedent), so the body observes the null Node passes; a typed use of a + * null value throws the checked conversion's TypeError. Returns the + * narrowed parameter symbols, empty when the declared types already + * admit null. */ +function narrowedExitListenerParams(lowerer: Lowerer, listener: ts.Expression): ts.Symbol[] { + if (!ts.isArrowFunction(listener) && !ts.isFunctionExpression(listener)) return []; + if (isJsSourceFile(ts.getSourceFileOfNode(listener))) return []; + const symbols: ts.Symbol[] = []; + const wanted: readonly string[] = ["f64", "string"]; + listener.parameters.nodes.slice(0, 2).forEach((param, i) => { + if (!ts.isIdentifier(param.name) || param.dotDotDotToken || param.initializer) return; + if (param.questionToken) return; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (!symbol || lowerer.runtimeOptionalBindingTypes.has(symbol)) return; + if (lowerer.paramShape(param).type.kind !== wanted[i]) return; + symbols.push(symbol); + }); + return symbols; +} + +/** Lowers an inline listener with the given parameters bound as + * checked-dynamic values typed `unknown` for the body. */ +function lowerWithCheckedParams( + lowerer: Lowerer, + listener: ts.Expression, + symbols: readonly ts.Symbol[], +): IrExpr { + const previousImplicit = lowerer.implicitParamTypes; + const implicit = new Map(previousImplicit ?? []); + for (const symbol of symbols) { + implicit.set(symbol, lowerer.checker.getUnknownType()); + lowerer.runtimeOptionalBindingTypes.set(symbol, DYN); + } + lowerer.implicitParamTypes = implicit; + try { + return lowerer.lowerExpr(listener); + } finally { + for (const symbol of symbols) lowerer.runtimeOptionalBindingTypes.delete(symbol); + lowerer.implicitParamTypes = previousImplicit; + } +} diff --git a/packages/compiler/src/frontend/lowering/builtins/class-values.ts b/packages/compiler/src/frontend/lowering/builtins/class-values.ts index 416217716..4d073c264 100644 --- a/packages/compiler/src/frontend/lowering/builtins/class-values.ts +++ b/packages/compiler/src/frontend/lowering/builtins/class-values.ts @@ -97,7 +97,7 @@ export function builtinClassAliasOwnsNoSlot(lowerer: Lowerer, symbol: ts.Symbol) if (decl === undefined || !ts.isVariableDeclaration(decl)) return false; if (builtinMemberRequireDecl(decl.name, decl.initializer)) return false; if (streamClassAliasDecl(lowerer, decl.name, decl.initializer)) return false; - if (!isJsSourceFile(decl.getSourceFile())) return true; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) return true; let init = decl.initializer; while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression; if (init === undefined) return true; @@ -157,9 +157,10 @@ export function runtimeClassValueOfType(lowerer: Lowerer, type: ts.Type): string const symbol = type.getSymbol(); if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; let info = lowerer.builtinEmitterInfoOf(symbol) ?? lowerer.builtinStreamInfoOf(symbol); - if (!info && symbol.name.endsWith("Constructor")) { + if (!info && ts.symbolName(symbol).endsWith("Constructor")) { for (const [irName, rec] of RUNTIME_ERROR_CLASSES) { - if (`${rec.lib}Constructor` === symbol.name) info = lowerer.classes.get(irName) ?? null; + if (`${rec.lib}Constructor` === ts.symbolName(symbol)) + info = lowerer.classes.get(irName) ?? null; } } return info?.valueCtorParams !== undefined ? info.def.name : null; diff --git a/packages/compiler/src/frontend/lowering/builtins/compression.ts b/packages/compiler/src/frontend/lowering/builtins/compression.ts index e69011522..717e09933 100644 --- a/packages/compiler/src/frontend/lowering/builtins/compression.ts +++ b/packages/compiler/src/frontend/lowering/builtins/compression.ts @@ -136,7 +136,7 @@ export function zlibStreamModeOf( if (alias !== null) return alias.kind === "zlib" ? alias.mode : null; const target = symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol; - const mode = ZLIB_STREAM_MODES.get(target.name); + const mode = ZLIB_STREAM_MODES.get(ts.symbolName(target)); if (mode === undefined) return null; return target.declarations.some((d) => ts.isClassDeclaration(d) && isZlibFallbackDecl(lowerer, d)) ? mode @@ -144,7 +144,7 @@ export function zlibStreamModeOf( } function isZlibFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean { - const file = d.getSourceFile(); + const file = ts.getSourceFileOfNode(d); if (!lowerer.isStdlibFile(file) || isNodeTypesPath(file.fileName)) return false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { diff --git a/packages/compiler/src/frontend/lowering/builtins/constants.ts b/packages/compiler/src/frontend/lowering/builtins/constants.ts index 611805d94..35f0fc095 100644 --- a/packages/compiler/src/frontend/lowering/builtins/constants.ts +++ b/packages/compiler/src/frontend/lowering/builtins/constants.ts @@ -170,7 +170,7 @@ export function fsConstantMemberRead(expr: ts.PropertyAccessExpression): IrExpr type: DYN, loc, }; - return isJsSourceFile(expr.getSourceFile()) + return isJsSourceFile(ts.getSourceFileOfNode(expr)) ? read : { kind: "dynCheck", value: read, type: F64, loc }; } diff --git a/packages/compiler/src/frontend/lowering/builtins/crypto.ts b/packages/compiler/src/frontend/lowering/builtins/crypto.ts index cd490bc87..e6834abd4 100644 --- a/packages/compiler/src/frontend/lowering/builtins/crypto.ts +++ b/packages/compiler/src/frontend/lowering/builtins/crypto.ts @@ -240,7 +240,9 @@ function cryptoKdfInput(lowerer: Lowerer, node: ts.Expression): IrExpr { const symbol = lowerer.typeOf(node).getSymbol(); if ( type?.kind === "bytes" || - (symbol?.name === "ArrayBuffer" && lowerer.isStdlibSymbol(symbol)) + (symbol !== undefined && + ts.symbolName(symbol) === "ArrayBuffer" && + lowerer.isStdlibSymbol(symbol)) ) { const value = lowerer.coerceInto(node, lowerer.lowerExpr(node), DYN); return { kind: "libCall", fn: "bytes.bufferSource", args: [value], type: BYTES_U8, loc }; @@ -878,8 +880,8 @@ export function isCipherType(lowerer: Lowerer, type: ts.Type): boolean { const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( !!symbol && - CIPHER_TYPE_NAMES.has(symbol.name) && - symbol.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())) + CIPHER_TYPE_NAMES.has(ts.symbolName(symbol)) && + symbol.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) ); } @@ -898,7 +900,7 @@ function v8CalleeText(node: ts.Expression): string { if (ts.isPropertyAccessExpression(node)) return `${v8CalleeText(node.expression)}.${node.name.text}`; if (ts.isCallExpression(node)) return `${v8CalleeText(node.expression)}(...)`; - return node.getText(); + return ts.getTextOfNode(node); } /** Calls on native Cipheriv/Decipheriv handles dispatch through the @@ -959,7 +961,7 @@ export function lowerCryptoHashMethodCall( return { kind: "seqExpr", stmts: evaluated, - result: nodeThrowExpr(1, "", `${access.getText()} is not a function`, DYN, loc), + result: nodeThrowExpr(1, "", `${ts.getTextOfNode(access)} is not a function`, DYN, loc), type: DYN, loc, }; diff --git a/packages/compiler/src/frontend/lowering/builtins/dates.ts b/packages/compiler/src/frontend/lowering/builtins/dates.ts index 32fe09ab3..31be995e7 100644 --- a/packages/compiler/src/frontend/lowering/builtins/dates.ts +++ b/packages/compiler/src/frontend/lowering/builtins/dates.ts @@ -58,9 +58,8 @@ export function lowerDateCall( } return { kind: "libCall", fn: "date.now", args: [], type: F64, loc }; } - // Date.parse uses the same bounded date-string parser as - // new Date(dateString).getTime(); keep their NaN and TimeClip behavior - // identical for the ISO timestamps used by CLI applications. + // Date.parse uses the same date-string parser as new Date(dateString) + // (V8's grammar in the runtime), so their NaN and TimeClip behavior agree. if (lowerer.stdlibGlobalMember(access, "Date") === "parse") { if (call.arguments.nodes.length !== 1 || ts.isSpreadElement(call.arguments.nodes[0]!)) { lowerer.noLowering(`Date.parse with ${call.arguments.nodes.length} arguments`, call); diff --git a/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts b/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts index fb1c92257..1b9a9c03a 100644 --- a/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts +++ b/packages/compiler/src/frontend/lowering/builtins/disposable-stack.ts @@ -9,7 +9,12 @@ import { varRef } from "../../../ir/build.js"; function isStdlibDisposableStack(lowerer: Lowerer, node: ts.Expression): boolean { const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node)); for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) { - if (symbol?.name === "DisposableStack" && lowerer.isStdlibSymbol(symbol)) return true; + if ( + symbol !== undefined && + ts.symbolName(symbol) === "DisposableStack" && + lowerer.isStdlibSymbol(symbol) + ) + return true; } return false; } diff --git a/packages/compiler/src/frontend/lowering/builtins/errors.ts b/packages/compiler/src/frontend/lowering/builtins/errors.ts index 0313cdd10..d95442829 100644 --- a/packages/compiler/src/frontend/lowering/builtins/errors.ts +++ b/packages/compiler/src/frontend/lowering/builtins/errors.ts @@ -1,7 +1,16 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; -import { BOOL, CAUGHT, DYN, F64, type IrExpr, type IrType, STRING } from "../../../ir/ir.js"; +import { + BOOL, + CAUGHT, + DYN, + DYN_CLASS_PROPERTIES, + F64, + type IrExpr, + type IrType, + STRING, +} from "../../../ir/ir.js"; const ASSERTION_ERROR_PROPERTIES: ReadonlySet = new Set([ "actual", @@ -18,9 +27,10 @@ const ASSERTION_ERROR_PROPERTIES: ReadonlySet = new Set([ export function isExecExceptionType(lowerer: Lowerer, t: ts.Type): boolean { const sym = t.getSymbol(); return ( - sym?.name === "ExecException" && + sym !== undefined && + ts.symbolName(sym) === "ExecException" && sym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); } @@ -43,6 +53,20 @@ export function refuseExecExceptionCode(lowerer: Lowerer, node: ts.Node): never * declaration is not stdlib). Reads only: writes keep their fence (no * compiled program constructs an errno error). Null for non-error * receivers and non-stdlib members, so the chain keeps trying. */ +/** Error members with slots of their own: never read through the dynamic + * node, even when an intersection restates them. */ +const ERROR_SLOT_MEMBERS = new Set([ + "code", + "cause", + "stack", + "message", + "name", + "errno", + "syscall", + "path", + "dest", +]); + export function lowerErrorCodeProperty( lowerer: Lowerer, expr: ts.PropertyAccessExpression, @@ -126,7 +150,40 @@ export function lowerErrorCodeProperty( declared, ); } - const systemField = ["errno", "syscall", "path", "dest"].includes(expr.name.text); + // A member an `Error & { ... }` intersection adds beyond Error's own and + // the system-error metadata (`Object.assign(new Error(m), { status })`): + // an own property of the error's checked-dynamic node, read at the + // declared type (the intersection mapping admits only checkable types). + if ( + recvT.className === "%Error" && + !ERROR_SLOT_MEMBERS.has(expr.name.text) && + !expr.questionDotToken && + !lowerer.isStdlibMember(expr) && + lowerer.typeOf(expr.expression).isIntersectionType() + ) { + const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (declared && declared.kind !== "void") { + const receiver = lowerer.lowerExpr(expr.expression); + const loc = locOf(expr); + return lowerer.coerceToExpected( + { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(receiver, DYN), + key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + type: DYN, + loc, + }, + declared, + ); + } + } + // A program class under the receiver's class can carry its own `code` + // (a declared field or a property added through an untyped reference), + // which Node reads instead of the errno code: read through the error's + // dynamic view, which answers both. + const systemField = + ["errno", "syscall", "path", "dest"].includes(expr.name.text) || + (expr.name.text === "code" && programClassMayOwnCode(lowerer, recvT.className)); if ( !systemField && expr.name.text !== "code" && @@ -162,7 +219,9 @@ export function lowerErrorCodeProperty( type: DYN, loc, }, - lowerer.withUndefinedArm(expr.name.text === "errno" ? F64 : STRING), + expr.name.text === "code" + ? lowerer.envValueType() + : lowerer.withUndefinedArm(expr.name.text === "errno" ? F64 : STRING), ); } return { @@ -184,6 +243,19 @@ export function lowerErrorCodeProperty( }; } +/** Whether a class declared in the program, `className` or one of its + * subclasses, has its own `code` field or a dynamic property bag. The + * runtime error classes keep `code` in the errno slot. */ +function programClassMayOwnCode(lowerer: Lowerer, className: string): boolean { + for (const [name, info] of lowerer.classes) { + if (name.startsWith("%")) continue; + if (name !== className && !lowerer.isSubclassOf(name, className)) continue; + if (info.def.fields.some((f) => f.name === "code" || f.name === DYN_CLASS_PROPERTIES)) + return true; + } + return false; +} + /** Whether values of `type` are errors: class instances whose class is * %Error or extends it. A subclass of a class that does not extend Error * cannot be an error either, so the answer depends on the static class diff --git a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts index 3fbea04aa..49875e004 100644 --- a/packages/compiler/src/frontend/lowering/builtins/filesystem.ts +++ b/packages/compiler/src/frontend/lowering/builtins/filesystem.ts @@ -250,7 +250,7 @@ export function lowerFsLadderCall( ); } if (bi.module !== "fs" && bi.module !== "fs/promises") return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const args = expr.arguments.nodes; if (args.some(ts.isSpreadElement)) return null; // Every ladder argument crosses as a dyn value; an argument that @@ -1564,7 +1564,7 @@ export function lowerDirentMethodCall( if (call.questionDotToken || access.questionDotToken) return null; if (!lowerer.isStdlibMember(access)) return null; const recvSym = lowerer.typeOf(access.expression).getSymbol(); - if (recvSym?.name !== "Dirent") return null; + if (recvSym === undefined || ts.symbolName(recvSym) !== "Dirent") return null; const receiver = lowerer.lowerExpr(access.expression); if (receiver.type.kind !== "record") return null; const shape = lowerer.shapes.get(receiver.type.shapeId); diff --git a/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts b/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts index 22e524690..498f9b6d4 100644 --- a/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts +++ b/packages/compiler/src/frontend/lowering/builtins/fs-streams.ts @@ -19,7 +19,7 @@ import { } from "../../../ir/ir.js"; function isFsFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean { - const file = d.getSourceFile(); + const file = ts.getSourceFileOfNode(d); if (!lowerer.isStdlibFile(file) || isNodeTypesPath(file.fileName)) return false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { @@ -153,11 +153,13 @@ export function fsStreamClassOf( lowerer: Lowerer, symbol: ts.Symbol | null | undefined, ): "ReadStream" | "WriteStream" | null { - if (!symbol || (symbol.name !== "ReadStream" && symbol.name !== "WriteStream")) return null; + if (!symbol) return null; + const name = ts.symbolName(symbol); + if (name !== "ReadStream" && name !== "WriteStream") return null; const declared = symbol.declarations.some( (d) => ts.isClassDeclaration(d) && isFsFallbackDecl(lowerer, d), ); - return declared ? symbol.name : null; + return declared ? name : null; } /** `new fs.ReadStream(path, options)` / `new fs.WriteStream(...)`: the diff --git a/packages/compiler/src/frontend/lowering/builtins/ipc.ts b/packages/compiler/src/frontend/lowering/builtins/ipc.ts index cf9d96117..90448854d 100644 --- a/packages/compiler/src/frontend/lowering/builtins/ipc.ts +++ b/packages/compiler/src/frontend/lowering/builtins/ipc.ts @@ -1,7 +1,17 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; -import { BOOL, type IrExpr, type IrType, STRING, type SrcLoc } from "../../../ir/ir.js"; +import { + BOOL, + type IrExpr, + type IrStmt, + type IrType, + STRING, + type SrcLoc, + UNDEFINED_T, +} from "../../../ir/ir.js"; +import { nodeThrowExpr } from "../../../ir/build.js"; +import { processHasIpc } from "./process.js"; /** Method calls on ChildProcess receivers: `child.on("exit"|"error", cb)` * registers a listener with the event loop's child registry. The event @@ -86,8 +96,54 @@ export function ipcSendCallback(lowerer: Lowerer, node: ts.Expression): IrExpr { return cb; } +/** process.send in a program without fork targets. This binary never opens + * a channel then, so only a Node.js parent's NODE_CHANNEL_FD can define + * process.send, and that channel has no lowering: a call fences when it runs. + * Without any channel the calls behave as Node's do on an undefined member: + * `process.send?.(...)` skips its arguments and answers undefined, and + * `process.send(...)` evaluates them and throws a TypeError. The message is + * never sent, so its type is not checked. */ +function lowerChannelLessSend(lowerer: Lowerer, call: ts.CallExpression): IrExpr { + const loc = locOf(call); + const fence = (type: IrType): IrExpr => + nodeThrowExpr( + 0, + "SC2020", + "process.send over an IPC channel opened by a Node.js parent (NODE_CHANNEL_FD) is not supported yet", + type, + loc, + ); + const hasIpc = processHasIpc(lowerer, loc); + if (call.questionDotToken) { + const type = lowerer.mapTypeOf(lowerer.typeOf(call)) ?? BOOL; + const absent: IrExpr = + type.kind === "bool" + ? { kind: "boolLit", value: false, type: BOOL, loc } + : lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type, + ); + return { kind: "ternary", cond: hasIpc, then: fence(type), else_: absent, type, loc }; + } + const stmts: IrStmt[] = call.arguments.nodes.map((arg) => ({ + kind: "exprStmt", + expr: lowerer.lowerExpr(ts.isSpreadElement(arg) ? arg.expression : arg), + loc: locOf(arg), + })); + const result: IrExpr = { + kind: "ternary", + cond: hasIpc, + then: fence(BOOL), + else_: nodeThrowExpr(1, "", "process.send is not a function", BOOL, loc), + type: BOOL, + loc, + }; + return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: BOOL, loc }; +} + export function lowerProcessIpcSend(lowerer: Lowerer, call: ts.CallExpression): IrExpr { const loc = locOf(call); + if (lowerer.forkTargets.length === 0) return lowerChannelLessSend(lowerer, call); if ( call.arguments.nodes.length < 1 || call.arguments.nodes.length > 2 || diff --git a/packages/compiler/src/frontend/lowering/builtins/json.ts b/packages/compiler/src/frontend/lowering/builtins/json.ts index b48030ba5..54bcae4b5 100644 --- a/packages/compiler/src/frontend/lowering/builtins/json.ts +++ b/packages/compiler/src/frontend/lowering/builtins/json.ts @@ -504,7 +504,7 @@ function jsonNullishArgument(lowerer: Lowerer, node: ts.Expression): boolean { const symbol = lowerer.checker.getSymbolAtLocation(node); return ( symbol !== undefined && - symbol.declarations.every((decl) => lowerer.isStdlibFile(decl.getSourceFile())) + symbol.declarations.every((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/key-object.ts b/packages/compiler/src/frontend/lowering/builtins/key-object.ts index ff6c0b38b..7e680fa20 100644 --- a/packages/compiler/src/frontend/lowering/builtins/key-object.ts +++ b/packages/compiler/src/frontend/lowering/builtins/key-object.ts @@ -23,7 +23,7 @@ export function isKeyObjectClass(lowerer: Lowerer, node: ts.Expression): boolean export function isKeyObjectValue(lowerer: Lowerer, node: ts.Expression): boolean { const type = lowerer.typeOf(node); const sym = type.getAliasSymbol() ?? type.getSymbol(); - return !!sym && sym.name === "KeyObject" && lowerer.isStdlibSymbol(sym); + return !!sym && ts.symbolName(sym) === "KeyObject" && lowerer.isStdlibSymbol(sym); } /** `v instanceof KeyObject`. */ @@ -51,7 +51,7 @@ export function lowerKeyObjectCall(lowerer: Lowerer, call: ts.CallExpression): I kind: "dynInvoke", recv: lowerer.lowerExprExpecting(access.expression, DYN), method: access.name.text, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc: locOf(call), diff --git a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts index 86d93de2c..5465aeafa 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-bindings.ts @@ -23,6 +23,17 @@ import { stripTypeCasts } from "./arguments.js"; export function builtinImportOf( lowerer: Lowerer, ident: ts.Identifier, +): { module: string; member: string } | null { + const binding = builtinModuleBindingOf(lowerer, ident); + // node:stream/web's exports ARE the web stream globals: its bindings + // lower through the globals' own paths (stdlibGlobalNameOf resolves the + // alias to the same name), not through module-member tables. + return binding?.module === "stream/web" ? null : binding; +} + +function builtinModuleBindingOf( + lowerer: Lowerer, + ident: ts.Identifier, ): { module: string; member: string } | null { const symbol = ts.isShorthandPropertyAssignment(ident.parent) && ident.parent.name === ident @@ -124,7 +135,7 @@ export function builtinImportOf( if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = lowerer.checker.getAliasedSymbol(symbol); const tdecl = target.declarations[0]; - if (tdecl !== undefined && tdecl.getSourceFile().isDeclarationFile) { + if (tdecl !== undefined && ts.getSourceFileOfNode(tdecl).isDeclarationFile) { for ( let p: ts.Node | undefined = tdecl.parent; p !== undefined && !ts.isSourceFile(p); @@ -132,7 +143,7 @@ export function builtinImportOf( ) { if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) { const module = canonicalBuiltinModule(p.name.text); - if (module !== null) return { module, member: target.name }; + if (module !== null) return { module, member: ts.symbolName(target) }; break; } } @@ -286,14 +297,14 @@ export function createRequireCalleeFileOf( ): ts.SourceFile | null { const e = stripTypeCasts(callee); if (ts.isCallExpression(e)) { - return createRequireBaseCallOf(lowerer, e) !== null ? e.getSourceFile() : null; + return createRequireBaseCallOf(lowerer, e) !== null ? ts.getSourceFileOfNode(e) : null; } if (!ts.isIdentifier(e)) return null; const symbol = lowerer.checker.getSymbolAtLocation(e); const decl = symbol ? symbol.declarations[0] : undefined; if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined) return null; return createRequireBindingDecl(lowerer, decl.name, decl.initializer) - ? decl.getSourceFile() + ? ts.getSourceFileOfNode(decl) : null; } diff --git a/packages/compiler/src/frontend/lowering/builtins/module-calls.ts b/packages/compiler/src/frontend/lowering/builtins/module-calls.ts index 956eddb93..c85f2c777 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-calls.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-calls.ts @@ -48,6 +48,7 @@ import { } from "./operating-system.js"; import { lowerQuerystringParseCall, lowerQuerystringStringifyCall } from "./querystring.js"; import { lowerFsGlobCallbackCall, lowerFsGlobSyncCall, lowerFspGlobCall } from "./fs-glob.js"; +import { lowerV8HeapStatisticsCall } from "./v8.js"; export function lowerBuiltinModuleCall( lowerer: Lowerer, @@ -56,7 +57,7 @@ export function lowerBuiltinModuleCall( fn: BuiltinModuleFn, loc: SrcLoc, ): IrExpr { - const name = expr.expression.getText(); + const name = ts.getTextOfNode(expr.expression); if (bi.module === "util/types") { return lowerUtilTypeCall(lowerer, expr, bi, loc, name); } @@ -134,6 +135,9 @@ export function lowerBuiltinModuleCall( if (bi.module === "child_process" && (bi.member === "execFileSync" || bi.member === "execSync")) { return lowerer.lowerExecSyncCall(expr, bi.member === "execSync", loc); } + if (bi.module === "v8" && bi.member === "getHeapStatistics") { + return lowerV8HeapStatisticsCall(lowerer, expr, loc); + } if (bi.module === "os" && bi.member === "networkInterfaces") { return lowerOsNetworkInterfacesCall(lowerer, expr, loc); } diff --git a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts index afa9dc1ea..6b2e9e589 100644 --- a/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts +++ b/packages/compiler/src/frontend/lowering/builtins/module-resolution.ts @@ -292,7 +292,7 @@ export function lowerImportMetaResolveCall( ); } const result = resolveImportMetaRuntime( - call.getSourceFile().fileName, + ts.getSourceFileOfNode(call).fileName, specifier, lowerer.targetPlatform, lowerer.frontendServices, @@ -314,8 +314,8 @@ function requireResolverBaseFile(lowerer: Lowerer, receiver: ts.Expression): ts. const created = createRequireCalleeFileOf(lowerer, receiver); if (created !== null) return created; return lowerer.isStdlibGlobal(receiver, "require") && - !isNodeEsmFile(receiver.getSourceFile(), lowerer.program) - ? receiver.getSourceFile() + !isNodeEsmFile(ts.getSourceFileOfNode(receiver), lowerer.program) + ? ts.getSourceFileOfNode(receiver) : null; } diff --git a/packages/compiler/src/frontend/lowering/builtins/navigator.ts b/packages/compiler/src/frontend/lowering/builtins/navigator.ts index 637edaf3c..f90448c1e 100644 --- a/packages/compiler/src/frontend/lowering/builtins/navigator.ts +++ b/packages/compiler/src/frontend/lowering/builtins/navigator.ts @@ -20,7 +20,8 @@ export function isNavigatorGlobal(lowerer: Lowerer, node: ts.Expression): boolea if (!ts.isIdentifier(expr) || expr.text !== "navigator") return false; if (lowerer.isStdlibGlobal(expr, "navigator")) return true; return ( - isJsSourceFile(expr.getSourceFile()) && lowerer.checker.getSymbolAtLocation(expr) === undefined + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + lowerer.checker.getSymbolAtLocation(expr) === undefined ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/net-address.ts b/packages/compiler/src/frontend/lowering/builtins/net-address.ts index d99d63fd3..9e795bca6 100644 --- a/packages/compiler/src/frontend/lowering/builtins/net-address.ts +++ b/packages/compiler/src/frontend/lowering/builtins/net-address.ts @@ -31,8 +31,10 @@ export function netAddressClassOf(lowerer: Lowerer, node: ts.Expression): NetAdd function netAddressValueClass(lowerer: Lowerer, node: ts.Expression): NetAddressClass | null { const type = lowerer.typeOf(node); const sym = type.getAliasSymbol() ?? type.getSymbol(); - if (!sym || (sym.name !== "SocketAddress" && sym.name !== "BlockList")) return null; - return lowerer.isStdlibSymbol(sym) ? sym.name : null; + if (!sym) return null; + const name = ts.symbolName(sym); + if (name !== "SocketAddress" && name !== "BlockList") return null; + return lowerer.isStdlibSymbol(sym) ? name : null; } /** `new SocketAddress(options?)` and `new BlockList()`: the argument @@ -127,7 +129,7 @@ export function lowerNetAddressCall(lowerer: Lowerer, call: ts.CallExpression): kind: "dynInvoke", recv: lowerer.lowerExprExpecting(access.expression, DYN), method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/builtins/performance.ts b/packages/compiler/src/frontend/lowering/builtins/performance.ts index 2a9962b0f..72304e6a9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/performance.ts +++ b/packages/compiler/src/frontend/lowering/builtins/performance.ts @@ -18,6 +18,7 @@ import { } from "../../../ir/ir.js"; import { PERF_ENTRY_FIELDS, PERF_ENTRY_MARKER, perfEntryJsonShape } from "../../type-mapper.js"; import { builtinImportOf } from "./module-bindings.js"; +import { lowerOptionalArgument } from "../optional-arguments.js"; import { checkedIterableSpread } from "../checked-iterable-spread.js"; /** True for any spelling of node:perf_hooks' `performance` object — the @@ -134,6 +135,12 @@ export function lowerPerfHooksCall( loc, }; } + if ( + access.name.text === "eventLoopUtilization" && + isPerfHooksPerformanceExpr(lowerer, access.expression) + ) { + return lowerEventLoopUtilization(lowerer, expr); + } const timeline = PERF_TIMELINE_CALLS.get(access.name.text); if (timeline && isPerfHooksPerformanceExpr(lowerer, access.expression)) { return lowerPerfTimelineCall(lowerer, expr, access.name.text, timeline, loc); @@ -142,7 +149,7 @@ export function lowerPerfHooksCall( lowerer.noLowering( `perf_hooks performance.${access.name.text}`, expr, - "performance.now() (and its .bind(performance) function value), mark, measure, the getEntries family, clearMarks and clearMeasures are the lowered surface", + "performance.now() (and its .bind(performance) function value), mark, measure, the getEntries family, clearMarks, clearMeasures and eventLoopUtilization are the lowered surface", ); } return null; @@ -278,7 +285,7 @@ function lowerPerfArguments(lowerer: Lowerer, call: ts.CallExpression, loc: SrcL }); continue; } - const spelling = arg.expression.getText(); + const spelling = ts.getTextOfNode(arg.expression); const items = checkedIterableSpread(lowerer, lowerer.lowerExpr(arg.expression), spelling, loc); stmts.push({ kind: "exprStmt", @@ -513,3 +520,309 @@ export function fencePerfEntryKeyWalk( `${what} over performance entries (Node keeps their fields as prototype getters, so own-key walks see none; read the fields or call toJSON())`, ); } + +/* ── event-loop metrics: eventLoopUtilization and monitorEventLoopDelay ── */ + +/** The stdlib IntervalHistogram (monitorEventLoopDelay's result), by the + * receiver's checker type. Its value is the runtime histogram handle. */ +function isIntervalHistogram(lowerer: Lowerer, node: ts.Expression): boolean { + const symbol = lowerer.typeOf(node).getSymbol(); + return ( + symbol !== undefined && + ts.symbolName(symbol) === "IntervalHistogram" && + lowerer.isStdlibSymbol(symbol) + ); +} + +/** IntervalHistogram's numeric members, as perf.eldStat selectors. */ +const HISTOGRAM_STATS: ReadonlyMap = new Map([ + ["min", 0], + ["max", 1], + ["mean", 2], + ["stddev", 3], + ["count", 4], + ["exceeds", 5], +]); + +/** `h.min`, `h.max`, … on an IntervalHistogram: the runtime's current + * statistic (Node's hdr_histogram values; nanoseconds). Null for every + * other read. */ +export function lowerHistogramProperty( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, +): IrExpr | null { + const which = HISTOGRAM_STATS.get(expr.name.text); + if ( + which === undefined || + expr.questionDotToken || + !isIntervalHistogram(lowerer, expr.expression) + ) + return null; + const loc = locOf(expr); + return { + kind: "libCall", + fn: "perf.eldStat", + args: [lowerer.lowerExprExpecting(expr.expression, F64), numLit(which, loc)], + type: F64, + loc, + }; +} + +/** enable(), disable(), reset() and percentile(p) on an IntervalHistogram. + * Other members (the BigInt twins, the percentiles Map) fence by name. */ +export function lowerHistogramMethodCall( + lowerer: Lowerer, + expr: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (access.questionDotToken || !isIntervalHistogram(lowerer, access.expression)) return null; + const loc = locOf(expr); + const name = access.name.text; + const handle = (): IrExpr => lowerer.lowerExprExpecting(access.expression, F64); + const args = expr.arguments.nodes; + if ((name === "enable" || name === "disable" || name === "reset") && args.length === 0) { + const fn = + name === "enable" + ? "perf.eldEnable" + : name === "disable" + ? "perf.eldDisable" + : "perf.eldReset"; + return { kind: "libCall", fn, args: [handle()], type: name === "reset" ? VOID : BOOL, loc }; + } + if (name === "percentile" && args.length === 1 && !ts.isSpreadElement(args[0]!)) { + // Node validates first: NaN, p <= 0 and p > 100 throw ERR_OUT_OF_RANGE. + const h = lowerer.declareHiddenLocal("%histogram", F64); + const p = lowerer.declareHiddenLocal("%percentile", F64); + const pRef = (): IrExpr => varRef(p.id, F64, loc); + const cmp = (op: "!==" | "<=" | ">", left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "bin", + op, + left, + right, + type: BOOL, + loc, + }); + const or = (left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "logical", + op: "||", + left, + right, + type: BOOL, + loc, + }); + const outOfRange = or( + or(cmp("!==", pRef(), pRef()), cmp("<=", pRef(), numLit(0, loc))), + cmp(">", pRef(), numLit(100, loc)), + ); + const message: IrExpr = { + kind: "strConcat", + left: { + kind: "strLit", + value: 'The value of "percentile" is out of range. It must be > 0 && <= 100. Received ', + type: STRING, + loc, + }, + right: { kind: "toString", operand: pRef(), type: STRING, loc }, + type: STRING, + loc, + }; + const thrown: IrExpr = { + kind: "libCall", + fn: "error.nodeThrow", + args: [ + numLit(2, loc), + { kind: "strLit", value: "ERR_OUT_OF_RANGE", type: STRING, loc }, + message, + ], + type: F64, + loc, + }; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: h.id, init: handle(), loc }, + { kind: "varDecl", localId: p.id, init: lowerer.lowerExprExpecting(args[0]!, F64), loc }, + ], + result: { + kind: "ternary", + cond: outOfRange, + then: thrown, + else_: { + kind: "libCall", + fn: "perf.eldPercentile", + args: [varRef(h.id, F64, loc), pRef()], + type: F64, + loc, + }, + type: F64, + loc, + }, + type: F64, + loc, + }; + } + lowerer.noLowering( + `IntervalHistogram.${name}`, + expr, + "enable(), disable(), reset(), percentile(p) and the min, max, mean, stddev, count and exceeds readings are the lowered surface", + ); +} + +/** `monitorEventLoopDelay(options?)` from node:perf_hooks, in any import + * spelling (`lowerBuiltinCall`): a new sampling histogram handle. The + * options are an inline object literal; the runtime validates the + * resolution as Node does (an integer of at least 1). */ +export function lowerMonitorEventLoopDelayCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { + const loc = locOf(expr); + const args = expr.arguments.nodes; + let resolution: IrExpr = numLit(10, loc); + if (args.length > 1 || args.some(ts.isSpreadElement)) { + lowerer.noLowering( + "this monitorEventLoopDelay call form", + expr, + "pass at most one options object", + ); + } + if (args.length === 1) { + const options = args[0]!; + if (!ts.isObjectLiteralExpression(options)) { + lowerer.noLowering( + "monitorEventLoopDelay with a non-literal options object", + options, + "spell the options inline: monitorEventLoopDelay({ resolution: 20 })", + ); + } + for (const property of options.properties.nodes) { + const shorthand = ts.isShorthandPropertyAssignment(property); + if ( + (!shorthand && !ts.isPropertyAssignment(property)) || + !ts.isIdentifier(property.name) || + property.name.text !== "resolution" + ) { + lowerer.noLowering( + "monitorEventLoopDelay options other than resolution", + property, + "resolution is the one option", + ); + } + // An undefined resolution takes the default, as Node's destructuring. + resolution = lowerOptionalArgument( + lowerer, + shorthand ? property.name : property.initializer, + F64, + numLit(10, loc), + ); + } + } + return { kind: "libCall", fn: "perf.eldNew", args: [resolution], type: F64, loc }; +} + +/** performance.eventLoopUtilization(u1?, u2?) — Node's + * lib/internal/perf/event_loop_utilization.js over the runtime's loop + * start and idle time, one lifted helper per arity. */ +function lowerEventLoopUtilization(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { + const loc = locOf(expr); + const args = expr.arguments.nodes; + const resultT = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (resultT?.kind !== "record" || args.length > 2 || args.some(ts.isSpreadElement)) { + lowerer.noLowering("this performance.eventLoopUtilization call form", expr); + } + const helper = eluHelperOf(lowerer, args.length, resultT); + return { + kind: "call", + callee: helper, + args: args.map((arg) => lowerer.lowerExprExpecting(arg, resultT)), + type: resultT, + loc, + }; +} + +function eluHelperOf(lowerer: Lowerer, arity: number, recT: IrType & { kind: "record" }): string { + const name = `%perf.elu.${arity}`; + if (lowerer.liftedFns.some((f) => f.name === name)) return name; + const loc: SrcLoc = { file: "", start: 0, end: 0 }; + const f64Local = (id: string) => ({ id, name: id, type: F64, mutable: false }); + const params = Array.from({ length: arity }, (_, i) => ({ + localId: `u${i + 1}.0`, + name: `u${i + 1}`, + type: recT as IrType, + })); + const locals = [ + ...params.map((p) => ({ id: p.localId, name: p.name, type: recT as IrType, mutable: false })), + f64Local("ls.0"), + f64Local("idle.0"), + f64Local("active.0"), + ]; + const ref = (id: string): IrExpr => varRef(id, F64, loc); + const field = (param: string, f: string): IrExpr => ({ + kind: "recordGet", + obj: varRef(param, recT, loc), + shapeId: recT.shapeId, + field: f, + type: F64, + loc, + }); + const bin = (op: "+" | "-" | "/" | "<=", left: IrExpr, right: IrExpr): IrExpr => ({ + kind: "bin", + op, + left, + right, + type: op === "<=" ? BOOL : F64, + loc, + }); + const record = (idle: IrExpr, active: IrExpr, utilization: IrExpr): IrExpr => ({ + kind: "recordLit", + fields: [ + { name: "idle", value: idle }, + { name: "active", value: active }, + { name: "utilization", value: utilization }, + ], + type: recT, + loc, + }); + const result = (idle: IrExpr, active: IrExpr): IrStmt => ({ + kind: "return", + value: record(idle, active, bin("/", active, bin("+", idle, active))), + loc, + }); + const decl = (id: string, init: IrExpr): IrStmt => ({ kind: "varDecl", localId: id, init, loc }); + const body: IrStmt[] = [ + decl("ls.0", { kind: "libCall", fn: "perf.loopStart", args: [], type: F64, loc }), + { + kind: "if", + cond: bin("<=", ref("ls.0"), numLit(0, loc)), + then: [ + { kind: "return", value: record(numLit(0, loc), numLit(0, loc), numLit(0, loc)), loc }, + ], + else_: null, + loc, + }, + ]; + if (arity === 2) { + body.push( + decl("idle.0", bin("-", field("u1.0", "idle"), field("u2.0", "idle"))), + decl("active.0", bin("-", field("u1.0", "active"), field("u2.0", "active"))), + result(ref("idle.0"), ref("active.0")), + ); + } else { + body.push( + decl("idle.0", { kind: "libCall", fn: "perf.idleTime", args: [], type: F64, loc }), + decl( + "active.0", + bin( + "-", + bin("-", { kind: "libCall", fn: "perf.now", args: [], type: F64, loc }, ref("ls.0")), + ref("idle.0"), + ), + ), + arity === 0 + ? result(ref("idle.0"), ref("active.0")) + : result( + bin("-", ref("idle.0"), field("u1.0", "idle")), + bin("-", ref("active.0"), field("u1.0", "active")), + ), + ); + } + lowerer.liftedFns.push({ name, params, returnType: recT, locals, body, loc }); + return name; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/process.ts b/packages/compiler/src/frontend/lowering/builtins/process.ts index 555eaf84f..934abbbeb 100644 --- a/packages/compiler/src/frontend/lowering/builtins/process.ts +++ b/packages/compiler/src/frontend/lowering/builtins/process.ts @@ -3,6 +3,7 @@ import { envSnapshotHelper, lowerProcessLoadEnvFile } from "./environment.js"; import { dynUndefinedExpr, boolLit, numLit, strLit, varRef } from "../../../ir/build.js"; import { timerStyleCallback } from "../lower-timers.js"; import * as ts from "../../ts7/adapter.js"; +import { requestGeneralStreamSurface } from "../general-streams.js"; import { type Lowerer, own } from "../lowerer.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { isSafeToDiscard } from "../expressions/evaluation-safety.js"; @@ -17,6 +18,7 @@ import { type IrExpr, type IrLibFn, type IrStmt, + type IrType, STRING, type SrcLoc, UNDEFINED_T, @@ -26,10 +28,149 @@ import { funcOf, } from "../../../ir/ir.js"; import { lowerBuiltinLoaderValue } from "../lower-builtin-values.js"; +import { builtinFunctionValue } from "../lower-exprs.js"; import { canonicalBuiltinModule } from "../../builtin-modules.js"; import { lowerProcessIpcSend, ipcMessageListener, ipcDisconnectListener } from "./ipc.js"; import { lowerDiagnosticsSubscriber, lowerTracingArguments } from "./async-context.js"; +/** Whether the process has an IPC channel, Node's test for defining + * process.send and process.disconnect: a channel this fork child opened + * (programs with fork targets only), or a non-empty NODE_CHANNEL_FD from a + * Node.js parent, the variable Node itself tests at startup. Built from the + * existing environment read, so the presence test links no runtime code. */ +export function processHasIpc(lowerer: Lowerer, loc: SrcLoc): IrExpr { + const envT = lowerer.envValueType(); + const unionId = (envT as IrType & { kind: "union" }).unionId; + const stringTag = lowerer.armTag(unionId, STRING); + const value = lowerer.declareHiddenLocal("%channelFd", envT); + const fromNode: IrExpr = { + kind: "seqExpr", + stmts: [ + { + kind: "varDecl", + localId: value.id, + init: { + kind: "libCall", + fn: "process.envGet", + args: [strLit("NODE_CHANNEL_FD", loc)], + type: envT, + loc, + }, + loc, + }, + ], + result: { + kind: "logical", + op: "&&", + left: { + kind: "unionIsTag", + unionId, + tag: stringTag, + negated: false, + value: varRef(value.id, envT, loc), + type: BOOL, + loc, + }, + right: { + kind: "toBool", + operand: { + kind: "unionNarrow", + unionId, + tag: stringTag, + value: varRef(value.id, envT, loc), + type: STRING, + loc, + }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; + if (lowerer.forkTargets.length === 0) return fromNode; + return { + kind: "logical", + op: "||", + left: { kind: "libCall", fn: "process.ipcOpen", args: [], type: BOOL, loc }, + right: fromNode, + type: BOOL, + loc, + }; +} + +/** True when the value of `node` is consumed only by ToBoolean: a + * condition, the operand of `!`, `Boolean(node)`, or an operand of `&&` / + * `||` whose own value is consumed that way (or discarded, for the left + * operand). */ +export function isTruthinessOnlyRead(lowerer: Lowerer, node: ts.Expression): boolean { + let child: ts.Node = node; + let parent = node.parent; + while (parent !== undefined && ts.isParenthesizedExpression(parent)) { + child = parent; + parent = parent.parent; + } + if (parent === undefined) return false; + if (ts.isIfStatement(parent) || ts.isWhileStatement(parent) || ts.isDoStatement(parent)) + return parent.expression === child; + if (ts.isForStatement(parent)) return parent.condition === child; + if (ts.isConditionalExpression(parent)) return parent.condition === child; + if (ts.isPrefixUnaryExpression(parent)) return parent.operator === ts.SyntaxKind.ExclamationToken; + if (ts.isCallExpression(parent)) { + return ( + !parent.questionDotToken && + parent.arguments.nodes.length === 1 && + parent.arguments.nodes[0] === child && + lowerer.isStdlibGlobal(parent.expression, "Boolean") + ); + } + if (ts.isBinaryExpression(parent)) { + const op = parent.operatorToken.kind; + if (op !== ts.SyntaxKind.AmpersandAmpersandToken && op !== ts.SyntaxKind.BarBarToken) + return false; + if (isTruthinessOnlyRead(lowerer, parent)) return true; + let outer: ts.Node | undefined = parent.parent; + while (outer !== undefined && ts.isParenthesizedExpression(outer)) outer = outer.parent; + return parent.left === child && outer !== undefined && ts.isExpressionStatement(outer); + } + return false; +} + +/** Whether a program source reads process.exitCode. Such a program + * records its exit-code assignments (process.setExitCodeTracked), so a + * read can tell an unset code from 0; other programs keep the untracked + * write. Computed once per lowering pass. */ +export function programReadsExitCode(lowerer: Lowerer): boolean { + if (lowerer.readsExitCode !== null) return lowerer.readsExitCode; + let found = false; + const walk = (node: ts.Node): void => { + if (found) return; + if ( + ts.isPropertyAccessExpression(node) && + node.name.text === "exitCode" && + !( + ts.isBinaryExpression(node.parent) && + node.parent.left === node && + node.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) && + lowerer.stdlibGlobalMember(node, "process") === "exitCode" + ) { + found = true; + return; + } + ts.forEachChild(node, walk); + }; + for (const sf of lowerer.program.getImplementationSourceFiles()) { + if (sf.isDeclarationFile) continue; + walk(sf); + if (found) break; + } + lowerer.readsExitCode = found; + return found; +} + /** The pinned Node release the runtime reports as process.versions.node * (packages/runtime/src/scr_lib.c SCR_NODE_COMPAT_VERSION, kept equal to * internal/compatibility/node-v24.json by test/node-pin.test.ts). */ @@ -51,14 +192,14 @@ export function lowerProcStreamMethodCall( if (!lowerer.isStdlibMember(access)) return null; const name = access.name.text; const loc = locOf(call); - if (isJsSourceFile(call.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(call))) { const receiver = lowerer.lowerExpr(access.expression); if (receiver.type.kind === "dyn") { return { kind: "dynInvoke", recv: receiver, method: name, - calleeName: call.expression.getText(), + calleeName: ts.getTextOfNode(call.expression), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -73,7 +214,7 @@ export function lowerProcStreamMethodCall( kind: "dynInvoke", recv: receiver, method: name, - calleeName: call.expression.getText(), + calleeName: ts.getTextOfNode(call.expression), args: [], type: DYN, loc, @@ -85,6 +226,7 @@ export function lowerProcStreamMethodCall( const receiver = lowerer.lowerExpr(access.expression); const data = lowerer.lowerExpr(call.arguments.nodes[0]!); if (data.type.kind !== "string") { + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `write of '${lowerer.fmt(data.type)}' data on a stream value`, call.arguments.nodes[0]!, @@ -93,6 +235,7 @@ export function lowerProcStreamMethodCall( } return { kind: "libCall", fn: "procStream.write", args: [receiver, data], type: BOOL, loc }; } + requestGeneralStreamSurface(lowerer, access.expression); lowerer.noLowering( `WritableStream.${name}`, call, @@ -123,6 +266,58 @@ export function isOptionalProcessStreamProperty( return lowerer.mapTypeOf(lowerer.typeOf(expr.expression))?.kind === "procStream"; } +/** The process.versions entries @types/node declares as plain strings + * that the runtime's versions object (node and openssl only) lacks. */ +const ABSENT_DECLARED_VERSIONS: ReadonlySet = new Set([ + "http_parser", + "v8", + "ares", + "uv", + "zlib", + "modules", +]); + +/** A process.versions member read whose declaration promises a string the + * runtime does not have. @types/node declares the entries above as plain + * strings, so a checked string read would throw on the absent entry and + * equality with undefined would fold to false. These reads keep the + * undefined arm, like the optional stream properties. The shipped fallback + * types declare other entries `string | undefined` and never reach this. + * An element read with a computed key qualifies when the checker types it + * as a plain string. */ +export function isOptionalProcessVersionsRead(lowerer: Lowerer, node: ts.Node): boolean { + let key: string | null; + if (ts.isPropertyAccessExpression(node)) key = node.name.text; + else if (ts.isElementAccessExpression(node)) + key = ts.isStringLiteralLike(node.argumentExpression) ? node.argumentExpression.text : null; + else return false; + if (key !== null && !ABSENT_DECLARED_VERSIONS.has(key)) return false; + if (!programMentionsVersions(lowerer)) return false; + const symbol = lowerer.typeOf(node.expression).getSymbol(); + return ( + symbol?.name === "ProcessVersions" && + symbol.declarations.some( + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + ) && + lowerer.mapTypeOf(lowerer.typeOf(node))?.kind === "string" + ); +} + +const mentionsVersions = new WeakMap(); + +/** Whether any implementation source spells `versions`. Programs that + * never do skip the per-read checker queries above entirely. */ +function programMentionsVersions(lowerer: Lowerer): boolean { + let known = mentionsVersions.get(lowerer); + if (known === undefined) { + known = lowerer.program + .getImplementationSourceFiles() + .some((sf) => !sf.isDeclarationFile && sf.text.includes("versions")); + mentionsVersions.set(lowerer, known); + } + return known; +} + export function lowerProcessStreamProperty( lowerer: Lowerer, expr: ts.PropertyAccessExpression, @@ -133,7 +328,7 @@ export function lowerProcessStreamProperty( ? lowerer.stdlibGlobalMember(expr.expression, "process") : null; if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (direct === "stdin" || direct === "stdout" || direct === "stderr" || @@ -374,9 +569,45 @@ export function lowerProcessProperty( const member = lowerer.stdlibGlobalMember(expr, "process"); if (member === null) return null; const loc = locOf(expr); + // `if (process.send)`: Node defines process.send and process.disconnect + // only when the process has an IPC channel. A read whose value only + // feeds a truthiness test answers that presence; other value uses keep + // the member fence. + if ( + (member === "send" || member === "disconnect") && + !expr.questionDotToken && + isTruthinessOnlyRead(lowerer, expr) + ) { + return processHasIpc(lowerer, loc); + } if (member === "versions") { return { kind: "libCall", fn: "process.versions", args: [], type: DYN, loc }; } + // process.exitCode: undefined until something assigns it, then the + // assigned code. A read the checker narrowed to number follows an + // assignment, so the code is set. + if (member === "exitCode" && !expr.questionDotToken) { + const current: IrExpr = { + kind: "libCall", + fn: "process.currentExitCode", + args: [], + type: F64, + loc, + }; + if (lowerer.mapTypeOf(lowerer.typeOf(expr))?.kind === "f64") return current; + const type: IrType = { kind: "union", unionId: lowerer.unions.intern([F64, UNDEFINED_T]) }; + return { + kind: "ternary", + cond: { kind: "libCall", fn: "process.hasExitCode", args: [], type: BOOL, loc }, + then: lowerer.coerceToExpected(current, type), + else_: lowerer.coerceToExpected( + { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, + type, + ), + type, + loc, + }; + } // process.features as a value (`"sea" in process.features`): an object // holding none of the flags, consistent with the member reads above. if (member === "features") { @@ -403,6 +634,11 @@ export function lowerProcessProperty( if (member === "hrtime") { return { kind: "libCall", fn: "process.hrtimeValue", args: [], type: DYN, loc }; } + // A stored process.nextTick in JavaScript (`cond ? setImmediate : + // process.nextTick`): the interned builtin function value. + if (member === "nextTick" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { + return builtinFunctionValue("process.nextTick", loc); + } if (member === "getBuiltinModule") { // Keep the native loader's argument validation when the function is // stored. The declaration's string signature must not insert a checked @@ -483,7 +719,7 @@ export function lowerProcessProperty( // OUTER expressions dispatch first. if ( member === "stdin" || - ((member === "stdout" || member === "stderr") && isJsSourceFile(expr.getSourceFile())) + ((member === "stdout" || member === "stderr") && isJsSourceFile(ts.getSourceFileOfNode(expr))) ) { return { kind: "libCall", @@ -513,7 +749,10 @@ export function lowerProcessMethodCall( access: ts.PropertyAccessExpression, ): IrExpr | null { if (call.questionDotToken) return null; - if (isJsSourceFile(call.getSourceFile()) && ts.isPropertyAccessExpression(access.expression)) { + if ( + isJsSourceFile(ts.getSourceFileOfNode(call)) && + ts.isPropertyAccessExpression(access.expression) + ) { const stream = lowerer.stdlibGlobalMember(access.expression, "process"); if (stream === "stdin" || stream === "stdout" || stream === "stderr") { if (call.arguments.nodes.some(ts.isSpreadElement)) @@ -522,7 +761,7 @@ export function lowerProcessMethodCall( kind: "dynInvoke", recv: lowerer.lowerExpr(access.expression), method: access.name.text, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc: locOf(call), @@ -699,7 +938,7 @@ export function lowerProcessMethodCall( loc, }, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [ lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN), lowerer.lowerExprExpecting(call.arguments.nodes[1]!, DYN), @@ -896,7 +1135,7 @@ export function lowerProcessMethodCall( // `writeOut: (str) => process.stdout.write(str)` — str untyped): // a runtime string writes; anything else throws the dynCheck's // honest TypeError at the call. - if (data.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) { + if (data.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(call))) { data = { kind: "dynCheck", value: data, type: STRING, loc }; } const isBytes = data.type.kind === "bytes" && data.type.elem === "u8"; @@ -911,7 +1150,7 @@ export function lowerProcessMethodCall( let callback: IrExpr | undefined; if (callbackNode) { callback = lowerer.lowerExpr(callbackNode); - if (callback.type.kind === "dyn" && isJsSourceFile(call.getSourceFile())) { + if (callback.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(call))) { callback = { kind: "dynCheck", value: callback, @@ -1575,10 +1814,36 @@ export function lowerProcessMethodCall( } if (member === "exit") { const arg = call.arguments.nodes[0]; - const code: IrExpr = + let code: IrExpr = arg !== undefined ? lowerer.lowerExprExpecting(arg, F64) : { kind: "libCall", fn: "process.currentExitCode", args: [], type: F64, loc }; + // process.exit(code) assigns process.exitCode before the 'exit' + // listeners run. Only a program that reads the property can observe + // that, so only such a program records the assignment. + if (arg !== undefined && programReadsExitCode(lowerer)) { + const binding = lowerer.declareHiddenLocal("%exitCode", F64); + code = { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: binding.id, init: code, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "process.setExitCodeTracked", + args: [varRef(binding.id, F64, loc)], + type: VOID, + loc, + }, + loc, + }, + ], + result: varRef(binding.id, F64, loc), + type: F64, + loc, + }; + } return { kind: "libCall", fn: "process.exit", args: [code], type: VOID, loc }; } return null; // process.argv(...) etc. are tsc errors before lowering @@ -1594,6 +1859,16 @@ export function lowerProcessOptionalMethodCall( lowerer: Lowerer, expr: ts.CallExpression, ): IrExpr | null { + // `process.send!(message)`: the asserted direct call of the optional + // member (the @types/node spelling under strict null checks). + if ( + !expr.questionDotToken && + ts.isNonNullExpression(expr.expression) && + ts.isPropertyAccessExpression(expr.expression.expression) && + !expr.expression.expression.questionDotToken && + lowerer.stdlibGlobalMember(expr.expression.expression, "process") === "send" + ) + return lowerProcessIpcSend(lowerer, expr); if (!expr.questionDotToken) return null; if (!ts.isPropertyAccessExpression(expr.expression)) return null; const member = lowerer.stdlibGlobalMember(expr.expression, "process"); diff --git a/packages/compiler/src/frontend/lowering/builtins/promises.ts b/packages/compiler/src/frontend/lowering/builtins/promises.ts index 2656da18b..668ef01f8 100644 --- a/packages/compiler/src/frontend/lowering/builtins/promises.ts +++ b/packages/compiler/src/frontend/lowering/builtins/promises.ts @@ -1,15 +1,18 @@ -import { checkedPromiseAll } from "../checked-promise-all.js"; +import { checkedPromiseAll, thenReader } from "../checked-promise-all.js"; import { InternalCompilerError } from "../../../errors.js"; import * as ts from "../../ts7/adapter.js"; -import { type Lowerer } from "../lowerer.js"; +import { newFnCtx, type Lowerer } from "../lowerer.js"; +import { varRef } from "../../../ir/build.js"; import { maybePromiseResolve, maybePromiseUnionOf } from "../maybe-promise.js"; import { isJsSourceFile, locOf } from "../../program.js"; import { + BOOL, DYN, type IrExpr, type IrFunction, type IrStmt, type IrType, + UNDEFINED_T, VOID, arrayOf, isUnitType, @@ -17,13 +20,179 @@ import { typeKey, } from "../../../ir/ir.js"; +/** `Promise.all([p, q, r])` over DIFFERENT promise types — the checker's + * tuple overload, `Promise<[A, B, C]>`. One interned async helper per + * entry/result signature takes the entries as arguments, awaits the + * runtime combinator over the same promises (each widened to + * Promise, a type-only view), so settlement is Node's exactly: the + * first rejection in settlement order wins and every later one counts as + * handled; then it reads each (now fulfilled) entry and returns the + * tuple. Entries keep their identity — no value converts through the + * checked-dynamic tree. Non-promise entries (numbers, strings, booleans, + * unit values — never thenable) pass straight into their slot; a + * checked-dynamic entry (a JavaScript function's result) joins through + * PromiseResolve: the promise it holds, a promise that adopts a thenable, + * or a promise fulfilled with its plain value. The tuple + * fulfills one microtask turn after the combinator does. Null when the + * result type or an entry has no typed home here. */ +function lowerTuplePromiseAll( + lowerer: Lowerer, + call: ts.CallExpression, + elems: IrExpr[], +): IrExpr | null { + const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); + if (resultT?.kind !== "promise" || resultT.inner.kind !== "record") return null; + const shape = lowerer.shapes.get(resultT.inner.shapeId); + if (!shape?.tuple || shape.fields.length !== elems.length) return null; + const plainValue = (t: IrType): boolean => + t.kind === "f64" || t.kind === "string" || t.kind === "bool" || isUnitType(t); + // A checked-dynamic entry (a JavaScript function's result) may hold a + // promise or a plain value at run time; it joins through its promise view. + const settles = (t: IrType): boolean => t.kind === "promise" || t.kind === "dyn"; + if (!elems.some((e) => settles(e.type))) return null; + if (!elems.every((e) => settles(e.type) || plainValue(e.type))) return null; + const loc = locOf(call); + const key = `${elems.map((e) => typeKey(e.type)).join(",")}->${typeKey(resultT.inner)}`; + let name = lowerer.arrHofHelpers.get(`promise.allTuple:${key}`); + if (name === undefined) { + name = `%promise.allTuple.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(`promise.allTuple:${key}`, name); + const ctx = newFnCtx(false, null, null, resultT.inner); + ctx.isAsync = true; + lowerer.fnStack.push(ctx); + try { + const params = elems.map((e, i) => ({ + localId: `entry.${i}`, + name: `entry${i}`, + type: e.type, + })); + const ref = (i: number): IrExpr => varRef(`entry.${i}`, elems[i]!.type, loc); + const dynPromise: IrType = { kind: "promise", inner: DYN }; + const body: IrStmt[] = []; + // Each dyn entry's promise view, in entry order: the promise it + // holds, a promise that adopts a thenable's settlement, or a promise + // fulfilled with the plain value (PromiseResolve's rule). + const views = new Map(); + elems.forEach((e, i) => { + if (e.type.kind !== "dyn") return; + const view = lowerer.declareHiddenLocal("%allEntry", dynPromise); + body.push({ + kind: "varDecl", + localId: view.id, + init: { + kind: "libCall", + fn: "dyn.promiseResolve", + args: [ref(i), thenReader(lowerer, loc)], + type: dynPromise, + loc, + }, + loc, + }); + views.set(i, varRef(view.id, dynPromise, loc)); + }); + const widened: IrExpr[] = []; + elems.forEach((e, i) => { + const view = views.get(i); + if (view !== undefined) { + widened.push({ + kind: "promiseVoidWiden", + value: view, + type: { kind: "promise", inner: VOID }, + loc, + }); + return; + } + if (e.type.kind !== "promise") return; + widened.push( + e.type.inner.kind === "void" + ? ref(i) + : { + kind: "promiseVoidWiden", + value: ref(i), + type: { kind: "promise", inner: VOID }, + loc, + }, + ); + }); + const settled: IrExpr = { + kind: "awaitExpr", + value: { + kind: "intrinsic", + name: "promise.all", + args: [ + { + kind: "arrayLit", + elems: widened, + type: arrayOf({ kind: "promise", inner: VOID }), + loc, + }, + ], + type: { kind: "promise", inner: VOID }, + loc, + }, + type: VOID, + loc, + }; + // Shape fields are sorted by name ("10" before "2"); a tuple field's + // name is its position. + const fields = shape.fields.map((field) => { + const i = Number(field.name); + const entry = elems[i]!; + let value: IrExpr; + const view = views.get(i); + if (view !== undefined) { + value = { kind: "awaitExpr", value: view, type: DYN, loc }; + } else if (entry.type.kind === "promise") { + value = + entry.type.inner.kind === "void" + ? { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc } + : { kind: "awaitExpr", value: ref(i), type: entry.type.inner, loc }; + } else { + value = ref(i); + } + return { name: field.name, value: lowerer.coerceToExpected(value, field.type) }; + }); + body.push( + { kind: "exprStmt", expr: settled, loc }, + { + kind: "return", + value: { kind: "recordLit", fields, type: resultT.inner, loc }, + loc, + }, + ); + lowerer.liftedFns.push({ + name, + params, + returnType: resultT.inner, + locals: [ + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...lowerer.ctx.locals, + ], + body, + async: true, + loc, + }); + } catch (e) { + lowerer.arrHofHelpers.delete(`promise.allTuple:${key}`); + throw e; + } finally { + lowerer.fnStack.pop(); + } + } + return { kind: "call", callee: name, args: elems, type: resultT, loc }; +} + /** `Promise.race([...])` on THE Promise global: the entries lower * individually (the array never materializes — promise-element arrays * have no representation) into a promise.race intrinsic; the result * type is the checker's combined promise, and each entry's inner type * must be that inner type, one of its union arms, or a sub-union of it * (the backend's interned adapters wrap/re-tag fulfillments; a wider - * entry would need machinery that doesn't exist and fences). + * entry would need machinery that doesn't exist and fences). A + * Promise entry fits any result, and an entry whose payload widens + * into the result's converts. When the checker's combined type has no + * representation, the result takes the type the entries that can fulfill + * share, or 'unknown'. * Promise.all/allSettled/any fence with the sequential-await hint; * resolve/reject and the rest fall to the member fence. Null for * non-Promise receivers. */ @@ -50,14 +219,47 @@ export function lowerPromiseStaticCall( "a non-empty array LITERAL of promises is the supported form: Promise.race([p, q])", ); } - const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); - if (resultT?.kind !== "promise") { - lowerer.noLowering( - "Promise.race with this combined result type", - call, - "the entries' value types must combine into a representable union", - ); + // A Promise entry (the deadline idiom: `new Promise((_, + // reject) => setTimeout(reject, ms))`) can only reject, so it races + // under any result type; its inner type maps to void. + const neverEntry = (el: ts.Expression): boolean => { + const t = lowerer.typeOf(el); + const arg = + ts.symbolNameOf(t.getSymbol()) === "Promise" + ? lowerer.checker.getTypeArguments(t as ts.TypeReference)[0] + : undefined; + return arg !== undefined && (arg.flags & ts.TypeFlags.Never) !== 0; + }; + const elements = argNode.elements.nodes; + let combined = lowerer.mapTypeOf(lowerer.typeOf(call)); + let lowered: IrExpr[] | null = null; + if (combined?.kind !== "promise") { + // The checker's combined type has no representation (an 'any' + // element array such as events.once's any[], or 'void | any[]'): the + // result takes the one type the entries that can fulfill share, or + // else 'unknown' when each of them widens into it. + lowered = elements.map((el) => lowerer.lowerExpr(el)); + const fulfilling = lowered.filter((_, i) => !neverEntry(elements[i]!)); + const first = fulfilling[0]?.type; + const dynResult: IrType & { kind: "promise" } = { kind: "promise", inner: DYN }; + if (first?.kind === "promise" && fulfilling.every((e) => typeEquals(e.type, first))) { + combined = first; + } else if ( + fulfilling.length > 0 && + fulfilling.every( + (e) => e.type.kind === "promise" && lowerer.promisePayloadWidens(e.type, dynResult), + ) + ) { + combined = dynResult; + } else { + lowerer.noLowering( + "Promise.race with this combined result type", + call, + "the entries' value types must combine into a representable union", + ); + } } + const resultT = combined; const resultArms = resultT.inner.kind === "union" ? (lowerer.unions.get(resultT.inner.unionId)?.arms ?? []) @@ -65,14 +267,17 @@ export function lowerPromiseStaticCall( const compatible = (inner: IrType): boolean => { if (typeEquals(inner, resultT.inner)) return true; if (!resultArms) return false; + // A Promise entry fulfills with undefined, which the result + // holds when the checker folded `void` into its undefined arm. + if (inner.kind === "void") return resultArms.some((a) => a.kind === "undefinedT"); if (inner.kind === "union") { const arms = lowerer.unions.get(inner.unionId)?.arms ?? []; return arms.every((a) => resultArms.some((b) => typeEquals(a, b))); } return resultArms.some((a) => typeEquals(a, inner)); }; - const entries = argNode.elements.nodes.map((el) => { - const entry = lowerer.lowerExpr(el); + const entries = elements.map((el, i) => { + const entry = lowered?.[i] ?? lowerer.lowerExpr(el); if (entry.type.kind !== "promise") { lowerer.noLowering( "Promise.race over non-promise entries", @@ -80,7 +285,11 @@ export function lowerPromiseStaticCall( "JS would resolve plain values — wrap them: new Promise((r) => r(v))", ); } - if (!compatible(entry.type.inner)) { + if (!compatible(entry.type.inner) && !(entry.type.inner.kind === "void" && neverEntry(el))) { + // A payload that widens into the result's (into 'unknown', or a + // checked conversion) keeps its promise and settlement turn. + if (lowerer.promisePayloadWidens(entry.type, resultT)) + return lowerer.coerceToExpected(entry, resultT); lowerer.unsupported( "SC1090", el, @@ -140,8 +349,12 @@ export function lowerPromiseStaticCall( return { kind: "intrinsic", name: "promise.all", args: [entriesArr], type: resultT, loc }; } + if (!isJsSourceFile(ts.getSourceFileOfNode(call))) { + const tuple = lowerTuplePromiseAll(lowerer, call, elems); + if (tuple) return tuple; + } if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && elems.every((entry) => lowerer.dynConvertible(entry.type)) ) { return checkedPromiseAll( @@ -162,7 +375,7 @@ export function lowerPromiseStaticCall( ); } const entries = lowerer.lowerExpr(argNode); - if (isJsSourceFile(call.getSourceFile()) && entries.type.kind === "dyn") + if (isJsSourceFile(ts.getSourceFileOfNode(call)) && entries.type.kind === "dyn") return checkedPromiseAll(lowerer, entries, loc); // loadPlugin(p)))` — the loadPlugins shape, where the checker @@ -221,7 +434,7 @@ export function lowerPromiseStaticCall( `one iterable of promises is the supported form: Promise.${member}(ps)`, ); } - if (isJsSourceFile(call.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(call))) { // JavaScript entries are checked-dynamic: any iterable, thenables // and plain values included, exactly like Promise.all. if ( @@ -488,6 +701,32 @@ export function lowerPromiseStaticCall( lowerer.noLowering("Promise.resolve at a unit-typed promise", call); } const value = lowerer.lowerExprExpecting(argNode, resultT.inner); + // In JavaScript, a checked-dynamic argument may itself hold a promise + // (a TypeScript async callback's result: `Promise.resolve(fn())`): + // Promise.resolve answers THAT promise — the same object, adopted — + // and wraps every other value. + if ( + isJsSourceFile(ts.getSourceFileOfNode(call)) && + value.type.kind === "dyn" && + resultT.inner.kind === "dyn" + ) { + const local = lowerer.declareHiddenLocal("%resolveValue", DYN); + const ref: IrExpr = { kind: "varRef", localId: local.id, type: DYN, loc }; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }], + result: { + kind: "ternary", + cond: { kind: "dynTest", test: "promise", value: ref, type: BOOL, loc }, + then: { kind: "dynCheck", value: ref, type: resultT, loc }, + else_: { kind: "intrinsic", name: "promise.resolve", args: [ref], type: resultT, loc }, + type: resultT, + loc, + }, + type: resultT, + loc, + }; + } return { kind: "intrinsic", name: "promise.resolve", args: [value], type: resultT, loc }; } return null; // reject lands on lowerPromiseRejectCall / the member fence diff --git a/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts b/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts new file mode 100644 index 000000000..cdb8a1fbb --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/promisify-callback.ts @@ -0,0 +1,377 @@ +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { varRef } from "../../../ir/build.js"; +import { + BOOL, + type IrExpr, + type IrLocal, + type IrParam, + type IrStmt, + type IrType, + type SrcLoc, + VOID, + funcOf, + isUnitType, + typeEquals, + typeKey, +} from "../../../ir/ir.js"; + +const ERROR_T: IrType = { kind: "object", className: "%Error" }; + +/** The parts of `util.promisify(fn)` for a typed callback-last function: + * `fn(...args, (err, value) => void)` becomes `(...args) => Promise`. */ +export interface CallbackShape { + fnT: IrType & { kind: "func" }; + resultT: IrType & { kind: "func" }; + args: readonly IrType[]; + callbackT: IrType & { kind: "func" }; + /** The parameter's declared type: callbackT, or `callbackT | undefined` + * for an optional callback. */ + callbackParamT: IrType; + inner: IrType; + /** The error parameter's union and the tag of its Error arm, or null when + * the callback declares no error parameter. */ + error: { type: IrType; errorTag: number; unitTags: number[] } | null; +} + +function callbackShape( + lowerer: Lowerer, + fnT: IrType | null, + resultT: IrType | null, + loc: SrcLoc, +): CallbackShape | null { + if (fnT?.kind !== "func" || resultT?.kind !== "func") return null; + if (fnT.rest || fnT.restAbi || fnT.argumentsAll || resultT.rest || resultT.restAbi) return null; + const callbackParamT = fnT.params[fnT.params.length - 1]; + // An optional callback (`close(callback?: (err?: Error) => void)`) takes + // its function arm; the call passes the callback into the union. + const callbackT = + callbackParamT?.kind === "union" + ? (() => { + const arms = lowerer.unions.get(callbackParamT.unionId)?.arms ?? []; + const fns = arms.filter((arm) => arm.kind === "func"); + return fns.length === 1 && + arms.every((arm) => arm.kind === "func" || arm.kind === "undefinedT") + ? fns[0] + : undefined; + })() + : callbackParamT; + if (callbackT?.kind !== "func" || callbackT.ret.kind !== "void") return null; + if (callbackT.rest || callbackT.restAbi || callbackT.params.length > 2) return null; + const args = fnT.params.slice(0, -1); + if ( + resultT.ret.kind !== "promise" || + resultT.params.length !== args.length || + !resultT.params.every((type, i) => typeEquals(type, args[i]!)) + ) + return null; + const inner = resultT.ret.inner; + let error: CallbackShape["error"] = null; + const errorT = callbackT.params[0]; + if (errorT !== undefined) { + if (errorT.kind !== "union") return null; + const arms = lowerer.unions.get(errorT.unionId)?.arms ?? []; + const errorTags = arms.flatMap((arm, tag) => + arm.kind === "object" && + (arm.className === "%Error" || lowerer.isSubclassOf(arm.className, "%Error")) + ? [tag] + : [], + ); + const unitTags = arms.flatMap((arm, tag) => (isUnitType(arm) ? [tag] : [])); + if (errorTags.length !== 1 || errorTags.length + unitTags.length !== arms.length) return null; + error = { type: errorT, errorTag: errorTags[0]!, unitTags }; + } + if (inner.kind !== "void") { + const valueT = callbackT.params[1]; + if (valueT === undefined) return null; + const probe: IrExpr = { kind: "varRef", localId: "%probe", type: valueT, loc }; + if (!typeEquals(lowerer.coerceToExpected(probe, inner).type, inner)) return null; + } + return { fnT, resultT, args, callbackT, callbackParamT: callbackParamT!, inner, error }; +} + +/** The callback shape of a `promisify(fn)` call, or null. */ +export function callbackPromisifyShape( + lowerer: Lowerer, + call: ts.CallExpression, + loc: SrcLoc, +): CallbackShape | null { + if (call.questionDotToken || call.arguments.nodes.length !== 1) return null; + const argNode = call.arguments.nodes[0]!; + if (ts.isSpreadElement(argNode)) return null; + return callbackShape( + lowerer, + lowerer.mapTypeOf(lowerer.typeOf(argNode)), + lowerer.mapTypeOf(lowerer.typeOf(call)), + loc, + ); +} + +function captureParam(localId: string, type: IrType): IrParam { + return { localId, name: localId, type }; +} + +function boxedLocal(localId: string, type: IrType): IrLocal { + return { id: localId, name: localId, type, mutable: false, boxed: true }; +} + +function plainLocal(localId: string, type: IrType): IrLocal { + return { id: localId, name: localId, type, mutable: false }; +} + +/** `util.promisify(fn)` over a typed callback-last function value (a bound + * method, a function declaration, a closure): a native function that calls + * `fn` with the arguments and a callback, and settles its promise from the + * callback like Node's wrapper: a truthy error rejects, otherwise the first + * value fulfills. A synchronous throw rejects (the executor's rule). Null + * when the argument or result does not have that typed shape. */ +export function lowerCallbackPromisify( + lowerer: Lowerer, + call: ts.CallExpression, + loc: SrcLoc, +): IrExpr | null { + const shape = callbackPromisifyShape(lowerer, call, loc); + if (shape === null) return null; + const argNode = call.arguments.nodes[0]!; + let fn = lowerer.lowerExpr(argNode); + if (!typeEquals(fn.type, shape.fnT)) fn = lowerer.coerceToExpected(fn, shape.fnT); + if (!typeEquals(fn.type, shape.fnT)) return null; + return { + kind: "call", + callee: promisifyMaker(lowerer, shape, loc), + args: [fn], + type: shape.resultT, + loc, + }; +} + +/** Interns the helper family for one (function, result) type pair and + * answers the maker: `(fn) => closure(...args) => Promise`. */ +function promisifyMaker(lowerer: Lowerer, shape: CallbackShape, loc: SrcLoc): string { + const key = `promisify:${typeKey(shape.fnT)}=>${typeKey(shape.resultT)}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const base = `%promisify.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(key, base); + const { fnT, resultT, args, callbackT, callbackParamT, inner, error } = shape; + const promiseT = resultT.ret; + const resolveT = funcOf(inner.kind === "void" ? [] : [inner], VOID); + const rejectT = funcOf([ERROR_T], VOID); + const executorT = funcOf([resolveT, rejectT], VOID); + const argIds = args.map((_, i) => `a${i}.0`); + + // The callback: (err, value) => err ? reject(err) : resolve(value). + const cbParams: IrParam[] = []; + callbackT.params.forEach((type, i) => + cbParams.push(captureParam(i === 0 ? "err.0" : "value.0", type)), + ); + const cbBody: IrStmt[] = []; + if (error !== null) { + const err = varRef("err.0", error.type, loc); + let truthy: IrExpr | null = null; + for (const tag of error.unitTags) { + const part: IrExpr = { + kind: "unionIsTag", + unionId: (error.type as IrType & { kind: "union" }).unionId, + tag, + negated: true, + value: err, + type: BOOL, + loc, + }; + truthy = + truthy === null + ? part + : { kind: "logical", op: "&&", left: truthy, right: part, type: BOOL, loc }; + } + const errorArm = lowerer.unions.get((error.type as IrType & { kind: "union" }).unionId)!.arms[ + error.errorTag + ]!; + let reason: IrExpr = { + kind: "unionNarrow", + unionId: (error.type as IrType & { kind: "union" }).unionId, + tag: error.errorTag, + value: err, + type: errorArm, + loc, + }; + if (!typeEquals(errorArm, ERROR_T)) + reason = { kind: "upcast", value: reason, type: ERROR_T, loc }; + const reject: IrStmt[] = [ + { + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef("rej.0", rejectT, loc), + args: [reason], + type: VOID, + loc, + }, + loc, + }, + { kind: "return", value: null, loc }, + ]; + cbBody.push( + truthy === null ? reject[0]! : { kind: "if", cond: truthy, then: reject, else_: null, loc }, + ); + if (truthy === null) cbBody.push(reject[1]!); + } + const value = + inner.kind === "void" + ? [] + : [lowerer.coerceToExpected(varRef("value.0", callbackT.params[1]!, loc), inner)]; + cbBody.push({ + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef("res.0", resolveT, loc), + args: value, + type: VOID, + loc, + }, + loc, + }); + lowerer.liftedFns.push({ + name: `${base}.cb`, + params: cbParams, + returnType: VOID, + captures: [captureParam("res.0", resolveT), captureParam("rej.0", rejectT)], + locals: [ + boxedLocal("res.0", resolveT), + boxedLocal("rej.0", rejectT), + ...cbParams.map((p) => plainLocal(p.localId, p.type)), + ], + body: cbBody, + loc, + }); + lowerer.liftedFns.push({ + name: `${base}.mkcb`, + params: [captureParam("res.0", resolveT), captureParam("rej.0", rejectT)], + returnType: callbackT, + locals: [boxedLocal("res.0", resolveT), boxedLocal("rej.0", rejectT)], + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: `${base}.cb`, + captures: ["res.0", "rej.0"], + type: callbackT, + loc, + }, + loc, + }, + ], + loc, + }); + + // The executor: (resolve, reject) => fn(...args, callback). + const captured: IrParam[] = [captureParam("f.0", fnT)]; + args.forEach((type, i) => captured.push(captureParam(argIds[i]!, type))); + lowerer.liftedFns.push({ + name: `${base}.exec`, + params: [captureParam("res.0", resolveT), captureParam("rej.0", rejectT)], + returnType: VOID, + captures: captured, + locals: [ + ...captured.map((p) => boxedLocal(p.localId, p.type)), + plainLocal("res.0", resolveT), + plainLocal("rej.0", rejectT), + ], + body: [ + { + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef("f.0", fnT, loc), + args: [ + ...args.map((type, i) => varRef(argIds[i]!, type, loc)), + lowerer.coerceToExpected( + { + kind: "call", + callee: `${base}.mkcb`, + args: [varRef("res.0", resolveT, loc), varRef("rej.0", rejectT, loc)], + type: callbackT, + loc, + }, + callbackParamT, + ), + ], + // The function's own return type; the value is discarded. + type: fnT.ret, + loc, + }, + loc, + }, + ], + loc, + }); + lowerer.liftedFns.push({ + name: `${base}.mkexec`, + params: captured, + returnType: executorT, + locals: captured.map((p) => boxedLocal(p.localId, p.type)), + body: [ + { + kind: "return", + value: { + kind: "closure", + fnName: `${base}.exec`, + captures: captured.map((p) => p.localId), + type: executorT, + loc, + }, + loc, + }, + ], + loc, + }); + + // The promisified function: (...args) => new Promise(executor). + const implParams: IrParam[] = []; + args.forEach((type, i) => implParams.push(captureParam(argIds[i]!, type))); + lowerer.liftedFns.push({ + name: `${base}.impl`, + params: implParams, + returnType: promiseT, + captures: [captureParam("f.0", fnT)], + locals: [boxedLocal("f.0", fnT), ...implParams.map((p) => plainLocal(p.localId, p.type))], + body: [ + { + kind: "return", + value: { + kind: "newPromise", + executor: { + kind: "call", + callee: `${base}.mkexec`, + args: [ + varRef("f.0", fnT, loc), + ...implParams.map((p) => varRef(p.localId, p.type, loc)), + ], + type: executorT, + loc, + }, + type: promiseT, + loc, + }, + loc, + }, + ], + loc, + }); + lowerer.liftedFns.push({ + name: base, + params: [captureParam("f.0", fnT)], + returnType: resultT, + locals: [boxedLocal("f.0", fnT)], + body: [ + { + kind: "return", + value: { kind: "closure", fnName: `${base}.impl`, captures: ["f.0"], type: resultT, loc }, + loc, + }, + ], + loc, + }); + return base; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/properties.ts b/packages/compiler/src/frontend/lowering/builtins/properties.ts index b0caa0456..70512fed9 100644 --- a/packages/compiler/src/frontend/lowering/builtins/properties.ts +++ b/packages/compiler/src/frontend/lowering/builtins/properties.ts @@ -1,5 +1,5 @@ import { isBuiltinClassInstance } from "./receiver-types.js"; -import { strLit } from "../../../ir/build.js"; +import { numLit, strLit } from "../../../ir/build.js"; import * as ts from "../../ts7/adapter.js"; import { type Lowerer } from "../lowerer.js"; import { locOf } from "../../program.js"; @@ -192,6 +192,25 @@ export function lowerBuiltinExtraProperty( // +1-or-NULL runtime pair. if (kind === "child" && (name === "stdin" || name === "stdout" || name === "stderr")) { const receiver = lowerer.lowerExpr(expr.expression); + // Under the general stream representation (general-streams.ts) the + // declared stream type maps to a runtime stream class: the read + // answers the child's bridged stream (one per stdio slot, so repeated + // reads keep identity), fed by the same pipe. + const general = lowerer.generalNodeStreams ? generalChildStdioType(lowerer, expr, name) : null; + if (general) { + const type: IrType = { + kind: "union", + unionId: lowerer.unions.intern([general, { kind: "nullT" }]), + }; + const read: IrExpr = { + kind: "libCall", + fn: "child.stdioStream", + args: [receiver, numLit(name === "stdin" ? 0 : name === "stdout" ? 1 : 2, loc)], + type, + loc, + }; + return lowerer.maybeNarrow(read, expr); + } const streamType = name === "stdin" ? CHILDWRITER_T : CHILDSTREAM_T; const type: IrType = { kind: "union", @@ -259,3 +278,24 @@ export function lowerBuiltinExtraProperty( } return null; } + +/** The runtime stream class a child stdio read produces under the general + * stream representation, or null when the declared type keeps the stdio + * handle (the fallback declarations' ChildStdin). */ +function generalChildStdioType( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, + name: "stdin" | "stdout" | "stderr", +): IrType | null { + const className = name === "stdin" ? "%Writable" : "%Readable"; + const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (!mapped) return null; + // Checked-dynamic stream slots: the declared stream type maps to dyn + // (dyn-streams.ts); the read still produces the bridged stream. + if (lowerer.dynNodeStreams && mapped.kind === "dyn") return { kind: "object", className }; + const arms = + mapped.kind === "union" ? (lowerer.unions.get(mapped.unionId)?.arms ?? []) : [mapped]; + return arms.some((arm) => arm.kind === "object" && arm.className === className) + ? { kind: "object", className } + : null; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/readline.ts b/packages/compiler/src/frontend/lowering/builtins/readline.ts index 6baeb65d1..ede65d788 100644 --- a/packages/compiler/src/frontend/lowering/builtins/readline.ts +++ b/packages/compiler/src/frontend/lowering/builtins/readline.ts @@ -18,6 +18,7 @@ import { READLINE_DOCUMENTED_OPTIONS, fenceOrDropOptionKey } from "../surfaces.j import { streamSidesOf } from "../lower-stream.js"; import type { IrLibFn } from "../../../ir/builtin-signatures.js"; import { voidizedCallback } from "../lower-server.js"; +import { builtinImportOf } from "./module-bindings.js"; /** The ambient module declaring `d`, or null. */ function ambientModuleOf(d: ts.Node): string | null { @@ -45,10 +46,10 @@ function isReadlineModule(spec: string | null): boolean { export function isReadlineTyped(lowerer: Lowerer, node: ts.Expression): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== "Interface") return false; + if (sym === undefined || ts.symbolName(sym) !== "Interface") return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; return isReadlineModule(ambientModuleOf(d)); }); } @@ -73,7 +74,7 @@ const RL_EVENT_LIST = * readline/promises question. */ function returnsPromise(lowerer: Lowerer, call: ts.CallExpression): boolean { const t = lowerer.checker.getTypeAtLocation(call); - return (t.getSymbol()?.name ?? "") === "Promise"; + return (ts.symbolNameOf(t.getSymbol()) ?? "") === "Promise"; } /** The `{ signal }` options argument of the promise question: the signal @@ -106,10 +107,10 @@ function questionSignal(lowerer: Lowerer, node: ts.Expression | undefined): ts.E function isReadlineUiTyped(lowerer: Lowerer, node: ts.Expression): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== "Readline") return false; + if (sym === undefined || ts.symbolName(sym) !== "Readline") return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; const spec = ambientModuleOf(d); return spec === "readline/promises" || spec === "node:readline/promises"; }); @@ -270,6 +271,80 @@ function lowerReadlineUiCall( return { kind: "seqExpr", stmts, result: recv, type: F64, loc }; } +/** The module ("readline" or "readline/promises") whose Interface class + * `node` names, or null. */ +function interfaceClassModule(lowerer: Lowerer, node: ts.Expression): string | null { + const builtin = ts.isIdentifier(node) + ? builtinImportOf(lowerer, node) + : ts.isPropertyAccessExpression(node) && !node.questionDotToken + ? lowerer.builtinMemberOf(node) + : null; + if (builtin?.member !== "Interface") return null; + return builtin.module === "readline" || builtin.module === "readline/promises" + ? builtin.module + : null; +} + +/** `rl instanceof readline.Interface` and `rl instanceof + * readlinePromises.Interface` over a readline Interface: an interface is + * an instance of the class of the module that created it (Node's + * readline/promises Interface does not extend node:readline's). Null for + * other instanceof tests. */ +export function lowerReadlineInstanceof( + lowerer: Lowerer, + expr: ts.BinaryExpression, +): IrExpr | null { + const module = interfaceClassModule(lowerer, expr.right); + if (module === null || !isReadlineTyped(lowerer, expr.left)) return null; + const loc = locOf(expr); + const recv = lowerer.lowerExprExpecting(expr.left, F64); + const isPromises: IrExpr = { + kind: "libCall", + fn: "rl.isPromises", + args: [recv], + type: BOOL, + loc, + }; + if (module === "readline/promises") return isPromises; + return { kind: "unary", op: "!", operand: isPromises, type: BOOL, loc }; +} + +/** `rl[Symbol.dispose]()` on a readline Interface: Node's dispose method + * calls close(), which does nothing on a closed interface. Null for other + * element-access calls. */ +export function lowerReadlineDisposeCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + const access = call.expression; + if (!ts.isElementAccessExpression(access) || access.questionDotToken || call.questionDotToken) { + return null; + } + let key = access.argumentExpression; + while (ts.isParenthesizedExpression(key)) key = key.expression; + if ( + !ts.isPropertyAccessExpression(key) || + lowerer.stdlibGlobalMember(key, "Symbol") !== "dispose" + ) { + return null; + } + if (!isReadlineTyped(lowerer, access.expression)) return null; + if (call.arguments.nodes.length > 0) { + lowerer.noLowering("readline.Interface[Symbol.dispose] with arguments", call); + } + const recv = lowerer.lowerExprExpecting(access.expression, F64); + return { kind: "libCall", fn: "rl.close", args: [recv], type: VOID, loc: locOf(call) }; +} + +/** A question query or setPrompt argument: the string, or for a value + * that need not be one (JavaScript), the checked-dynamic value, which the + * runtime writes the way Node's kWriteToOutput does (dyn: true). */ +function lowerPromptArg(lowerer: Lowerer, node: ts.Expression): { value: IrExpr; dyn: boolean } { + if (lowerer.typeOf(node).flags & ts.TypeFlags.StringLike) { + return { value: lowerer.lowerExprExpecting(node, STRING), dyn: false }; + } + const value = lowerer.lowerExpr(node); + if (value.type.kind === "string") return { value, dyn: false }; + return { value: lowerer.coerceInto(node, value, DYN), dyn: true }; +} + /** Method calls on readline Interface receivers (both modules): * - question(query, cb) — the callback form; question(query, { signal }?) * — the promise form (readline/promises); @@ -303,18 +378,20 @@ export function lowerReadlineMethodCall( args.length <= 2 ) { const recv = receiver(); - const query = lowerer.lowerExprExpecting(args[0]!, STRING); + const query = lowerPromptArg(lowerer, args[0]!); const signal = questionSignal(lowerer, args[1]); const type = { kind: "promise", inner: STRING } as const; if (signal === null) { - return { kind: "libCall", fn: "rl.questionAsync", args: [recv, query], type, loc }; + const fn = query.dyn ? "rl.questionAsyncDyn" : "rl.questionAsync"; + return { kind: "libCall", fn, args: [recv, query.value], type, loc }; } const sig = lowerer.lowerExprExpecting(signal, DYN); - return { kind: "libCall", fn: "rl.questionAsyncSignal", args: [recv, query, sig], type, loc }; + const fn = query.dyn ? "rl.questionAsyncSignalDyn" : "rl.questionAsyncSignal"; + return { kind: "libCall", fn, args: [recv, query.value, sig], type, loc }; } if (name === "question" && args.length === 2) { const recv = receiver(); - const query = lowerer.lowerExprExpecting(args[0]!, STRING); + const query = lowerPromptArg(lowerer, args[0]!); const cb = lowerer.lowerExpr(args[1]!); if (cb.type.kind !== "func" || cb.type.ret.kind !== "void" || cb.type.params.length > 1) { lowerer.unsupported( @@ -331,7 +408,8 @@ export function lowerReadlineMethodCall( `question callbacks whose parameter is not 'string' (got '${lowerer.fmt(param)}')`, ); } - return { kind: "libCall", fn: "rl.question", args: [recv, query, cb], type: VOID, loc }; + const fn = query.dyn ? "rl.questionDyn" : "rl.question"; + return { kind: "libCall", fn, args: [recv, query.value, cb], type: VOID, loc }; } const simple: Record = { close: "rl.close", @@ -366,8 +444,9 @@ export function lowerReadlineMethodCall( } if (name === "setPrompt" && args.length === 1) { const recv = receiver(); - const prompt = lowerer.lowerExprExpecting(args[0]!, STRING); - return { kind: "libCall", fn: "rl.setPrompt", args: [recv, prompt], type: VOID, loc }; + const prompt = lowerPromptArg(lowerer, args[0]!); + const fn = prompt.dyn ? "rl.setPromptDyn" : "rl.setPrompt"; + return { kind: "libCall", fn, args: [recv, prompt.value], type: VOID, loc }; } if (name === "getPrompt" && args.length === 0) { return { kind: "libCall", fn: "rl.getPrompt", args: [receiver()], type: STRING, loc }; @@ -871,6 +950,25 @@ export function lowerReadlineCursorCall( return { kind: "seqExpr", stmts, result, type: BOOL, loc }; } +/** A checked-dynamic input or output (a JavaScript function's result, an + * object's member, an element that may be undefined) as the stream it + * holds: the runtime refuses a value that is not a stream with that + * side, and answers null for an output of null or undefined. Other + * values pass through. */ +function streamFromDyn(lowerer: Lowerer, node: ts.Expression, output: boolean): IrExpr { + let v = lowerer.lowerExpr(node); + if (v.type.kind === "union") v = lowerer.coerceInto(node, v, DYN); + if (v.type.kind !== "dyn") return v; + const loc = locOf(node); + return { + kind: "libCall", + fn: output ? "rl.outputFromDyn" : "rl.inputFromDyn", + args: [v, boolLit(output, loc)], + type: { kind: "object", className: output ? "%Writable" : "%Readable" }, + loc, + }; +} + /** The interface's input: process.stdin (null) or a readable stream. */ function lowerInput(lowerer: Lowerer, node: ts.Expression): IrExpr | null { if ( @@ -879,7 +977,7 @@ function lowerInput(lowerer: Lowerer, node: ts.Expression): IrExpr | null { ) { return null; } - const v = lowerer.lowerExpr(node); + const v = streamFromDyn(lowerer, node, false); const info = v.type.kind === "object" ? lowerer.classes.get(v.type.className) : undefined; const sides = streamSidesOf(lowerer, info); if (sides !== "r" && sides !== "rw") { @@ -904,7 +1002,7 @@ function lowerOutput(lowerer: Lowerer, node: ts.Expression): IrExpr | "stdout" | return "none"; } if (ts.isIdentifier(node) && node.text === "undefined") return "none"; - const v = lowerer.lowerExpr(node); + const v = streamFromDyn(lowerer, node, true); const info = v.type.kind === "object" ? lowerer.classes.get(v.type.className) : undefined; const sides = streamSidesOf(lowerer, info); if (sides !== "w" && sides !== "rw") { @@ -923,8 +1021,8 @@ function lowerOutput(lowerer: Lowerer, node: ts.Expression): IrExpr | "stdout" | * stream; the output process.stdout, a writable stream, or absent * (prompts are then not written). rl.create's arguments: the mode literal * (1 stream input, 2 stream output, 4 stdout output, 8 signal, 16 - * prompt), then the input and output streams when present, crlfDelay, - * the prompt and the signal when present. An interface that can be a + * prompt, 32 node:readline/promises), then the input and output streams + * when present, crlfDelay, the prompt and the signal when present. An interface that can be a * terminal one (a `terminal` option that is not false, or none with * process.stdout as the output, which is a terminal when it is a TTY) * goes through rl.terminal with the completer. */ @@ -1107,7 +1205,8 @@ export function lowerReadlineCreateCall( (typeof output === "object" ? 2 : 0) | (output === "stdout" ? 4 : 0) | (signal !== null ? 8 : 0) | - (prompt !== null ? 16 : 0); + (prompt !== null ? 16 : 0) | + (promises ? 32 : 0); const args: IrExpr[] = [numLit(mode, loc)]; if (input !== null) args.push(input); if (typeof output === "object") args.push(output); diff --git a/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts b/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts index fc507e2bd..230441d31 100644 --- a/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts +++ b/packages/compiler/src/frontend/lowering/builtins/receiver-types.ts @@ -10,10 +10,10 @@ export function isBuiltinClassInstance( ): boolean { const type = lowerer.checker.getTypeAtLocation(node); const symbol = type.getAliasSymbol() ?? type.getSymbol(); - if (symbol?.name !== name) return false; + if (symbol === undefined || ts.symbolName(symbol) !== name) return false; return symbol.declarations.some( (declaration) => (ts.isClassDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) && - lowerer.isStdlibFile(declaration.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration)), ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts b/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts new file mode 100644 index 000000000..7db3f8dcc --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/runtime-constructor.ts @@ -0,0 +1,181 @@ +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { type ClassInfo } from "../lower-classes.js"; +import { isJsSourceFile, locOf } from "../../program.js"; +import { varRef } from "../../../ir/build.js"; +import { BOOL, STRING, type IrExpr, type IrType, type SrcLoc } from "../../../ir/ir.js"; + +/** `x.constructor` on an instance typed as EventEmitter or a stream class + * in a TypeScript file: the receiver's runtime ClassInfo, or null. The + * runtime records each instance's Node class name (EventEmitter, Readable, + * ReadStream, Gzip, Socket, a program subclass's name), which the + * lowerings below read with emitter.className. */ +export function runtimeConstructorReceiver( + lowerer: Lowerer, + expr: ts.Expression, +): { receiver: ts.Expression; info: ClassInfo } | null { + let e = expr; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (!ts.isPropertyAccessExpression(e) || e.questionDotToken) return null; + if (e.name.text !== "constructor" || isJsSourceFile(ts.getSourceFileOfNode(e))) return null; + const mapped = lowerer.mapTypeOf(lowerer.typeOf(e.expression)); + if (mapped?.kind !== "object") return null; + const info = lowerer.classes.get(mapped.className); + if (!info?.def.runtime || (!info.builtinEmitter && info.builtinStream === undefined)) return null; + return { receiver: e.expression, info }; +} + +/** `x.constructor.name`: the instance's class name. */ +export function lowerRuntimeConstructorName( + lowerer: Lowerer, + receiver: ts.Expression, + loc: SrcLoc, +): IrExpr { + return { + kind: "libCall", + fn: "emitter.className", + args: [lowerer.lowerExpr(receiver)], + type: STRING, + loc, + }; +} + +/** `x.constructor === C` for EventEmitter or a stream class C: true when + * the instance's own class is C, which needs both the vtable (C's own + * interval, not a subclass's) and the class name (fs, zlib, crypto, child + * process and worker streams share the base classes' vtables). */ +export function lowerRuntimeConstructorIs( + lowerer: Lowerer, + receiver: ts.Expression, + target: ClassInfo, + negated: boolean, + loc: SrcLoc, +): IrExpr { + const value = lowerer.lowerExpr(receiver); + const local = lowerer.declareHiddenLocal("%constructorOf", value.type); + const ref = varRef(local.id, value.type, loc); + const test = exactClassTest(lowerer, ref, target, loc); + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }], + result: negated ? { kind: "unary", op: "!", operand: test, type: BOOL, loc } : test, + type: BOOL, + loc, + }; +} + +/** `x.constructor` as a value: the class object of the instance's own class + * among EventEmitter and the stream classes, as the receiver's static + * class's class value. Instances of the other runtime classes (fs, zlib, + * crypto, child process and worker streams) and of program subclasses + * report SC1090 at run time. */ +export function lowerRuntimeConstructor( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, + info: ClassInfo, +): IrExpr { + const loc = locOf(access); + const resultT: IrType = { kind: "classval", className: info.def.name }; + const value = lowerer.lowerExpr(access.expression); + const local = lowerer.declareHiddenLocal("%constructorOf", value.type); + const ref = varRef(local.id, value.type, loc); + let result: IrExpr = refusalCall(lowerer, resultT, loc); + for (const candidate of valueClassesUnder(info)) { + lowerer.defineClassValueConstruct(candidate); + lowerer.defineClassValueConstruct(info); + const classRef: IrExpr = { + kind: "classRef", + className: candidate.def.name, + type: { kind: "classval", className: candidate.def.name }, + loc, + }; + result = { + kind: "ternary", + cond: exactClassTest(lowerer, ref, candidate, loc), + then: candidate === info ? classRef : { kind: "upcast", value: classRef, type: resultT, loc }, + else_: result, + type: resultT, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }], + result, + type: resultT, + loc, + }; +} + +/** `info` and its runtime descendants whose values construct. */ +function valueClassesUnder(info: ClassInfo): ClassInfo[] { + const out: ClassInfo[] = []; + const visit = (c: ClassInfo): void => { + if (!c.def.runtime) return; + if (c.valueCtorParams !== undefined) out.push(c); + for (const s of c.subclasses) visit(s); + }; + visit(info); + return out; +} + +/** The instance is of class `target` itself: inside its interval, outside + * each direct subclass's (runtime or program), and named as it. */ +function exactClassTest(lowerer: Lowerer, ref: IrExpr, target: ClassInfo, loc: SrcLoc): IrExpr { + let test: IrExpr = { + kind: "instanceOf", + value: ref, + className: target.def.name, + type: BOOL, + loc, + }; + for (const sub of target.subclasses) { + test = { + kind: "logical", + op: "&&", + left: test, + right: { + kind: "unary", + op: "!", + operand: { kind: "instanceOf", value: ref, className: sub.def.name, type: BOOL, loc }, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; + } + const name: IrExpr = { + kind: "strEq", + negated: false, + left: { kind: "libCall", fn: "emitter.className", args: [ref], type: STRING, loc }, + right: { kind: "strLit", value: target.def.name.slice(1), type: STRING, loc }, + type: BOOL, + loc, + }; + return { kind: "logical", op: "&&", left: test, right: name, type: BOOL, loc }; +} + +/** The run-time refusal for an instance whose class has no class value. */ +function refusalCall(lowerer: Lowerer, type: IrType, loc: SrcLoc): IrExpr { + const name = `%constructor.refusal:${type.kind === "classval" ? type.className : ""}`; + if (!lowerer.liftedFns.some((fn) => fn.name === name)) { + lowerer.liftedFns.push({ + name, + params: [], + locals: [], + returnType: type, + body: [ + { + kind: "runtimeFence", + code: "SC1090", + message: + "reading 'constructor' of an fs, zlib, crypto, child process or worker stream, or of an instance of a program subclass of EventEmitter or a stream class, is not supported yet", + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: name, args: [], type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts index e89b1f07e..7229d5b84 100644 --- a/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts +++ b/packages/compiler/src/frontend/lowering/builtins/text-codecs.ts @@ -360,13 +360,15 @@ export function storedTextCodecClassOf( expr: ts.Expression, ): TextCodecCtor["cls"] | null { const sym = lowerer.typeOf(expr).getSymbol(); - if (!sym || (sym.name !== "TextEncoder" && sym.name !== "TextDecoder")) return null; + if (!sym) return null; + const name = ts.symbolName(sym); + if (name !== "TextEncoder" && name !== "TextDecoder") return null; return sym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) - ? sym.name + ? name : null; } @@ -517,8 +519,9 @@ function directTextCodecCtorOf(lowerer: Lowerer, expr: ts.Expression): TextCodec if (callee.text !== "TextDecoder" && callee.text !== "TextEncoder") return null; const sym = lowerer.resolveValueSymbol(callee); if (!sym || !lowerer.isStdlibSymbol(sym)) return null; - if (sym.name !== "TextDecoder" && sym.name !== "TextEncoder") return null; - return { cls: sym.name, ctor }; + const name = ts.symbolName(sym); + if (name !== "TextDecoder" && name !== "TextEncoder") return null; + return { cls: name, ctor }; } /** Inline codec calls avoid allocating a receiver; stored receivers use diff --git a/packages/compiler/src/frontend/lowering/builtins/timers.ts b/packages/compiler/src/frontend/lowering/builtins/timers.ts index 9f950cddc..7230d18cd 100644 --- a/packages/compiler/src/frontend/lowering/builtins/timers.ts +++ b/packages/compiler/src/frontend/lowering/builtins/timers.ts @@ -154,9 +154,9 @@ function isTimerHandleTyped( ): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getAliasSymbol() ?? t.getSymbol(); - if (sym?.name !== name) return false; + if (sym === undefined || ts.symbolName(sym) !== name) return false; return sym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ); } function isTimeoutTyped(lowerer: Lowerer, node: ts.Expression): boolean { diff --git a/packages/compiler/src/frontend/lowering/builtins/url.ts b/packages/compiler/src/frontend/lowering/builtins/url.ts index 3de973335..dca8a7cc0 100644 --- a/packages/compiler/src/frontend/lowering/builtins/url.ts +++ b/packages/compiler/src/frontend/lowering/builtins/url.ts @@ -842,7 +842,7 @@ export function lowerLegacyUrlCall( if (bi.member === "domainToASCII" || bi.member === "domainToUnicode") { args.push({ kind: "boolLit", value: expr.arguments.nodes.length === 0, type: BOOL, loc }); } else { - const warn = !insideNodeModules(expr.getSourceFile().fileName); + const warn = !insideNodeModules(ts.getSourceFileOfNode(expr).fileName); args.push({ kind: "boolLit", value: warn, type: BOOL, loc }); } return { kind: "libCall", fn: fn.fn, args, type: fn.result, loc }; diff --git a/packages/compiler/src/frontend/lowering/builtins/v8.ts b/packages/compiler/src/frontend/lowering/builtins/v8.ts new file mode 100644 index 000000000..3582ce8ae --- /dev/null +++ b/packages/compiler/src/frontend/lowering/builtins/v8.ts @@ -0,0 +1,111 @@ +/* node:v8 — importable, with one lowered member. Packages require it at + * top level and touch it on optional paths (dd-trace's runtime metrics), + * so the module itself is not refused. getHeapStatistics answers + * allocator-derived figures in Node's shape and field order, in the + * spirit of process.memoryUsage(): approximate values, not V8 heap + * figures (a compiled binary embeds no V8 engine). Every other member + * fences at its use with the by-design reason (builtinFenceHintOf). */ +import { numLit } from "../../../ir/build.js"; +import * as ts from "../../ts7/adapter.js"; +import { type Lowerer } from "../lowerer.js"; +import { F64, type IrExpr, type SrcLoc, VOID } from "../../../ir/ir.js"; + +/** v8.getHeapStatistics()'s fields, in Node's order. */ +const HEAP_STATISTICS_FIELDS = [ + "total_heap_size", + "total_heap_size_executable", + "total_physical_size", + "total_available_size", + "used_heap_size", + "heap_size_limit", + "malloced_memory", + "peak_malloced_memory", + "does_zap_garbage", + "number_of_native_contexts", + "number_of_detached_contexts", + "total_global_handles_size", + "used_global_handles_size", + "external_memory", +]; + +/** `v8.getHeapStatistics()`: one memoryUsage sample (the runtime heap's + * total and used bytes), with the machine's memory as the heap limit (the + * runtime heap has no fixed ceiling) and the available size the limit + * minus used bytes. V8-only figures (executable code space, malloced + * memory, global handles, external memory, zapping) read 0, and one + * native context exists. The record is the call site's interned HeapInfo + * shape, built by a shared helper per shape. */ +export function lowerV8HeapStatisticsCall( + lowerer: Lowerer, + call: ts.CallExpression, + loc: SrcLoc, +): IrExpr { + if (call.arguments.nodes.length !== 0) { + lowerer.noLowering(`v8.getHeapStatistics with ${call.arguments.nodes.length} arguments`, call); + } + const t = lowerer.mapTypeOf(lowerer.typeOf(call)); + if (t?.kind !== "record") lowerer.badType(call, lowerer.typeOf(call)); + const shape = lowerer.shapes.get(t.shapeId); + if ( + !shape || + !shape.fields.every((f) => HEAP_STATISTICS_FIELDS.includes(f.name) && f.type.kind === "f64") + ) { + lowerer.badType(call, lowerer.typeOf(call)); + } + const key = `v8.getHeapStatistics:${t.shapeId}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = `%v8.getHeapStatistics.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, helper); + const sample = (index: number): IrExpr => ({ + kind: "libCall", + fn: "process.memoryUsageField", + args: [numLit(index, loc)], + type: F64, + loc, + }); + const limit = (): IrExpr => ({ kind: "libCall", fn: "os.totalmem", args: [], type: F64, loc }); + const value = (name: string): IrExpr => { + switch (name) { + case "total_heap_size": + case "total_physical_size": + return sample(1); + case "used_heap_size": + return sample(2); + case "heap_size_limit": + return limit(); + case "total_available_size": + return { kind: "bin", op: "-", left: limit(), right: sample(2), type: F64, loc }; + case "number_of_native_contexts": + return numLit(1, loc); + default: + return numLit(0, loc); + } + }; + lowerer.liftedFns.push({ + name: helper, + params: [], + returnType: t, + locals: [], + body: [ + { + kind: "exprStmt", + expr: { kind: "libCall", fn: "process.memoryUsageTake", args: [], type: VOID, loc }, + loc, + }, + { + kind: "return", + value: { + kind: "recordLit", + fields: shape.fields.map((f) => ({ name: f.name, value: value(f.name) })), + type: t, + loc, + }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: helper, args: [], type: t, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/webassembly.ts b/packages/compiler/src/frontend/lowering/builtins/webassembly.ts index dc61853f8..56347e437 100644 --- a/packages/compiler/src/frontend/lowering/builtins/webassembly.ts +++ b/packages/compiler/src/frontend/lowering/builtins/webassembly.ts @@ -2,17 +2,44 @@ import * as ts from "../../ts7/adapter.js"; import { type Lowerer, PoisonError } from "../lowerer.js"; import { locOf } from "../../program.js"; import { webAssemblyDiag } from "../../../diagnostics/diagnostic.js"; -import { STRING, type IrExpr } from "../../../ir/ir.js"; +import { BOOL, DYN, F64, STRING, type IrExpr } from "../../../ir/ir.js"; +import { dynUndefinedExpr } from "../../../ir/build.js"; +import { lowerDynObjectLiteral } from "../expressions/object-literals.js"; import { stdlibGlobalNameOf } from "../surfaces.js"; +import { isWebAssemblyType } from "../../type-mapper.js"; /* The WebAssembly API. scriptc.d.ts declares its types (lib.dom's - * namespace) so that packages whose declarations mention them typecheck, - * and the type mapper gives those types the checked-dynamic representation. - * No module compiles to native code yet, so every reached use of the API - * value fences at its expression with webAssemblyDiag: a build error in - * TypeScript sources, a located runtime fence in JavaScript ones. typeof - * needs no value and folds to Node's answer. */ + * namespace) and the type mapper gives them the checked-dynamic + * representation. Modules whose bytes the build sees are translated to + * native code (wasm/translate.ts) and found at run time by the SHA-256 of + * their bytes (scr_wasm.c): + * - WebAssembly.compile, instantiate and validate calls, new + * WebAssembly.Module, Instance, Memory, Table and Global, and + * WebAssembly.Module.imports, exports and customSections lower to + * wasm.* libCalls; members of the values they produce (exports, memory + * buffers, globals, tables) dispatch through the native handles; + * - the classes called without `new` throw Node's TypeError + * (wasm.withoutNew); + * - `v instanceof WebAssembly.` lowers to wasm.is; + * - the error classes construct with or without `new` (wasm.errorNew). + * Every other reached use of the API value (the classes as values, the + * streaming functions, Tag and Exception) still fences at its expression + * with webAssemblyDiag: a build error in TypeScript sources, a located + * runtime fence in JavaScript ones. typeof needs no value and folds to Node's + * answer. A lowering marks the lowerer, so the build translates the + * modules it discovers (lowerToIr). */ +/** The classes `wasm.is` answers, by kind. */ +const WASM_CLASSES = [ + "Module", + "Instance", + "Memory", + "Table", + "Global", + "CompileError", + "LinkError", + "RuntimeError", +] as const; /** Strips the spellings that leave a value unchanged: parentheses, type * assertions, `satisfies` and non-null assertions. */ function bare(expr: ts.Expression): ts.Expression { @@ -130,3 +157,248 @@ export function lowerWebAssemblyTypeof(lowerer: Lowerer, operand: ts.Expression) } return null; } + +/** The member `WebAssembly.` names (any spelling of the namespace), + * or null. */ +function webAssemblyMember(lowerer: Lowerer, expr: ts.Expression): string | null { + const surface = webAssemblySurface(lowerer, expr); + if (surface === null || !surface.startsWith("WebAssembly.")) return null; + const member = surface.slice("WebAssembly.".length); + return member.includes(".") ? null : member; +} + +function lowerArg(lowerer: Lowerer, arg: ts.Expression): IrExpr { + return ts.isObjectLiteralExpression(arg) + ? lowerDynObjectLiteral(lowerer, arg) + : lowerer.lowerExprExpecting(arg, DYN); +} + +/** An argument whose evaluation has no effect: a literal, a plain + * identifier, or an empty object or array literal. */ +function isInert(arg: ts.Expression): boolean { + const node = bare(arg); + if (ts.isObjectLiteralExpression(node)) return node.properties.nodes.length === 0; + if (ts.isArrayLiteralExpression(node)) return node.elements.nodes.length === 0; + return ( + ts.isIdentifier(node) || + ts.isNumericLiteral(node) || + node.kind === ts.SyntaxKind.BigIntLiteral || + ts.isStringLiteral(node) || + ts.isNoSubstitutionTemplateLiteral(node) || + node.kind === ts.SyntaxKind.NullKeyword || + node.kind === ts.SyntaxKind.TrueKeyword || + node.kind === ts.SyntaxKind.FalseKeyword + ); +} + +/** The arguments, padded with undefined to `count`. Extra arguments are + * ignored, as the API ignores them, when evaluating them has no effect; + * null for spreads or other extra arguments (they stay refused). */ +function lowerArgs( + lowerer: Lowerer, + args: readonly ts.Expression[], + count: number, + loc: IrExpr["loc"], +): IrExpr[] | null { + if (args.some(ts.isSpreadElement)) return null; + if (args.length > count) { + if (!args.slice(count).every(isInert)) return null; + args = args.slice(0, count); + } + const out = args.map((arg) => lowerArg(lowerer, arg)); + while (out.length < count) out.push(dynUndefinedExpr(loc)); + return out; +} + +function errorNew(member: string, args: IrExpr[], loc: IrExpr["loc"]): IrExpr { + const kind = (WASM_CLASSES as readonly string[]).indexOf(member); + return { + kind: "libCall", + fn: "wasm.errorNew", + args: [{ kind: "numLit", value: kind, type: F64, loc }, args[0]!, args[1]!], + type: DYN, + loc, + }; +} + +/** The classes that require `new`, in wasm.withoutNew's numbering. */ +const NEW_ONLY = ["Module", "Instance", "Memory", "Table", "Global"]; + +/** `WebAssembly.Module.imports(module)`, `exports(module)` and + * `customSections(module, name)`. Their results are arrays of descriptors + * or ArrayBuffers built as checked-dynamic values, then taken into the + * call's own type. Null for anything else. */ +function lowerModuleStatic(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + const surface = webAssemblySurface(lowerer, call.expression); + const name = surface?.startsWith("WebAssembly.Module.") + ? surface.slice("WebAssembly.Module.".length) + : null; + if (name !== "imports" && name !== "exports" && name !== "customSections") return null; + const loc = locOf(call); + const args = lowerArgs(lowerer, call.arguments.nodes, name === "customSections" ? 2 : 1, loc); + if (args === null) return null; + lowerer.usesWebAssembly = true; + if (name === "customSections") lowerer.usesWasmCustomSections = true; + const fn = + name === "imports" + ? "wasm.moduleImports" + : name === "exports" + ? "wasm.moduleExports" + : "wasm.moduleCustomSections"; + const result: IrExpr = { kind: "libCall", fn, args, type: DYN, loc }; + return lowerer.coerceInto(call, result, lowerer.irTypeOf(call)); +} + +/** `WebAssembly.compile(bytes)`, `WebAssembly.instantiate(source, + * imports?)`, `WebAssembly.validate(bytes)`, the error classes called + * without `new`, the other classes called without `new` (a TypeError), and + * Module's static functions. Null for anything else. */ +export function lowerWebAssemblyCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + if (call.questionDotToken) return null; + const member = webAssemblyMember(lowerer, call.expression); + if (member === null) return lowerModuleStatic(lowerer, call); + const loc = locOf(call); + const newOnly = NEW_ONLY.indexOf(member); + if (newOnly >= 0) { + // The arguments evaluate, then the call throws. + const args = lowerArgs(lowerer, call.arguments.nodes, 2, loc); + if (args === null) return null; + lowerer.usesWebAssembly = true; + return { + kind: "libCall", + fn: "wasm.withoutNew", + args: [{ kind: "numLit", value: newOnly, type: F64, loc }, args[0]!, args[1]!], + type: DYN, + loc, + }; + } + const isError = member === "CompileError" || member === "LinkError" || member === "RuntimeError"; + if (member !== "compile" && member !== "instantiate" && member !== "validate" && !isError) + return null; + const used = member === "instantiate" || isError ? 2 : 1; + const args = lowerArgs(lowerer, call.arguments.nodes, used, loc); + if (args === null) return null; + lowerer.usesWebAssembly = true; + let result: IrExpr; + if (isError) result = errorNew(member, args, loc); + else if (member === "validate") + result = { kind: "libCall", fn: "wasm.validate", args: [args[0]!], type: BOOL, loc }; + else if (member === "compile") + result = { + kind: "libCall", + fn: "wasm.compile", + args: [args[0]!], + type: { kind: "promise", inner: DYN }, + loc, + }; + else + result = { + kind: "libCall", + fn: "wasm.instantiate", + args: [args[0]!, args[1]!], + type: { kind: "promise", inner: DYN }, + loc, + }; + return result; +} + +/** `new WebAssembly.Module(bytes)`, `new WebAssembly.Instance(module, + * imports?)`, `new WebAssembly.Memory(descriptor)`, `new + * WebAssembly.Table(descriptor, value?)`, `new + * WebAssembly.Global(descriptor, value?)` and the error classes. Null for + * anything else. */ +export function lowerWebAssemblyNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null { + const member = webAssemblyMember(lowerer, expr.expression); + if (member === null) return null; + const isError = member === "CompileError" || member === "LinkError" || member === "RuntimeError"; + if (!NEW_ONLY.includes(member) && !isError) return null; + const loc = locOf(expr); + const used = member === "Module" || member === "Memory" ? 1 : 2; + const args = lowerArgs(lowerer, expr.arguments?.nodes ?? [], used, loc); + if (args === null) return null; + lowerer.usesWebAssembly = true; + let result: IrExpr; + if (isError) result = errorNew(member, args, loc); + else if (member === "Module") + result = { kind: "libCall", fn: "wasm.moduleNew", args: [args[0]!], type: DYN, loc }; + else if (member === "Memory") + result = { kind: "libCall", fn: "wasm.memoryNew", args: [args[0]!], type: DYN, loc }; + else if (member === "Table") + result = { kind: "libCall", fn: "wasm.tableNew", args: [args[0]!, args[1]!], type: DYN, loc }; + else if (member === "Global") + result = { kind: "libCall", fn: "wasm.globalNew", args: [args[0]!, args[1]!], type: DYN, loc }; + else + result = { + kind: "libCall", + fn: "wasm.instanceNew", + args: [args[0]!, args[1]!], + type: DYN, + loc, + }; + return result; +} + +/** `v instanceof WebAssembly.`. Null for anything else. */ +export function lowerWebAssemblyInstanceof( + lowerer: Lowerer, + expr: ts.BinaryExpression, +): IrExpr | null { + const member = webAssemblyMember(lowerer, expr.right); + if (member === null) return null; + const kind = (WASM_CLASSES as readonly string[]).indexOf(member); + if (kind < 0) return null; + const loc = locOf(expr); + lowerer.usesWebAssembly = true; + const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); + return { + kind: "libCall", + fn: "wasm.is", + args: [value, { kind: "numLit", value: kind, type: F64, loc }], + type: BOOL, + loc, + }; +} + +/** WebAssembly API values: `WebAssembly.compile`, `instantiate` and + * `validate` read as functions, the namespace itself (`WebAssembly`, + * `globalThis.WebAssembly`), and the global object cast to a type with a + * `WebAssembly` member (pi codemode's spelling). Null for anything else, + * which refuseWebAssemblyUse then fences. */ +export function lowerWebAssemblyValue(lowerer: Lowerer, expr: ts.Expression): IrExpr | null { + const surface = webAssemblySurface(lowerer, expr); + if (surface === null) return null; + const loc = locOf(expr); + const node = bare(expr); + const fn = (name: string, args: IrExpr[]): IrExpr => { + lowerer.usesWebAssembly = true; + return { kind: "libCall", fn: name as "wasm.namespace", args, type: DYN, loc }; + }; + if (surface === "globalThis.WebAssembly") { + // The cast spelling names the global object, not its member. + const cast = + (ts.isAsExpression(expr) || ts.isTypeAssertion(expr)) && + isGlobalThis(lowerer, expr.expression); + return cast ? fn("wasm.globalView", []) : fn("wasm.namespace", []); + } + if (surface === "WebAssembly") return fn("wasm.namespace", []); + const index = ["WebAssembly.compile", "WebAssembly.instantiate", "WebAssembly.validate"].indexOf( + surface, + ); + if (index < 0 || !ts.isPropertyAccessExpression(node)) return null; + return fn("wasm.function", [{ kind: "numLit", value: index, type: F64, loc }]); +} + +/** `global.valueOf()` on a receiver typed as a WebAssembly value: a + * Global answers its value (the generic dyn.valueOf answers the object + * itself for native handles other than Date). Null for other receivers. */ +export function lowerWebAssemblyValueOf( + lowerer: Lowerer, + receiver: ts.Expression, + recv: IrExpr, + loc: IrExpr["loc"], +): IrExpr | null { + const ctx = { checker: lowerer.checker, isStdlibFile: lowerer.isStdlibFile }; + if (!isWebAssemblyType(lowerer.typeOf(receiver), ctx)) return null; + lowerer.usesWebAssembly = true; + return { kind: "libCall", fn: "wasm.valueOf", args: [recv], type: DYN, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts b/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts index 2a9ff9eef..93cba84c5 100644 --- a/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts +++ b/packages/compiler/src/frontend/lowering/builtins/webcrypto.ts @@ -103,7 +103,7 @@ export function fenceWebCryptoMember( use: "read" | "call", ): IrExpr | null { if (use !== "read" || !ts.isPropertyAccessExpression(access)) return null; - if (isJsSourceFile(access.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(access))) return null; if (isSubtleReceiver(lowerer, access)) { lowerer.noLowering("crypto.subtle as a value", access, SUBTLE_HINT); } @@ -123,8 +123,8 @@ export function isWebCryptoMemberType(lowerer: Lowerer, type: ts.Type): boolean const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( symbol !== undefined && - symbol.name !== "Crypto" && - WEBCRYPTO_DYN_TYPES.has(symbol.name) && + ts.symbolName(symbol) !== "Crypto" && + WEBCRYPTO_DYN_TYPES.has(ts.symbolName(symbol)) && lowerer.isStdlibSymbol(symbol) ); } diff --git a/packages/compiler/src/frontend/lowering/builtins/workers.ts b/packages/compiler/src/frontend/lowering/builtins/workers.ts index 335fdb969..1426041a2 100644 --- a/packages/compiler/src/frontend/lowering/builtins/workers.ts +++ b/packages/compiler/src/frontend/lowering/builtins/workers.ts @@ -174,10 +174,10 @@ function propertyNameText(name: ts.PropertyName): string | undefined { function isWorkerThreadsClass(lowerer: Lowerer, node: ts.Expression, name: string): boolean { const t = lowerer.checker.getTypeAtLocation(node); const sym = t.getSymbol(); - if (sym?.name !== name) return false; + if (sym === undefined || ts.symbolName(sym) !== name) return false; return sym.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let up: ts.Node | undefined = d.parent; while (up) { if (ts.isModuleDeclaration(up) && ts.isStringLiteral(up.name)) diff --git a/packages/compiler/src/frontend/lowering/call-arguments.ts b/packages/compiler/src/frontend/lowering/call-arguments.ts index f5600b033..49f7c8ec0 100644 --- a/packages/compiler/src/frontend/lowering/call-arguments.ts +++ b/packages/compiler/src/frontend/lowering/call-arguments.ts @@ -1,3 +1,4 @@ +import { forwardedTupleType, pureForwardedValues, stripTypeWrappers } from "./typed-arguments.js"; import { dynUndefinedExpr } from "../../ir/build.js"; import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; @@ -26,13 +27,13 @@ import { checkedIterableSpread } from "./checked-iterable-spread.js"; import { type ParamShape } from "./call-signatures.js"; import { arrayValueRead, arrayValueType } from "./array-values.js"; import { buildArrayConversion } from "./coercions/builders.js"; -import { typeKey } from "../type-mapper.js"; +import { restTupleElementTypes, typeKey } from "../type-mapper.js"; function fixedTupleSpreadInfo( lowerer: Lowerer, node: ts.Expression, ): { type: IrType & { kind: "record" }; fields: { name: string; type: IrType }[] } | null { - const type = lowerer.mapTypeOf(lowerer.typeOf(node)); + const type = forwardedTupleType(lowerer, node) ?? lowerer.mapTypeOf(lowerer.typeOf(node)); if (type?.kind !== "record") return null; const shape = lowerer.shapes.get(type.shapeId); if (!shape?.tuple || shape.fields.length === 0) return null; @@ -42,6 +43,18 @@ function fixedTupleSpreadInfo( }; } +/** `...t` over a provably EMPTY tuple bound to a plain name (`[]`, a rest + * tuple whose type parameter or `Parameters` resolved to no positions): + * it contributes no arguments, and reading the name has no effect. */ +function emptyTupleSpread(lowerer: Lowerer, node: ts.Expression): boolean { + let operand = node; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + if (!ts.isIdentifier(operand)) return false; + const forwarded = forwardedTupleType(lowerer, operand); + if (forwarded) return forwarded.kind === "array"; + return restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx)?.length === 0; +} + export function completeArgs( lowerer: Lowerer, argNodes: readonly ts.Expression[], @@ -59,7 +72,9 @@ export function completeArgs( // graph. Ordinary calls and fixed tuples never need that analysis. const runtimeSpreads = argNodes.filter( (arg): arg is ts.SpreadElement => - ts.isSpreadElement(arg) && !fixedTupleSpreadInfo(lowerer, arg.expression), + ts.isSpreadElement(arg) && + !fixedTupleSpreadInfo(lowerer, arg.expression) && + !emptyTupleSpread(lowerer, arg.expression), ); // Spread operands lowered while choosing the completion, each exactly once. const loweredSpreads = new Map(); @@ -128,7 +143,7 @@ export function completeArgs( loc: value.loc, }; } - value = checkedIterableSpread(lowerer, value, arg.expression.getText(), loc); + value = checkedIterableSpread(lowerer, value, ts.getTextOfNode(arg.expression), loc); if (spreads === 1 && index === argNodes.length - 1) { stmts.push({ kind: "exprStmt", @@ -138,7 +153,7 @@ export function completeArgs( args: [ ref(), value, - { kind: "strLit", value: arg.expression.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(arg.expression), type: STRING, loc }, ], type: VOID, loc, @@ -190,6 +205,12 @@ export function completeArgs( sources.push(arg); continue; } + if (emptyTupleSpread(lowerer, arg.expression)) continue; + const pure = pureForwardedValues(lowerer, arg.expression); + if (pure) { + for (const value of pure) sources.push({ ir: value }); + continue; + } const tuple = fixedTupleSpreadInfo(lowerer, arg.expression); if (!tuple) { sources.push(arg); @@ -204,7 +225,13 @@ export function completeArgs( // values as separately written arguments while the saved tuple stays // alive through the call. const spreadLoc = locOf(arg); - const value = lowerer.coerceInto(arg.expression, lowerer.lowerExpr(arg.expression), tuple.type); + // A forwarded tuple seen through type assertions (`...(arguments as + // unknown as Parameters)`) spreads the tuple itself. + const operand = + forwardedTupleType(lowerer, arg.expression) !== null + ? stripTypeWrappers(arg.expression) + : arg.expression; + const value = lowerer.coerceInto(arg.expression, lowerer.lowerExpr(operand), tuple.type); const saved = lowerer.declareHiddenLocal("%callSpread", tuple.type); const savedRef = (): IrExpr => ({ kind: "varRef", @@ -506,9 +533,38 @@ export function completeFuncValueArgs( shapes.push({ type: rest, mode: "rest" }); return completeArgs(lowerer, call.arguments.nodes, shapes, loc, call); } + // A fixed-tuple spread (`fn(...args)` over a tuple-typed rest binding) + // has a compile-time arity: completion flattens it into the positional + // slots exactly like a direct call's. + if ( + funcType.rest !== true && + call.arguments.nodes.some(ts.isSpreadElement) && + call.arguments.nodes.every( + (arg) => + !ts.isSpreadElement(arg) || + fixedTupleSpreadInfo(lowerer, arg.expression) !== null || + emptyTupleSpread(lowerer, arg.expression), + ) + ) { + const shapes: ParamShape[] = funcType.params.map((type) => ({ + type, + mode: omittedArgFor(lowerer, type, loc) ? "omittable" : "required", + })); + return completeArgs(lowerer, call.arguments.nodes, shapes, loc, call); + } const args = call.arguments.nodes.map((arg, index) => lowerer.lowerExprExpecting(arg, funcType.params[index]), ); + // Effect-free surplus arguments of a fixed-arity value (`(r: void) => + // void` drops its void slot) evaluate to nothing observable: drop them, + // as the callee ignores them. + while ( + funcType.rest !== true && + args.length > funcType.params.length && + isSafeToDiscard(args[args.length - 1]!) + ) { + args.pop(); + } for (let i = args.length; i < funcType.params.length; i++) { const absent = omittedArgFor(lowerer, funcType.params[i]!, loc); if (!absent) { @@ -549,7 +605,7 @@ export function lowerSpreadArgsCall( // TypeScript reaches this lane only for checked-dynamic variadic callees // (`(...args: unknown[]) => R`), whose ABI is the JS one. if ( - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && !(callee.type.kind === "func" && callee.type.rest === true && callee.type.restAbi === undefined) ) return null; @@ -557,7 +613,7 @@ export function lowerSpreadArgsCall( if (callee.type.kind !== "dyn" && callee.type.kind !== "func") return null; const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression) - ? expr.expression.getText() + ? ts.getTextOfNode(expr.expression) : ts.isIdentifier(expr.expression) ? expr.expression.text : "value"; @@ -584,8 +640,8 @@ export function lowerSpreadArgsCall( if (p.spreadOf !== null) { // The spelling rides along for V8's nullish spread-call // TypeError ("v is not iterable (cannot read property ...)"). - spreads.push({ arg: args.length, what: p.spreadOf.getText() }); - args.push(checkedIterableSpread(lowerer, p.v, p.spreadOf.getText(), loc)); + spreads.push({ arg: args.length, what: ts.getTextOfNode(p.spreadOf) }); + args.push(checkedIterableSpread(lowerer, p.v, ts.getTextOfNode(p.spreadOf), loc)); } else { args.push(lowerer.coerceInto(p.node!, p.v, DYN)); } diff --git a/packages/compiler/src/frontend/lowering/call-signatures.ts b/packages/compiler/src/frontend/lowering/call-signatures.ts index 6db1735db..47cdd834f 100644 --- a/packages/compiler/src/frontend/lowering/call-signatures.ts +++ b/packages/compiler/src/frontend/lowering/call-signatures.ts @@ -1,3 +1,8 @@ +import { + typedArgumentsCandidate, + typedArgumentsShapes, + typedArgumentsTarget, +} from "./typed-arguments.js"; import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; import { bodyReadsArguments } from "../arguments-usage.js"; @@ -15,6 +20,7 @@ import { import { isJsSourceFile, isNodeEsmFile } from "../program.js"; import { genResultRecord, + restTupleParamTypes, isAnyArrayType, isUnitOnlyTsType, jsOpenObjectType, @@ -27,6 +33,7 @@ import { isStreamUndefCallExpr } from "./lower-server.js"; import { nsPathPrefix } from "./lower-namespaces.js"; import { declSymbolOf } from "./lower-modules.js"; import { npmStaticPackageOfPath } from "../npm-static.js"; +import { restPackingAdapterFits } from "./coercions/function-adapters.js"; import { implicitMonoFile, implicitAnyParamSymbolsOf } from "./generic-functions.js"; /** How a parameter participates in CALL-SITE COMPLETION (the frontend @@ -46,12 +53,19 @@ export type ParamMode = "required" | "omittable" | "rest" | "dynRest" | "argumen * otherwise. `bodyType` is present exactly for DEFAULTED program params: * the plain T the body sees after the prologue applies the default (see * declareParams). `callDefault` is the compile-time default value for a - * table-backed builtin's omittable slot. */ + * table-backed builtin's omittable slot. `entryNarrow` marks a dyn ABI slot + * inherited from a JavaScript base method whose TypeScript override declares + * a typed parameter: the prologue narrows the checked value to that type. */ export interface ParamShape { type: IrType; mode: ParamMode; bodyType?: IrType; callDefault?: IrExpr; + entryNarrow?: IrType; + /** One flattened position of a fixed-tuple rest parameter + * (restTupleShapes): the declaration's last parameter owns every shape + * from its index on, and the body binds the tuple (declareParams). */ + restTuple?: true; } export interface FnSig { @@ -134,7 +148,9 @@ export function documentedParameter(param: ts.ParameterDeclaration): boolean { return ( param.type !== undefined || /@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ); } @@ -144,7 +160,7 @@ export function checkedJsDocumentedParameter( lowerer: Lowerer, param: ts.ParameterDeclaration, ): boolean { - return lowerer.checkedJsFile(param.getSourceFile()) && documentedParameter(param); + return lowerer.checkedJsFile(ts.getSourceFileOfNode(param)) && documentedParameter(param); } /** One parameter's ParamShape — the shared signature-shaped collection @@ -166,10 +182,10 @@ export function checkedJsDocumentedParameter( export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): ParamShape { const stage = lowerer.stageSourceType(param.name); if (stage) return { type: stage, mode: "required" }; - if (isJsSourceFile(param.getSourceFile()) && ts.isSetAccessorDeclaration(param.parent)) + if (isJsSourceFile(ts.getSourceFileOfNode(param)) && ts.isSetAccessorDeclaration(param.parent)) return { type: DYN, mode: "required" }; if ( - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && ts.isConstructorDeclaration(param.parent) && !param.dotDotDotToken && !checkedJsDocumentedParameter(lowerer, param) @@ -193,12 +209,14 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa lowerer.isStdlibGlobal(construction.expression, "Promise"); if ( !nativeExecutor && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !param.dotDotDotToken && !ts.isSetAccessorDeclaration(param.parent) && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { return param.initializer @@ -207,10 +225,12 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa } if ( param.initializer && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { return { type: DYN, mode: "omittable", bodyType: DYN }; @@ -219,10 +239,12 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // contract. Consume the actual iterable, including surplus elements. if ( ts.isArrayBindingPattern(param.name) && - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && !/@(?:param|type)\b/.test( - param.getSourceFile().text.slice(param.parent?.pos ?? param.pos, param.getStart()), + ts + .getSourceFileOfNode(param) + .text.slice(param.parent?.pos ?? param.pos, ts.tokenStartOfNode(param)), ) ) { if (param.dotDotDotToken) return { type: DYN, mode: "dynRest" }; @@ -284,7 +306,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // ARRAY param the dyn call thunk fills with the call's surplus // arguments; the binding is that array (dynRest — funcType marks // `rest`, and the value only ever calls through the boxed thunk). - if (isJsSourceFile(param.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(param))) { const restMapped = lowerer.mapTypeOf(lowerer.typeOf(param.name)); // Unmapped JS rest parameters use the native variadic checked-value ABI. @@ -311,7 +333,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa // including typed arrays passed through checked callbacks. Extracting // a native array here would copy it and discard element writes. if ( - isJsSourceFile(param.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(param)) && !param.type && (raw.kind === "array" || (raw.kind === "union" && @@ -323,7 +345,7 @@ export function paramShape(lowerer: Lowerer, param: ts.ParameterDeclaration): Pa } let type = lowerer.runtimeOptionalBindingType(param.name, lowerer.irTypeOf(param.name)); const body = param.parent?.body; - if (isJsSourceFile(param.getSourceFile()) && body && type.kind !== "dyn") { + if (isJsSourceFile(ts.getSourceFileOfNode(param)) && body && type.kind !== "dyn") { const symbol = lowerer.checker.getSymbolAtLocation(param.name); let acceptsUndefined = false; ts.walkPreorder(body, (node) => { @@ -369,6 +391,28 @@ export function isThisParameter(param: ts.ParameterDeclaration): boolean { return ts.isIdentifier(param.name) && param.name.text === "this"; } +/** A TypeScript rest parameter bound to a name whose type is a fixed tuple + * (`...args: [A, B]`, `...args: A` with A bound to a tuple, `...args: + * Parameters` with F bound): one required shape per position. Call + * sites and function values see the equivalent fixed signature; the body + * rebuilds the tuple (declareParams) unless it only forwards it. Null for + * every other parameter. */ +export function restTupleShapes( + lowerer: Lowerer, + param: ts.ParameterDeclaration, + tsType?: ts.Type, +): ParamShape[] | null { + if ( + param.dotDotDotToken === undefined || + !ts.isIdentifier(param.name) || + isJsSourceFile(ts.getSourceFileOfNode(param)) + ) { + return null; + } + const elems = restTupleParamTypes(tsType ?? lowerer.typeOf(param.name), lowerer.typeCtx); + return elems ? elems.map((type) => ({ type, mode: "required", restTuple: true })) : null; +} + /** ParamShapes for a whole parameter list. */ export function paramShapes( lowerer: Lowerer, @@ -376,21 +420,48 @@ export function paramShapes( signature?: ts.Signature, blameOf?: (param: ts.ParameterDeclaration, index: number) => ts.Node, ): ParamShape[] { - if (!signature) - return params.filter((p) => !isThisParameter(p)).map((param) => lowerer.paramShape(param)); + const out: ParamShape[] = []; + if (!signature) { + for (const param of params) { + if (isThisParameter(param)) continue; + const flattened = restTupleShapes(lowerer, param); + if (flattened) out.push(...flattened); + else out.push(lowerer.paramShape(param)); + } + return out; + } const symbols = signature.getParameters(); - return params.map((declParam, i) => { + const shapeAt = (declParam: ts.ParameterDeclaration, i: number): ParamShape => { const stage = lowerer.stageSourceType(declParam.name); if (stage) return { type: stage, mode: "required" as const }; const symbol = symbols[i]; const tsType = symbol ? lowerer.checker.getTypeOfSymbol(symbol) : lowerer.typeOf(declParam.name); + // A call-site instantiation whose concrete argument type does not map + // is that argument's blocker: name it there, with its concrete type, + // rather than as the declaration's unbound type parameter. + if ( + blameOf !== undefined && + !isJsSourceFile(ts.getSourceFileOfNode(declParam)) && + (tsType.flags & ts.TypeFlags.Any) === 0 && + declParam.type !== undefined && + mentionsTypeParameter(lowerer, lowerer.checker.getTypeFromTypeNode(declParam.type)) && + lowerer.mapTypeOf(tsType) === null + ) { + lowerer.badType(blameOf(declParam, i), tsType); + } let mapped = jsOpenObjectType( declParam, lowerer.runtimeOptionalBindingType( declParam.name, - lowerer.mapTypeOf(tsType) ?? lowerer.irTypeOf(declParam.name), + lowerer.mapTypeOf(tsType) ?? + // A TypeScript instantiation at `any` (a type argument inferred + // from an untyped value) takes the checked-dynamic slot. + ((tsType.flags & ts.TypeFlags.Any) !== 0 && + !isJsSourceFile(ts.getSourceFileOfNode(declParam)) + ? DYN + : lowerer.irTypeOf(declParam.name)), ), lowerer.shapes, lowerer.unions, @@ -424,7 +495,32 @@ export function paramShapes( ); } return { type: mapped, mode: declParam.questionToken ? "omittable" : "required" }; + }; + params.forEach((declParam, i) => { + const symbol = symbols[i]; + const flattened = restTupleShapes( + lowerer, + declParam, + symbol ? lowerer.checker.getTypeOfSymbol(symbol) : undefined, + ); + if (flattened) out.push(...flattened); + else out.push(shapeAt(declParam, i)); }); + return out; +} + +/** A declared type that is (or is a union over) a bare type parameter + * or an instantiation of a generic alias/interface over type arguments. */ +function mentionsTypeParameter(lowerer: Lowerer, t: ts.Type): boolean { + if (t.flags & ts.TypeFlags.TypeParameter) return true; + const mentions = (part: ts.Type): boolean => mentionsTypeParameter(lowerer, part); + if (t.isUnionType()) return ts.constituentTypes(t).some(mentions); + if (t.getAliasTypeArguments().some(mentions)) return true; + return ( + (t.flags & ts.TypeFlags.Object) !== 0 && + ((t as ts.ObjectType).objectFlags & ts.ObjectFlags.Reference) !== 0 && + lowerer.checker.getTypeArguments(t as ts.TypeReference).some(mentions) + ); } /** A default expression may itself yield undefined. Keep that arm in the @@ -522,6 +618,21 @@ export function requireExactArityValue( // optional/defaulted params spell their `T | undefined` slots (mapType's // completed-signature contract), so omitted trailing args complete with // the undefined arm like any direct call. + // A nested function declaration whose only non-required parameter is a + // typed rest: its value carries the typed-rest ABI its own type spells, + // and every use converts it at its slot (same signature, or the rest + // packing adapter for fixed parameters). + if ( + contextual === null && + ts.isFunctionDeclaration(blame) && + blame.name !== undefined && + funcType.kind === "func" && + funcType.restAbi === "typed" && + shapes.every((s) => s.mode === "required" || s.mode === "rest") + ) { + const own = lowerer.mapTypeOf(lowerer.typeOf(blame.name)); + if (own && typeEquals(own, funcType)) return; + } const target = contextual ? lowerer.checker.getContextualType(contextual) : undefined; const mapped = target ? lowerer.mapTypeOf(target) @@ -566,7 +677,7 @@ export function requireExactArityValue( const ownType = lowerer.typeOf(contextual); mappedFn = lowerer.mapTypeOf(ownType) ?? - (isJsSourceFile(contextual.getSourceFile()) + (isJsSourceFile(ts.getSourceFileOfNode(contextual)) ? dynFallbackType(lowerer, contextual, ownType) : null); } @@ -582,6 +693,16 @@ export function requireExactArityValue( ) { return; } + // A typed-rest function in a slot with fixed parameters (`log(...m: + // string[])` into `(m: string) => void`): the slot coercion's packing + // adapter collects the slot's arguments into the rest array. + if ( + mappedFn?.kind === "func" && + funcType.kind === "func" && + restPackingAdapterFits(lowerer, funcType, mappedFn) + ) { + return; + } // An uncontextualized npm-static implementation function can still // have a declaration-backed completed ABI: the package's .d.ts // projection supplies the body-facing parameter/return types while @@ -594,7 +715,7 @@ export function requireExactArityValue( target === undefined && contextual !== null && (ts.isArrowFunction(contextual) || ts.isFunctionExpression(contextual)) && - npmStaticPackageOfPath(contextual.getSourceFile().fileName) !== null + npmStaticPackageOfPath(ts.getSourceFileOfNode(contextual).fileName) !== null ) { return; } @@ -659,7 +780,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara // routes generic declarations (direct calls, namespace/CJS member // calls, value references) resolves it through genericFnsBySymbol // unchanged. - if (implicitMonoFile(decl.getSourceFile()) && !isAsync && !isGenerator) { + if (implicitMonoFile(ts.getSourceFileOfNode(decl)) && !isAsync && !isGenerator) { const implicit = implicitAnyParamSymbolsOf(lowerer, decl); if (implicit) { const nameText = decl.name?.text ?? "%default"; @@ -668,7 +789,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara lowerer.genericFnsBySymbol.set(symbol, { decl, baseName: nameText, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(decl) + nameText), + qualifiedName: lowerer.qualify(ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + nameText), typeParams: [], instances: new Map(), implicitParams: implicit, @@ -703,7 +824,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara // collide. The anonymous default export takes the synthetic // "%default" spelling ('%' cannot appear in a user identifier). name: lowerer.qualify( - decl.getSourceFile(), + ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + (decl.name?.text ?? "%default"), ), params, @@ -732,7 +853,7 @@ function appendArgumentsParameter( ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) ) || - !isJsSourceFile(node.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(node)) || !bodyReadsArguments(node) ) return; @@ -740,7 +861,11 @@ function appendArgumentsParameter( const classMethod = ts.isMethodDeclaration(node) && (ts.isClassDeclaration(node.parent) || ts.isClassExpression(node.parent)); - if (!classMethod && !isNodeEsmFile(node.getSourceFile(), lowerer.program)) { + if ( + !classMethod && + !isNodeEsmFile(ts.getSourceFileOfNode(node), lowerer.program) && + !unmappedArguments(node) + ) { lowerer.unsupported( "SC1090", node, @@ -751,6 +876,35 @@ function appendArgumentsParameter( shapes.push({ type: DYN, mode }); } +/** Whether `arguments` inside `node` is Node's unmapped object: strict code + * (a 'use strict' prologue on the file or an enclosing function, or a class + * body) or a non-simple parameter list (defaults, rest, destructuring). Its + * elements then never alias the parameters, which is the ES module case. */ +function unmappedArguments(node: ts.SignatureDeclaration): boolean { + if ( + node.parameters.nodes.some( + (p) => + p.initializer !== undefined || p.dotDotDotToken !== undefined || !ts.isIdentifier(p.name), + ) + ) + return true; + const useStrict = (statements: readonly ts.Statement[]): boolean => { + for (const statement of statements) { + if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) + return false; + if (statement.expression.text === "use strict") return true; + } + return false; + }; + for (let n: ts.Node | undefined = node; n; n = n.parent) { + if (ts.isClassDeclaration(n) || ts.isClassExpression(n)) return true; + if (ts.isFunctionLike(n) && n.body && ts.isBlock(n.body) && useStrict(n.body.statements.nodes)) + return true; + if (ts.isSourceFile(n)) return useStrict(n.statements.nodes); + } + return false; +} + /** Signature checks + param shapes + IR func type for any lambda-like * node. The func type's params are the ABI types: optional/default params * become `T | undefined`, and typed rest becomes one packed-array slot. @@ -766,11 +920,21 @@ export function lambdaSignature( | ts.SetAccessorDeclaration, ): { shapes: ParamShape[]; funcType: IrType & { kind: "func" } } { if (!node.body) lowerer.unsupported("SC1090", node, "function overload signatures"); + // A parameterless function expression forwarding its `arguments`, + // asserted to a fixed signature: that signature is its ABI. + if (typedArgumentsCandidate(node) && !node.typeParameters?.nodes.length) { + const target = typedArgumentsTarget(lowerer, node); + if (target) { + const shapes = typedArgumentsShapes(target); + lowerer.typedArgumentsLambdas.add(node); + return { shapes, funcType: funcTypeFromParamShapes(shapes, target.ret) }; + } + } // A JS replacement of a native stream's write method receives the // actual call tuple, including overloads and omitted arguments. Node's // contextual overload signature cannot define this closure's ABI. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && (ts.isFunctionExpression(node) || ts.isArrowFunction(node)) && node.parameters.nodes.every( @@ -888,7 +1052,7 @@ export function lambdaSignature( : []; const ctxDecl = ctxSigs.length === 1 ? ctxSigs[0]!.declaration : undefined; const ctxRetRaw = - ctxDecl && !ctxDecl.getSourceFile().isDeclarationFile + ctxDecl && !ts.getSourceFileOfNode(ctxDecl).isDeclarationFile ? lowerer.mapTypeOf(lowerer.checker.getReturnTypeOfSignature(ctxSigs[0]!)) : null; // An async lambda always produces a promise, so a slot returning a @@ -910,7 +1074,7 @@ export function lambdaSignature( // must match the slot's ABI exactly. const dynVariadicSlot = ctxRet?.kind === "dyn" && - !isJsSourceFile(node.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(node)) && shapes.some((shape) => shape.mode === "dynRest"); if ( dynVariadicSlot || diff --git a/packages/compiler/src/frontend/lowering/checked-promise-all.ts b/packages/compiler/src/frontend/lowering/checked-promise-all.ts index c3be91833..6c24a8b2c 100644 --- a/packages/compiler/src/frontend/lowering/checked-promise-all.ts +++ b/packages/compiler/src/frontend/lowering/checked-promise-all.ts @@ -11,7 +11,7 @@ const COMBINATOR_LIB_CALLS = { /** `value.then` through the program's own property dispatch, so a class * instance's `then` method is visible to the runtime's thenable check. */ -function thenReader(lowerer: Lowerer, loc: SrcLoc): IrExpr { +export function thenReader(lowerer: Lowerer, loc: SrcLoc): IrExpr { const name = "%promise.thenOf"; const type = funcOf([DYN], DYN); if (!lowerer.liftedFns.some((fn) => fn.name === name)) { diff --git a/packages/compiler/src/frontend/lowering/cjs-exports-object.ts b/packages/compiler/src/frontend/lowering/cjs-exports-object.ts index 5ea4001fc..30030b18f 100644 --- a/packages/compiler/src/frontend/lowering/cjs-exports-object.ts +++ b/packages/compiler/src/frontend/lowering/cjs-exports-object.ts @@ -22,25 +22,30 @@ import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; import { cjsExportAssignmentOf, + cjsExportDiscardReason, cjsExportTargetLiteral, isCjsJsFile, isModuleExportsAccess, isRequireStatement, locOf, + npmStaticDepSf7, + resolveImport, } from "../program.js"; +import { cjsWholeExportValueOf, inlineProgramRequireOf } from "./cjs-inline-require.js"; import { isCjsModuleGlobal } from "./lower-node-module.js"; +import { bindingNeverReassigned } from "./binding-analysis.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; + if (!isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program)) return false; const symbol = lowerer.checker.getSymbolAtLocation(node); const decls = symbol ? symbol.declarations : []; return !decls.some( (d) => - !d.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(d).isDeclarationFile && (ts.isVariableDeclaration(d) || ts.isParameter(d) || ts.isBindingElement(d) || @@ -57,7 +62,7 @@ export function isCjsModuleExports(lowerer: Lowerer, node: ts.Expression): boole return ( isModuleExportsAccess(node) && isCjsModuleGlobal(lowerer, node.expression) && - isCjsJsFile(node.getSourceFile(), lowerer.program) + isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program) ); } @@ -70,7 +75,7 @@ export function cjsInFileExportGlobalOf( access: ts.PropertyAccessExpression, ): IrGlobal | null { if (access.questionDotToken || !ts.isIdentifier(access.name)) return null; - const sf = access.getSourceFile(); + const sf = ts.getSourceFileOfNode(access); const recv = isModuleExportsAccess(access.expression) || (ts.isIdentifier(access.expression) && @@ -181,7 +186,7 @@ function exportsAssignments( * it runs: `exports` unless the file rebinds it; `module.exports` unless the * file rebinds `exports` or replaces `module.exports`. */ function namesOriginalExports(lowerer: Lowerer, node: ts.Expression): boolean { - const assigned = exportsAssignments(lowerer, node.getSourceFile()); + const assigned = exportsAssignments(lowerer, ts.getSourceFileOfNode(node)); if (assigned.exports) return false; return ts.isIdentifier(node) || !assigned.moduleExports; } @@ -247,7 +252,7 @@ export function usesCjsExportsIdentity(lowerer: Lowerer, sf: ts.SourceFile): boo * refusal. Null when `node` is not such a use. */ export function lowerCjsExportsObjectValue(lowerer: Lowerer, node: ts.Expression): IrExpr | null { if (!isCjsExportsObjectValue(lowerer, node)) return null; - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); // A module that replaces module.exports answers `module.exports` reads // through the replacement's own machinery (the class-expression whole // export, the replaced-exports reads). @@ -323,7 +328,7 @@ export function cjsExportMemberWriteGlobalOf( lowerer: Lowerer, target: ts.PropertyAccessExpression, ): IrGlobal | null { - const sf = target.getSourceFile(); + const sf = ts.getSourceFileOfNode(target); if (!isCjsJsFile(sf, lowerer.program)) return null; const assigned = exportsAssignments(lowerer, sf); if (assigned.exports || assigned.moduleExports) return null; @@ -428,3 +433,128 @@ export function cjsExportCallIgnoresThis( return false; }); } + +/** The value behind a binding of a CommonJS member export (an `import { + * name }` or a top-level `const { name } = require(...)`) written once as + * `module.exports. = ` or `exports. = `, when the + * module exports a value whole and the member is a property added to it + * (`exports = module.exports = Client; exports.StatsD = Client`). The + * checker gives such a binding a synthesized property symbol, so the + * module's statements answer. The value is a binding of the exporting + * module or a primitive literal, which the use site reads again. Follows + * `module.exports = require("./x")` re-exports. Null for any other binding. */ +export function cjsMemberAliasValueOf(lowerer: Lowerer, ref: ts.Identifier): ts.Expression | null { + const sym = lowerer.checker.getSymbolAtLocation(ref); + if (!sym) return null; + const decls = sym.declarations; + // `import { StatsD } from "pkg"` + const specifier = sym.flags & ts.SymbolFlags.Alias ? decls.find(ts.isImportSpecifier) : undefined; + if (specifier !== undefined) { + const importDecl = specifier.parent?.parent?.parent; + if ( + importDecl === undefined || + !ts.isImportDeclaration(importDecl) || + !ts.isStringLiteral(importDecl.moduleSpecifier) + ) + return null; + const spec = importDecl.moduleSpecifier.text; + const from = ts.getSourceFileOfNode(importDecl); + const dep = + resolveImport(lowerer.program, from, spec, "import") ?? + npmStaticDepSf7(lowerer.program, from, spec, "import"); + if (dep === null) return null; + return memberAliasIn(lowerer, dep, (specifier.propertyName ?? specifier.name).text, 0); + } + // `const { shout } = require("./lib")` at a module's top level + const element = decls.find(ts.isBindingElement); + if (element === undefined || element.dotDotDotToken || !ts.isIdentifier(element.name)) + return null; + const pattern = element.parent; + const declaration = pattern?.parent; + if ( + pattern === undefined || + !ts.isObjectBindingPattern(pattern) || + declaration === undefined || + !ts.isVariableDeclaration(declaration) || + declaration.initializer === undefined || + (ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const) === 0 + ) + return null; + const key = element.propertyName ?? element.name; + if (!ts.isIdentifier(key)) return null; + const req = inlineProgramRequireOf(lowerer, declaration.initializer); + if (req === null) return null; + return memberAliasIn(lowerer, req.dep, key.text, 0); +} + +function memberAliasIn( + lowerer: Lowerer, + sf: ts.SourceFile, + name: string, + depth: number, +): ts.Expression | null { + const member = memberWritesIn(lowerer, sf, name, depth); + // One write: a member written again would be a runtime slot. + return member !== null && member.writes === 1 ? member.value : null; +} + +/** The writes of member `name` in `sf` and in the modules it re-exports + * through `module.exports = require("./x")`, which share its exports + * object. `value` is the last write's value when the use site can read + * it again. Null when the module does not export a value whole. */ +function memberWritesIn( + lowerer: Lowerer, + sf: ts.SourceFile, + name: string, + depth: number, +): { value: ts.Expression | null; writes: number } | null { + if (depth > 32 || sf.isDeclarationFile || !isCjsJsFile(sf, lowerer.program)) return null; + const whole = cjsWholeExportValueOf(sf); + if (whole === null) return null; + let value: ts.Expression | null = null; + let writes = 0; + const inner = inlineProgramRequireOf(lowerer, whole.rhs); + if (inner !== null) { + // The re-exported module runs first; this module's own writes follow. + const dep = memberWritesIn(lowerer, inner.dep, name, depth + 1); + if (dep === null) return null; + ({ value, writes } = dep); + } + for (const stmt of sf.statements.nodes) { + const cjs = cjsExportAssignmentOf(stmt); + if (cjs?.kind !== "member" || !ts.isIdentifier(cjs.name) || cjs.name.text !== name) continue; + writes++; + if (cjsExportDiscardReason(stmt) !== null) return null; + let written: ts.Expression = cjs.value; + while (ts.isParenthesizedExpression(written)) written = written.expression; + value = rereadableMemberValue(lowerer, stmt, written) ? written : null; + } + return { value, writes }; +} + +/** True when reading `value` again at the use site observes what the + * member write `stmt` stored: a primitive literal, or a module binding + * declared before the write and never reassigned. */ +function rereadableMemberValue( + lowerer: Lowerer, + stmt: ts.Statement, + value: ts.Expression, +): boolean { + if ( + ts.isStringLiteral(value) || + ts.isNumericLiteral(value) || + ts.isNoSubstitutionTemplateLiteral(value) || + value.kind === ts.SyntaxKind.TrueKeyword || + value.kind === ts.SyntaxKind.FalseKeyword + ) + return true; + if (!ts.isIdentifier(value)) return false; + const sym = lowerer.resolveValueSymbol(value); + if (sym === null || sym.declarations.length !== 1) return false; + const decl = sym.declarations[0]!; + if (ts.getSourceFileOfNode(decl) !== ts.getSourceFileOfNode(stmt)) return false; + // A function declaration is initialized when the module starts; any + // other binding must be initialized before the write stores it. + if (!ts.isFunctionDeclaration(decl) && decl.end > stmt.pos) return false; + return bindingNeverReassigned(lowerer, sym, decl); +} diff --git a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts index 174f9dc7c..2b28c6533 100644 --- a/packages/compiler/src/frontend/lowering/cjs-inline-require.ts +++ b/packages/compiler/src/frontend/lowering/cjs-inline-require.ts @@ -56,7 +56,7 @@ export function inlineProgramRequireOf( if (!ts.isCallExpression(e) || !ts.isIdentifier(e.expression)) return null; const spec = requireSpecOf(e); if (spec === null || canonicalBuiltinModule(spec) !== null) return null; - const sf = e.getSourceFile(); + const sf = ts.getSourceFileOfNode(e); if (!isCjsJsFile(sf, lowerer.program)) return null; if (!isCjsRequireGlobal(lowerer, e.expression)) return null; if (createRequireCalleeFileOf(lowerer, e.expression) !== null) return null; @@ -201,7 +201,7 @@ export function lowerUncompiledRequireCall( call: ts.CallExpression, ): IrExpr | null { if (!ts.isIdentifier(call.expression) || call.expression.text !== "require") return null; - const sf = call.getSourceFile(); + const sf = ts.getSourceFileOfNode(call); if (!isCjsJsFile(sf, lowerer.program) || !isCjsRequireGlobal(lowerer, call.expression)) return null; if (createRequireCalleeFileOf(lowerer, call.expression) !== null) return null; @@ -240,7 +240,7 @@ export function requireNestedPatternsOf(decl: ts.VariableDeclaration): ts.Bindin if (!ts.isObjectBindingPattern(decl.name) || decl.initializer === undefined) return []; const spec = requireSpecOf(decl.initializer); if (spec === null || canonicalBuiltinModule(spec) !== null) return []; - if (!isJsSourceFile(decl.getSourceFile())) return []; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) return []; return decl.name.elements.nodes.filter( (e) => e.name !== undefined && diff --git a/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts b/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts index cd83c8709..aae221b30 100644 --- a/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts +++ b/packages/compiler/src/frontend/lowering/cjs-tsc-interop.ts @@ -175,7 +175,7 @@ export function tscInteropDefaultValueOf( export function isDeadTscHelperDecl(lowerer: Lowerer, stmt: ts.Statement): boolean { const name = tscHelperDeclName(stmt); if (name === null) return false; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const helperSpans = new Map(); for (const s of sf.statements.nodes) { const n = tscHelperDeclName(s); @@ -192,11 +192,11 @@ export function isDeadTscHelperDecl(lowerer: Lowerer, stmt: ts.Statement): boole const parent = node.parent; if (parent !== undefined && ts.isVariableDeclaration(parent) && parent.name === node) return undefined; - const at = node.getStart(sf); - if (at >= decl.getStart(sf) && at < decl.getEnd()) return undefined; + const at = ts.tokenStartOfNode(node); + if (at >= ts.tokenStartOfNode(decl) && at < decl.end) return undefined; for (const [other, otherDecl] of helperSpans) { if (other !== helper && dead.has(other)) { - if (at >= otherDecl.getStart(sf) && at < otherDecl.getEnd()) return undefined; + if (at >= ts.tokenStartOfNode(otherDecl) && at < otherDecl.end) return undefined; } } const call = parent; diff --git a/packages/compiler/src/frontend/lowering/class-attributes.ts b/packages/compiler/src/frontend/lowering/class-attributes.ts new file mode 100644 index 000000000..4e7bbe801 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/class-attributes.ts @@ -0,0 +1,440 @@ +import * as ts from "../ts7/adapter.js"; +import { + BOOL, + CLASS_ATTRIBUTES, + CLASS_FROZEN, + CLASS_NOT_EXTENSIBLE, + CLASS_SEALED, + DYN, + F64, + FIELD_NOT_WRITABLE, + STRING, + VOID, + canConvertToDyn, + canDynCheckTo, + classFieldAttributesName, + isClassOwnEnumerableFieldName, + isDynTypedRefType, + type IrExpr, + type IrFunction, + type IrStmt, + type IrType, + type SrcLoc, +} from "../../ir/ir.js"; +import { numLit, strLit, varRef } from "../../ir/build.js"; +import { transformStmtList } from "../../ir/traverse.js"; +import { locOf } from "../program.js"; +import { type Lowerer, PoisonError } from "./lowerer.js"; +import type { ClassInfo } from "./lower-classes.js"; +import { classPropertiesHelper } from "./class-dynamic-dispatch.js"; +import { runtimeRooted } from "./class-descriptors.js"; + +/* Object.freeze, Object.seal, Object.preventExtensions and + * Object.defineProperty on class instances. Only the hierarchies these calls + * name pay for them: such a hierarchy gets a CLASS_ATTRIBUTES integrity word + * per instance, an attribute word per redefined field, and a check before + * each write to a declared field (guardClassAttributeWrites). The instance's + * checked view carries the attributes, so reflection answers from it. */ + +type ClassObjectType = Extract; + +function attributeClass(lowerer: Lowerer, target: IrExpr): ClassInfo | null { + if (!isDynTypedRefType(target.type)) return null; + const info = lowerer.classes.get(target.type.className); + if (!info || runtimeRooted(info) || info.decl == null) return null; + return info; +} + +/** Adds a zero-initialized f64 to every class of the hierarchy, at the same + * offset, like the property bag. */ +function addHiddenField(info: ClassInfo, name: string): void { + let root = info; + while (root.base && !root.base.def.runtime) root = root.base; + if (root.fields.has(name)) return; + const index = root.def.fields.length; + const add = (current: ClassInfo): void => { + if (!current.fields.has(name)) { + current.fields.set(name, F64); + current.def.fields.splice(index, 0, { name, type: F64 }); + } + current.subclasses.forEach(add); + }; + add(root); +} + +function hiddenFieldGet(receiver: IrExpr, className: string, field: string, loc: SrcLoc): IrExpr { + return { kind: "fieldGet", obj: receiver, className, field, type: F64, loc }; +} + +/** Object.freeze, Object.seal and Object.preventExtensions of a class + * instance: sets the instance's integrity bits, then applies the operation to + * the properties added to the instance. Answers the instance. */ +export function lowerClassIntegrity( + lowerer: Lowerer, + call: ts.CallExpression, + member: "freeze" | "seal" | "preventExtensions", + target: IrExpr, +): IrExpr | null { + const info = attributeClass(lowerer, target); + if (!info) return null; + addHiddenField(info, CLASS_ATTRIBUTES); + const loc = locOf(call); + const type = target.type as ClassObjectType; + const bits = + member === "freeze" + ? CLASS_NOT_EXTENSIBLE | CLASS_SEALED | CLASS_FROZEN + : member === "seal" + ? CLASS_NOT_EXTENSIBLE | CLASS_SEALED + : CLASS_NOT_EXTENSIBLE; + const slot = lowerer.declareHiddenLocal("%integrityReceiver", type); + const receiver = (): IrExpr => varRef(slot.id, type, loc); + const bag: IrExpr = { + kind: "call", + callee: classPropertiesHelper(lowerer, loc).name, + args: [lowerer.coerceToExpected(receiver(), DYN)], + type: DYN, + loc, + }; + const operation = { + freeze: "dyn.freeze", + seal: "dyn.seal", + preventExtensions: "dyn.preventExtensions", + } as const; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: slot.id, init: target, loc }, + { + kind: "fieldSet", + obj: receiver(), + className: type.className, + field: CLASS_ATTRIBUTES, + value: { + kind: "bin", + op: "|", + left: hiddenFieldGet(receiver(), type.className, CLASS_ATTRIBUTES, loc), + right: numLit(bits, loc), + type: F64, + loc, + }, + attributesChecked: true, + loc, + }, + { + kind: "exprStmt", + expr: { kind: "libCall", fn: operation[member], args: [bag], type: DYN, loc }, + loc, + }, + ], + result: receiver(), + type, + loc, + }; +} + +/** An object literal with a get or set property. */ +function literalAccessorDescriptor(node: ts.Expression): boolean { + while (ts.isParenthesizedExpression(node)) node = node.expression; + return ( + ts.isObjectLiteralExpression(node) && + node.properties.nodes.some( + (property) => + property.name !== undefined && + (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) && + (property.name.text === "get" || property.name.text === "set"), + ) + ); +} + +/** Object.defineProperty with a string-literal key naming a declared field + * of a class instance: a data descriptor changes the field's value and + * attributes, validated as Node validates ordinary objects. Answers the + * instance, or null when the call is not of that form. An object-literal + * accessor descriptor is refused at compile time; other accessor + * descriptors throw SC2020 when the call runs. */ +export function lowerClassFieldDefine( + lowerer: Lowerer, + call: ts.CallExpression, + target: IrExpr, +): IrExpr | null { + const info = attributeClass(lowerer, target); + const key = call.arguments.nodes[1]!; + if (!info || !ts.isStringLiteral(key)) return null; + const field = key.text; + const fieldType = info.fields.get(field); + if ( + fieldType === undefined || + !isClassOwnEnumerableFieldName(field) || + [...(info.symbolFields?.values() ?? [])].includes(field) + ) + return null; + if (literalAccessorDescriptor(call.arguments.nodes[2]!)) { + lowerer.pushDiag({ + code: "SC2020", + message: `Object.defineProperty with an accessor descriptor for the declared field '${field}' is not supported yet`, + loc: locOf(call), + }); + throw new PoisonError(); + } + const getRecord = (id: string) => lowerer.shapes.get(id); + const getUnion = (id: string) => lowerer.unions.get(id); + if ( + !canConvertToDyn(fieldType, getRecord, getUnion) || + !canDynCheckTo(fieldType, getRecord, getUnion) + ) + return null; + const attributesField = classFieldAttributesName(field); + addHiddenField(info, CLASS_ATTRIBUTES); + addHiddenField(info, attributesField); + const loc = locOf(call); + const type = target.type as ClassObjectType; + const receiverSlot = lowerer.declareHiddenLocal("%defineReceiver", type); + const descriptorSlot = lowerer.declareHiddenLocal("%defineDescriptor", DYN); + const holderSlot = lowerer.declareHiddenLocal("%defineHolder", DYN); + const receiver = (): IrExpr => varRef(receiverSlot.id, type, loc); + const descriptor = (): IrExpr => varRef(descriptorSlot.id, DYN, loc); + const holder = (): IrExpr => varRef(holderSlot.id, DYN, loc); + const name = (): IrExpr => strLit(field, loc); + const current: IrExpr = { + kind: "fieldGet", + obj: receiver(), + className: type.className, + field, + type: fieldType, + loc, + }; + const defined: IrExpr = { + kind: "libCall", + fn: "class.definedValue", + args: [holder(), name()], + type: DYN, + loc, + }; + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: receiverSlot.id, init: target, loc }, + { + kind: "varDecl", + localId: descriptorSlot.id, + init: lowerer.lowerExprExpecting(call.arguments.nodes[2]!, DYN), + loc, + }, + { + kind: "varDecl", + localId: holderSlot.id, + init: { + kind: "libCall", + fn: "class.defineField", + args: [ + lowerer.coerceToExpected(current, DYN), + hiddenFieldGet(receiver(), type.className, attributesField, loc), + hiddenFieldGet(receiver(), type.className, CLASS_ATTRIBUTES, loc), + name(), + descriptor(), + ], + type: DYN, + loc, + }, + loc, + }, + { + kind: "if", + cond: { + kind: "libCall", + fn: "class.descriptorHasValue", + args: [descriptor()], + type: BOOL, + loc, + }, + then: [ + { + kind: "fieldSet", + obj: receiver(), + className: type.className, + field, + value: lowerer.coerceToExpected(defined, fieldType), + attributesChecked: true, + loc, + }, + ], + else_: null, + loc, + }, + { + kind: "fieldSet", + obj: receiver(), + className: type.className, + field: attributesField, + value: { + kind: "libCall", + fn: "class.definedBits", + args: [holder(), name()], + type: F64, + loc, + }, + attributesChecked: true, + loc, + }, + ], + result: receiver(), + type, + loc, + }; +} + +function classDisplayName(info: ClassInfo): string { + return info.def.jsName ?? info.decl?.name?.text ?? info.def.name.replace(/^%/, ""); +} + +/** The instance's own class name for an error message: a helper testing the + * most derived classes of the hierarchy first. */ +function instanceClassName( + lowerer: Lowerer, + info: ClassInfo, + receiver: IrExpr, + helpers: Map, + loc: SrcLoc, +): IrExpr { + if (info.subclasses.length === 0) return strLit(classDisplayName(info), loc); + let helper = helpers.get(info.def.name); + if (!helper) { + const type: IrType = { kind: "object", className: info.def.name }; + const value = varRef("value", type, loc); + const descendants = (current: ClassInfo): ClassInfo[] => + current.subclasses.flatMap((child) => [...descendants(child), child]); + const body: IrStmt[] = descendants(info).map((child): IrStmt => ({ + kind: "if", + cond: { kind: "instanceOf", value, className: child.def.name, type: BOOL, loc }, + then: [{ kind: "return", value: strLit(classDisplayName(child), loc), loc }], + else_: null, + loc, + })); + body.push({ kind: "return", value: strLit(classDisplayName(info), loc), loc }); + helper = { + name: `%class.instanceName.${helpers.size}`, + params: [{ localId: "value", name: "value", type }], + returnType: STRING, + locals: [{ id: "value", name: "value", type, mutable: false }], + body, + loc, + }; + helpers.set(info.def.name, helper); + } + return { kind: "call", callee: helper.name, args: [receiver], type: STRING, loc }; +} + +/** Checks the integrity word, and the field's attribute word when it has one, + * before every write to a declared field of a hierarchy that has them. A + * read-only field throws Node's TypeError in strict mode code; a sloppy-mode + * write leaves it unchanged. Other classes keep their plain stores. */ +export function guardClassAttributeWrites(lowerer: Lowerer, functions: IrFunction[]): void { + const guarded = new Set( + [...lowerer.classes.values()] + .filter((info) => info.def.fields.some((field) => field.name === CLASS_ATTRIBUTES)) + .map((info) => info.def.name), + ); + if (guarded.size === 0) return; + const helpers = new Map(); + for (const fn of functions) { + const local = (type: IrType, loc: SrcLoc): Extract => { + const id = `%attributeWrite.${fn.locals.length}`; + fn.locals.push({ id, name: id, type, mutable: false }); + return { kind: "varRef", localId: id, type, loc }; + }; + fn.body = transformStmtList(fn.body, { + stmt: (statement) => { + if ( + statement.kind !== "fieldSet" || + statement.attributesChecked || + !guarded.has(statement.className) || + !isClassOwnEnumerableFieldName(statement.field) + ) + return statement; + const info = lowerer.classes.get(statement.className)!; + const loc = statement.loc; + const receiver = local(statement.obj.type, loc); + const value = local(statement.value.type, loc); + const masked = (field: string, bit: number): IrExpr => ({ + kind: "bin", + op: "&", + left: hiddenFieldGet(receiver, statement.className, field, loc), + right: numLit(bit, loc), + type: F64, + loc, + }); + const attributesField = classFieldAttributesName(statement.field); + let readOnly = masked( + CLASS_ATTRIBUTES, + statement.define ? CLASS_NOT_EXTENSIBLE : CLASS_FROZEN, + ); + if (!statement.define && info.fields.has(attributesField)) { + readOnly = { + kind: "bin", + op: "|", + left: readOnly, + right: masked(attributesField, FIELD_NOT_WRITABLE), + type: F64, + loc, + }; + } + const reject: IrStmt[] = statement.define + ? [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "class.throwNotExtensible", + args: [strLit(statement.field, loc)], + type: VOID, + loc, + }, + loc, + }, + ] + : statement.sloppy + ? [] + : [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "class.throwReadOnly", + args: [ + strLit(statement.field, loc), + instanceClassName(lowerer, info, receiver, helpers, loc), + ], + type: VOID, + loc, + }, + loc, + }, + ]; + return { + kind: "block", + body: [ + { kind: "varDecl", localId: receiver.localId, init: statement.obj, loc }, + { kind: "varDecl", localId: value.localId, init: statement.value, loc }, + { + kind: "if", + cond: { + kind: "bin", + op: "!==", + left: readOnly, + right: numLit(0, loc), + type: BOOL, + loc, + }, + then: reject, + else_: [{ ...statement, obj: receiver, value, attributesChecked: true }], + loc, + }, + ], + loc, + }; + }, + expr: (expression) => expression, + }); + } + for (const helper of helpers.values()) functions.push(helper); +} diff --git a/packages/compiler/src/frontend/lowering/class-callbacks.ts b/packages/compiler/src/frontend/lowering/class-callbacks.ts index 859938845..6ca685684 100644 --- a/packages/compiler/src/frontend/lowering/class-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/class-callbacks.ts @@ -39,8 +39,8 @@ export function isClassCallback(lowerer: Lowerer, info: ClassInfo, name: string) !found.sig.abstract && (lowerer.prototypeMethodAccesses.has(name) || (info.decl !== null && - isJsSourceFile(info.decl.getSourceFile()) && - !lowerer.checkedJsFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && + !lowerer.checkedJsFile(ts.getSourceFileOfNode(info.decl)) && found.sig.params.length === 0 && found.sig.ret.kind === "void")) ); @@ -171,7 +171,7 @@ export function classCallbackCall( const spreads: { arg: number; what: string }[] = []; const args = argumentNodes.map((argument, index) => { if (!ts.isSpreadElement(argument)) return lowerer.lowerExprExpecting(argument, DYN); - const spelling = argument.expression.getText(); + const spelling = ts.getTextOfNode(argument.expression); spreads.push({ arg: index, what: spelling }); return checkedIterableSpread(lowerer, lowerer.lowerExpr(argument.expression), spelling, loc); }); diff --git a/packages/compiler/src/frontend/lowering/class-construction.ts b/packages/compiler/src/frontend/lowering/class-construction.ts index 7f4a003c9..30f504b4a 100644 --- a/packages/compiler/src/frontend/lowering/class-construction.ts +++ b/packages/compiler/src/frontend/lowering/class-construction.ts @@ -18,6 +18,7 @@ import type { Lowerer } from "./lowerer.js"; import type { ParamShape } from "./call-signatures.js"; import { classPrototypeData } from "./class-prototypes.js"; import { classInstanceOf } from "./class-dynamic-dispatch.js"; +import { inheritedBuiltinCtorFence } from "./lower-classes.js"; const PREFIX = "%class.construct:"; const PACKED = `${PREFIX}packed`; @@ -380,6 +381,29 @@ export class ClassConstructionDispatch { changed = true; continue; } + const type: IrType = { kind: "classval", className: name }; + const callee = varRef("p.0", DYN, loc); + // A constructor inherited from a runtime stream or EventEmitter + // takes no arguments here: arguments report at run time, as they do + // at compile time for typed construction. + const fence = inheritedBuiltinCtorFence(lowerer, info, loc); + if (fence !== null && fn.name !== PACKED && fn.params.length > 1) { + fn.body.unshift({ + kind: "if", + cond: { + kind: "libCall", + fn: "dyn.classIs", + args: [callee, { kind: "strLit", value: typeKey(type), type: STRING, loc }], + type: BOOL, + loc, + }, + then: [fence], + else_: null, + loc, + }); + changed = true; + continue; + } const args = constructorArguments( lowerer, info.ctorParams, @@ -388,8 +412,6 @@ export class ClassConstructionDispatch { fn.name === PACKED ? varRef("p.1", DYN, loc) : undefined, ); if (!args) continue; - const type: IrType = { kind: "classval", className: name }; - const callee = varRef("p.0", DYN, loc); const value = constructedValue(lowerer, info, { kind: "newValue", callee: { kind: "dynCheck", value: callee, type, loc }, @@ -400,6 +422,28 @@ export class ClassConstructionDispatch { const body: IrStmt[] = [ { kind: "return", value: lowerer.coerceToExpected(value, DYN), loc }, ]; + // A spread call's argument count is known only at run time. + if (fence !== null && fn.name === PACKED) + body.unshift({ + kind: "if", + cond: { + kind: "bin", + op: ">", + left: { + kind: "libCall", + fn: "dyn.arrLen", + args: [varRef("p.1", DYN, loc)], + type: F64, + loc, + }, + right: { kind: "numLit", value: 0, type: F64, loc }, + type: BOOL, + loc, + }, + then: [fence], + else_: null, + loc, + }); fn.body.unshift({ kind: "if", cond: { diff --git a/packages/compiler/src/frontend/lowering/class-descriptors.ts b/packages/compiler/src/frontend/lowering/class-descriptors.ts index 473ed2496..cb980f0d0 100644 --- a/packages/compiler/src/frontend/lowering/class-descriptors.ts +++ b/packages/compiler/src/frontend/lowering/class-descriptors.ts @@ -23,7 +23,7 @@ function bagOnlyProperty(owner: ClassInfo, name: string): boolean { return !owner.fields.has(name) && owner.subclasses.every((child) => bagOnlyProperty(child, name)); } -function runtimeRooted(info: ClassInfo): boolean { +export function runtimeRooted(info: ClassInfo): boolean { for (let current: ClassInfo | null = info; current; current = current.base) { if ( current.def.runtime || diff --git a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts index 6b515c1ff..de84e3fed 100644 --- a/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-dynamic-dispatch.ts @@ -1,3 +1,4 @@ +import { primitiveConstructorOr } from "./lower-exprs.js"; import { dynUndefinedExpr, helperLoc, nodeThrowExpr, varRef } from "../../ir/build.js"; import { constructedValue } from "./constructor-returns.js"; import { eventTargetApiMembers, eventTargetInheritedMember } from "./lower-event-target.js"; @@ -44,6 +45,7 @@ import { observedPrototypeMethod, reflectClassPrototype, } from "./class-prototypes.js"; +import { perEvaluationPrivateNames } from "./private-brands.js"; import { errorPropertyRead, errorPropertyWrite, @@ -1156,7 +1158,7 @@ export class ClassDynamicDispatch { if (this.bagClasses.has(plan.key)) continue; this.bagClasses.add(plan.key); this.ensurePropertyBag(info); - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { let root = info; while (root.base && !root.base.def.runtime) root = root.base; const track = (current: ClassInfo): void => { @@ -2069,6 +2071,49 @@ export class ClassDynamicDispatch { // A read compares the bag with the receiver and then reads it: // evaluate it once, so each read retains the receiver once. const bagRead = varRef("bag.0", DYN, loc); + const bagGet: IrExpr | null = read + ? { + kind: "libCall", + fn: "dyn.bagGet", + args: [bagRead, read.key, varRef("p.0", DYN, loc)], + type: DYN, + loc, + } + : null; + // A class instance whose class chain declares no member of an + // Object.prototype method's name (classes that do answer in + // their own branches) inherits it unless an own property + // shadows it; a class constructor's static bag reads as before. + const instanceRead: IrExpr | null = + read && INHERITED_OBJECT_METHODS.has(name) + ? { + kind: "ternary", + cond: { + kind: "dynTest", + test: "function", + value: varRef("p.0", DYN, loc), + type: BOOL, + loc, + }, + then: bagGet!, + else_: { + kind: "ternary", + cond: this.readBagHas(lowerer, "dyn.hasKey", bagRead, read.key, loc), + then: bagGet!, + else_: { + kind: "libCall", + fn: "dyn.builtinMethod", + args: [{ kind: "strLit", value: "Object", type: STRING, loc }, read.key], + type: DYN, + loc, + }, + type: DYN, + loc, + }, + type: DYN, + loc, + } + : bagGet; const fallback: IrExpr = read ? { kind: "ternary", @@ -2080,13 +2125,7 @@ export class ClassDynamicDispatch { loc, }, then: { ...read, value: bagRead }, - else_: { - kind: "libCall", - fn: "dyn.bagGet", - args: [bagRead, read.key, varRef("p.0", DYN, loc)], - type: DYN, - loc, - }, + else_: instanceRead!, type: DYN, loc, } @@ -2111,7 +2150,20 @@ export class ClassDynamicDispatch { ] : [ { kind: "varDecl", localId: "bag.0", init: bag, loc }, - { kind: "return", value: fallback, loc }, + { + kind: "return", + // Primitives answer their wrapper constructors. + value: + name === "constructor" + ? primitiveConstructorOr( + lowerer, + varRef("p.0", DYN, loc), + fallback, + loc, + ) + : fallback, + loc, + }, ], loc, }; @@ -2694,6 +2746,7 @@ export class ClassDynamicDispatch { !findGenericMethodOn(lowerer, info, dispatch.name) && dispatch.name !== "constructor" && !info.fields.has(dispatch.name) && + !(dispatch.name === "stack" && lowerer.isSubclassOf(info.def.name, "%Error")) && !findMethodOn(lowerer, info, `get:${dispatch.name}`) && !findMethodOn(lowerer, info, `set:${dispatch.name}`)) ) @@ -2885,10 +2938,12 @@ export class ClassDynamicDispatch { for (const [symbol, name] of info.symbolMethods) symbolEntries.push([symbol, name]); for (const [symbol, name] of symbolEntries) { const declaration = symbol.valueDeclaration; + // Module-scope consts and CommonJS key-table properties + // (classSymbolKeyOfSymbol) both hold their key in a global. if ( declaration && - ts.isVariableDeclaration(declaration) && - ts.isIdentifier(declaration.name) + ((ts.isVariableDeclaration(declaration) && ts.isIdentifier(declaration.name)) || + ts.isPropertyAssignment(declaration)) ) { const global = lowerer.globalsBySymbol.get(symbol); if (global) @@ -3191,7 +3246,7 @@ export class ClassDynamicDispatch { for (const name of this.boxedConstructors) { if (this.staticBagClasses.has(name)) continue; const info = lowerer.classes.get(name); - if (!info?.decl || !isJsSourceFile(info.decl.getSourceFile())) continue; + if (!info?.decl || !isJsSourceFile(ts.getSourceFileOfNode(info.decl))) continue; const loc = this.staticPropertyBag.loc; const bag = classStaticData(lowerer, info, loc); if (!bag) continue; @@ -4311,19 +4366,43 @@ export class ClassDynamicDispatch { loc, }; if (accessor && info.decl) { - const method = classMethodValue(lowerer, info.decl, info, member, loc); - if (method) { - const call: IrExpr = { - kind: "libCall", - fn: "dyn.reflectApply", - args: [ - lowerer.coerceToExpected(method, DYN), - actualReceiver, - { kind: "dynArrLit", elems: write ? [varRef("p.1", DYN, loc)] : [], type: DYN, loc }, - ], - type: DYN, - loc, - }; + // An accessor whose private names belong to one evaluation runs as + // the instance's own evaluation's prototype accessor. + const declarer = accessor.declarer; + const prototype = perEvaluationPrivateNames(declarer) + ? classPrototypeData(lowerer, declarer, loc, upcastTo(lowerer, receiver, declarer.def.name)) + : null; + const method = prototype ? null : classMethodValue(lowerer, info.decl, info, member, loc); + if (prototype || method) { + const call: IrExpr = prototype + ? { + kind: "libCall", + fn: write ? "dyn.reflectSet" : "dyn.reflectGet", + args: [ + prototype, + lowerer.coerceToExpected(key, DYN), + ...(write ? [varRef("p.1", DYN, loc)] : []), + actualReceiver, + ], + type: write ? BOOL : DYN, + loc, + } + : { + kind: "libCall", + fn: "dyn.reflectApply", + args: [ + lowerer.coerceToExpected(method!, DYN), + actualReceiver, + { + kind: "dynArrLit", + elems: write ? [varRef("p.1", DYN, loc)] : [], + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }; return [ { kind: "if", @@ -4498,6 +4577,110 @@ export class ClassDynamicDispatch { }, ]; } + // An Error's own `stack`: a string is the native error's stack, which + // typed reads share; any other value is kept with the instance's own + // properties, which a read checks first. A class whose chain declares + // a `stack` accessor answers through it (the accessor code below). + if ( + name === "stack" && + !info.fields.has(name) && + lowerer.isSubclassOf(info.def.name, "%Error") && + !findMethodOn(lowerer, info, "get:stack") && + !findMethodOn(lowerer, info, "set:stack") + ) { + const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc }; + const error = lowerer.upcastTo(receiver, "%Error"); + if (write) { + const helper = classPropertiesHelper(lowerer, loc); + this.generated.add(helper); + const store: IrStmt = { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.keySet", + args: [ + { + kind: "call", + callee: helper.name, + args: [lowerer.coerceToExpected(receiver, DYN)], + type: DYN, + loc, + }, + key, + varRef("p.1", DYN, loc), + ], + type: VOID, + loc, + }, + loc, + }; + return [ + { + kind: "if", + cond: { + kind: "dynTest", + test: "string", + value: varRef("p.1", DYN, loc), + type: BOOL, + loc, + }, + then: [ + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "error.setStack", + args: [error, lowerer.coerceToExpected(varRef("p.1", DYN, loc), STRING)], + type: VOID, + loc, + }, + loc, + }, + { + kind: "if", + cond: this.readBagHas( + lowerer, + "dyn.hasKey", + this.readPropertyBag(lowerer, receiver, loc), + key, + loc, + ), + then: [store], + else_: null, + loc, + }, + ], + else_: [store], + loc, + }, + { kind: "return", value: null, loc }, + ]; + } + const bag = this.readPropertyBag(lowerer, receiver, loc); + return [ + { + kind: "return", + value: { + kind: "ternary", + cond: this.readBagHas(lowerer, "dyn.hasKey", bag, key, loc), + then: { + kind: "libCall", + fn: "dyn.bagGet", + args: [bag, key, lowerer.coerceToExpected(receiver, DYN)], + type: DYN, + loc, + }, + else_: lowerer.coerceToExpected( + { kind: "libCall", fn: "error.stack", args: [error], type: STRING, loc }, + DYN, + ), + type: DYN, + loc, + }, + loc, + }, + ]; + } if ((name === "message" || name === "name") && lowerer.isSubclassOf(info.def.name, "%Error")) return write ? [ @@ -4688,19 +4871,40 @@ export class ClassDynamicDispatch { if (!field && !accessor) { const instance = lowerer.coerceToExpected(receiver, DYN); const bag = this.readPropertyBagOrEmpty(lowerer, instance, loc); - return [ - { - kind: "return", - value: { - kind: "libCall", - fn: "dyn.bagGet", - args: [bag, { kind: "strLit", value: name, type: STRING, loc }, instance], - type: DYN, + const key: IrExpr = { kind: "strLit", value: name, type: STRING, loc }; + const own: IrExpr = { + kind: "libCall", + fn: "dyn.bagGet", + args: [bag, key, instance], + type: DYN, + loc, + }; + // An Object.prototype method no class in the chain declares: an + // own property shadows it, otherwise the read answers the same + // interned value `Object.prototype.` reads. + if (INHERITED_OBJECT_METHODS.has(name) && !findMethodOn(lowerer, info, name)) { + return [ + { + kind: "return", + value: { + kind: "ternary", + cond: this.readBagHas(lowerer, "dyn.hasKey", bag, key, loc), + then: own, + else_: { + kind: "libCall", + fn: "dyn.builtinMethod", + args: [{ kind: "strLit", value: "Object", type: STRING, loc }, key], + type: DYN, + loc, + }, + type: DYN, + loc, + }, loc, }, - loc, - }, - ]; + ]; + } + return [{ kind: "return", value: own, loc }]; } const type = field ?? accessor!.sig.ret; if (!canConvertToDyn(type, getRecord, getUnion)) return fence(); @@ -5223,3 +5427,12 @@ export class ClassDynamicDispatch { return body; } } + +/** Object.prototype methods every class instance inherits unless its class + * chain declares them. */ +const INHERITED_OBJECT_METHODS: ReadonlySet = new Set([ + "valueOf", + "toString", + "hasOwnProperty", + "propertyIsEnumerable", +]); diff --git a/packages/compiler/src/frontend/lowering/class-iterables.ts b/packages/compiler/src/frontend/lowering/class-iterables.ts new file mode 100644 index 000000000..8c58fc0bd --- /dev/null +++ b/packages/compiler/src/frontend/lowering/class-iterables.ts @@ -0,0 +1,35 @@ +import type { IrExpr, IrType } from "../../ir/ir.js"; +import type { Lowerer } from "./lowerer.js"; +import { accessorCall } from "./lower-classes.js"; + +/** The generator a class instance's `[Symbol.iterator]` returns, when that + * method is a generator method taking no arguments: `obj[Symbol.iterator]()`, + * called once, as for-of, spread and Array.from call it. Null for other + * values, and for classes whose iterator the program can replace at run + * time (an instance property, a prototype write). */ +export function classGeneratorIterable(lowerer: Lowerer, value: IrExpr): IrExpr | null { + if (value.type.kind !== "object" || !iteratesThroughGenerator(lowerer, value.type)) return null; + const className = value.type.className; + const ret = lowerer.findMethodOn(lowerer.classes.get(className)!, "sym:iterator")!.sig.ret; + return accessorCall(lowerer, className, "sym:iterator", value, [], ret, value.loc); +} + +/** Whether values of `type` iterate through a generator method (the check + * classGeneratorIterable makes, from the type alone). */ +export function iteratesThroughGenerator(lowerer: Lowerer, type: IrType): boolean { + if (type.kind !== "object") return false; + const info = lowerer.classes.get(type.className); + if (!info || info.def.runtime || info.fields.has("sym:iterator")) return false; + if ( + lowerer.prototypeMethodAccesses.has("sym:iterator") || + lowerer.replacedSymbolMethods.has("sym:iterator") + ) + return false; + const found = lowerer.findMethodOn(info, "sym:iterator"); + return ( + !!found && + found.sig.params.length === 0 && + found.sig.ret.kind === "generator" && + !found.sig.ret.async + ); +} diff --git a/packages/compiler/src/frontend/lowering/class-method-mutations.ts b/packages/compiler/src/frontend/lowering/class-method-mutations.ts index c07ec9136..cc8700469 100644 --- a/packages/compiler/src/frontend/lowering/class-method-mutations.ts +++ b/packages/compiler/src/frontend/lowering/class-method-mutations.ts @@ -43,9 +43,19 @@ export function collectClassMethodMutations( const jsAssignments: (ts.PropertyAccessExpression | ts.ElementAccessExpression)[] = []; const computedMethods: ts.Expression[] = []; const typedClasses: (ts.ClassDeclaration | ts.ClassExpression)[] = []; + const symbolMethods = new Set(); let unknownKey: ts.Node | undefined; let numericKey: ts.Node | undefined; let unknownOwnField = false; + // `sym:iterator` for `Symbol.iterator`: the name a class method declared + // with a well-known symbol key takes. + const wellKnownMethodName = (key: ts.Expression): string | null => { + while (ts.isParenthesizedExpression(key)) key = key.expression; + if (!ts.isPropertyAccessExpression(key)) return null; + const member = lowerer.stdlibGlobalMember(key, "Symbol"); + if (member === null || !(lowerer.typeOf(key).flags & ts.TypeFlags.ESSymbolLike)) return null; + return `sym:${member}`; + }; // JavaScript method inference and dynamic class factories keep their // existing dispatch contract. The native-call proof applies to typed // declarations (including JavaScript files TypeScript checks), while @@ -142,10 +152,11 @@ export function collectClassMethodMutations( const required = properties.filter( (property) => (property.flags & ts.SymbolFlags.Optional) === 0, ); - if (required.some((property) => !FUNCTION_MEMBER_NAMES.has(property.name))) return true; + if (required.some((property) => !FUNCTION_MEMBER_NAMES.has(ts.symbolName(property)))) + return true; return ( required.length === 0 && - properties.every((property) => !FUNCTION_MEMBER_NAMES.has(property.name)) + properties.every((property) => !FUNCTION_MEMBER_NAMES.has(ts.symbolName(property))) ); }; // A string index whose values cannot be functions excludes callable @@ -157,7 +168,7 @@ export function collectClassMethodMutations( .getIndexInfosOfType(lowerer.typeOf(value)) .some( (index) => - (index.keyType.flags & ts.TypeFlags.StringLike) !== 0 && functionFree(index.valueType), + (index.keyType.flags & ts.TypeFlags.StringLike) !== 0 && functionFree(index.type), ); const literalLoopKeys = (key: ts.Expression): (string | number)[] | null => { if (!ts.isIdentifier(key)) return null; @@ -555,6 +566,19 @@ export function collectClassMethodMutations( lowerer.prototypeMethodAccesses.set(name, site); } for (const { key, receiver, site } of computedWrites) { + // A class method declared with a well-known symbol key is not a write; + // any other write with one can replace that method. + const symbolName = key ? wellKnownMethodName(unwrap(key)) : null; + if (symbolName) { + const declaration = + (ts.isMethodDeclaration(site) || + ts.isGetAccessorDeclaration(site) || + ts.isSetAccessorDeclaration(site)) && + (ts.isClassDeclaration(site.parent) || ts.isClassExpression(site.parent)); + if (declaration) symbolMethods.add(symbolName); + else lowerer.replacedSymbolMethods.add(symbolName); + continue; + } const type = key ? lowerer.typeOf(key) : undefined; const names = type && (literalValues(type) ?? (key ? literalLoopKeys(unwrap(key)) : null)); if (names) { @@ -585,8 +609,10 @@ export function collectClassMethodMutations( if (String(Number(name)) === name) markName(name, numericKey); } } - if (unknownKey) + if (unknownKey) { for (const method of methods) lowerer.prototypeMethodAccesses.set(method, unknownKey); + for (const method of symbolMethods) lowerer.replacedSymbolMethods.add(method); + } if (!unknownKey && !unknownOwnField) for (const field of arrowFields) { if (!receiverFields.has(field)) lowerer.receiverFreeCallbackFields.add(field); diff --git a/packages/compiler/src/frontend/lowering/class-method-values.ts b/packages/compiler/src/frontend/lowering/class-method-values.ts index 10572377e..1197d9bdd 100644 --- a/packages/compiler/src/frontend/lowering/class-method-values.ts +++ b/packages/compiler/src/frontend/lowering/class-method-values.ts @@ -27,6 +27,7 @@ import { funcTypeFromParamShapes, type ParamShape } from "./call-signatures.js"; import { erasedGenericInstance, implicitDefaultInstance } from "./generic-functions.js"; import { errorToStringMethod } from "./error-methods.js"; import { classCallbackValue, isClassCallback } from "./class-callbacks.js"; +import { perEvaluationPrivateNames } from "./private-brands.js"; export interface ClassMethodValueSelection { expression: ts.PropertyAccessExpression; @@ -51,18 +52,20 @@ export function lowerClassMethodValue( } const loc = locOf(expr); const receiver = lowerer.lowerExpr(expr.expression); + const declared = findMethodOn(lowerer, info, method); // A JS checker may name a class while the value is held in checked // storage. Read its actual property so own replacements and getters - // participate, and never pass a dynamic value to a native selector. - if (receiver.type.kind === "dyn") + // participate, and never pass a dynamic value to a native selector. A + // method whose private names belong to one evaluation is that + // evaluation's prototype method as well. + if (receiver.type.kind === "dyn" || (!!declared && perEvaluationPrivateNames(declared.declarer))) return { kind: "dynKeyGet", - value: receiver, + value: lowerer.coerceToExpected(receiver, DYN), key: { kind: "strLit", value: method, type: STRING, loc }, type: DYN, loc, }; - const declared = findMethodOn(lowerer, info, method); // Abstract declarations have no prototype function. Concrete descendants // provide the extracted value through the same reachability-driven selector. let value = declared?.sig.abstract @@ -89,7 +92,8 @@ export function lowerClassMethodValue( loc, }; }; - const checked = isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(value.type); + const checked = + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(value.type); if (checked) value = lowerer.coerceToExpected(value, DYN); // Select the declaration when extracting the value. Calling the value // later must not redispatch the method name on a different receiver. @@ -371,7 +375,7 @@ function reflectedMethodValue( if (!member || (!ts.isMethodDeclaration(member) && !ts.isAccessor(member)) || !member.body) return null; let needsNativeReceiver = - !isJsSourceFile(declaration.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) || info.localClass !== undefined || lowerer.errorHierarchyClassOf(info.def.name); ts.walkPreorder(member, (node) => { @@ -537,10 +541,13 @@ export function finalizeClassMethodValues(lowerer: Lowerer, functions: IrFunctio !(expr.kind === "closure" && expr.captures.includes(thisParam.localId)), }); if (usesThis) continue; - // A method that never uses this remains callable when detached. + // A method that never uses this remains callable when detached. Without + // class captures it needs no class environment, so the detached copy + // drops the (empty) capture list, which requires a receiver. const name = `%method.receiverless:${target.name}`; + const { classCaptures: _classCaptures, ...detached } = target; functions.push({ - ...target, + ...detached, name, params: target.params.slice(1), locals: target.locals.filter((local) => local.id !== thisParam.localId), diff --git a/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts b/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts index 0c9a24f24..33515bf2c 100644 --- a/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts +++ b/packages/compiler/src/frontend/lowering/class-protocol-dispatch.ts @@ -87,7 +87,7 @@ function codeOwner(node: ts.Node): ts.Node { !ts.isPropertyDeclaration(current) ) current = current.parent; - return current ?? node.getSourceFile(); + return current ?? ts.getSourceFileOfNode(node); } /** A local variable or parameter that only its own function's code @@ -102,21 +102,21 @@ function ownedBinding(lowerer: Lowerer, id: ts.Identifier): boolean { const decl = symbol.valueDeclaration; let owned = decl !== undefined && - decl.getSourceFile() === id.getSourceFile() && + ts.getSourceFileOfNode(decl) === ts.getSourceFileOfNode(id) && ((ts.isVariableDeclaration(decl) && ts.isIdentifier(decl.name)) || (ts.isParameter(decl) && ts.isIdentifier(decl.name))); if (owned && decl) { const owner = codeOwner(decl); const names = (node: ts.Node): boolean => ts.isIdentifier(node) && - node.text === symbol.name && + node.text === ts.symbolName(symbol) && lowerer.resolveValueSymbol(node) === symbol; const written = (target: ts.Node): boolean => { let found = false; const walk = (node: ts.Node): void => { if (found) return; if (names(node)) found = true; - else node.forEachChild(walk); + else ts.forEachChild(node, walk); }; walk(target); return found; @@ -144,9 +144,9 @@ function ownedBinding(lowerer: Lowerer, id: ts.Identifier): boolean { ) target = node.initializer; if (target && codeOwner(node) !== owner && written(target)) owned = false; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - owner.forEachChild(visit); + ts.forEachChild(owner, visit); } lowerer.protocolOwnedBindings.set(symbol, owned); return owned; @@ -188,7 +188,8 @@ function protocolSignature( call: ts.CallExpression, access: ts.PropertyAccessExpression, ): ProtocolSignature | null { - const recvTs = lowerer.typeOf(access.expression); + // A guarded optional step's receiver still carries its nullish arms. + const recvTs = lowerer.checker.getNonNullableType(lowerer.typeOf(access.expression)); const property = lowerer.checker.getPropertyOfType(recvTs, access.name.text); if (!property) return null; const declarations = property.declarations; @@ -240,10 +241,11 @@ export function lowerProtocolMethodCall( access: ts.PropertyAccessExpression, recv: IrExpr, ): IrExpr | null { + // An optional chain that already guarded this step (`s?.m(x)`, the chain + // re-dispatching with the narrowed receiver) calls like a plain one. if ( recv.type.kind !== "dyn" || - call.questionDotToken || - access.questionDotToken || + lowerer.chainBlocked(call, access) || call.arguments.nodes.some(ts.isSpreadElement) ) return null; @@ -300,7 +302,7 @@ export function lowerProtocolMethodCall( kind: "dynCall", callee: probed ? varRef("p.callee", DYN, loc) : read(), receiver: varRef("p.0", DYN, loc), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), calleeNameValue: varRef("p.calleeName", STRING, loc), args: params.map((type, i) => lowerer.coerceToExpected(varRef(`p.${i + 1}`, type, loc), DYN)), type: DYN, @@ -376,7 +378,7 @@ export function lowerProtocolMethodCall( receiver, ...(callee ? [varRef(callee.id, DYN, loc)] : []), ...args, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: result, loc, @@ -485,7 +487,7 @@ export function lowerProtocolPropertyRead( lowerer: Lowerer, access: ts.PropertyAccessExpression, ): IrExpr | null { - if (isJsSourceFile(access.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(access))) return null; const receiverType = lowerer.typeOf(access.expression); if (!lowerer.classImplementedProtocol(receiverType)) return null; const name = access.name.text; diff --git a/packages/compiler/src/frontend/lowering/class-protocols.ts b/packages/compiler/src/frontend/lowering/class-protocols.ts index 6208ab9f1..172224053 100644 --- a/packages/compiler/src/frontend/lowering/class-protocols.ts +++ b/packages/compiler/src/frontend/lowering/class-protocols.ts @@ -48,9 +48,9 @@ export class ClassProtocols { ) ) continue; - const entries = this.methods.get(property.name) ?? []; + const entries = this.methods.get(ts.symbolName(property)) ?? []; entries.push(node); - this.methods.set(property.name, entries); + this.methods.set(ts.symbolName(property), entries); } } if ( @@ -506,7 +506,7 @@ export class ClassProtocols { if (facts.observed.has(symbol) || facts.written.has(symbol)) return true; const target = facts.targets.get(symbol); if (target === undefined) return true; - const names = new Set(this.checker.getPropertiesOfType(target).map((p) => p.name)); + const names = new Set(this.checker.getPropertiesOfType(target).map((p) => ts.symbolName(p))); const origins = facts.origins.get(symbol) ?? new Set(); const writesNames = (written: Set | "all"): boolean => written === "all" || [...written].some((name) => names.has(name)); @@ -551,7 +551,7 @@ export class ClassProtocols { .getPropertiesOfType(declared) .some( (property) => - names.has(property.name) && + names.has(ts.symbolName(property)) && property.declarations.some( (declaration) => ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration), @@ -610,7 +610,8 @@ export class ClassProtocols { * method needs a live receiver (see protocol). */ private pickOfProgramClass(type: ts.Type): boolean { const alias = type.getAliasSymbol(); - if (alias === undefined || (alias.name !== "Pick" && alias.name !== "Omit")) return false; + if (alias === undefined || (ts.symbolName(alias) !== "Pick" && ts.symbolName(alias) !== "Omit")) + return false; if (!alias.declarations.every((declaration) => declaration.getSourceFile().isDeclarationFile)) return false; const source = type.getAliasTypeArguments()[0]; @@ -622,7 +623,9 @@ export class ClassProtocols { return ( symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0 && - symbol.declarations.some((declaration) => !declaration.getSourceFile().isDeclarationFile) && + symbol.declarations.some( + (declaration) => !ts.getSourceFileOfNode(declaration).isDeclarationFile, + ) && // `typeof C` (the class value) shares the class symbol. this.checker.getConstructSignatures(type).length === 0 ); @@ -647,7 +650,8 @@ export class ClassProtocols { this.methods.size > 0 && symbol.declarations.some( (declaration) => - ts.isInterfaceDeclaration(declaration) && !declaration.getSourceFile().isDeclarationFile, + ts.isInterfaceDeclaration(declaration) && + !ts.getSourceFileOfNode(declaration).isDeclarationFile, ) && this.protocol(type) ); @@ -666,7 +670,7 @@ export class ClassProtocols { const declarations = symbol.declarations; return ( declarations.length > 0 && - declarations.every((declaration) => !declaration.getSourceFile().isDeclarationFile) + declarations.every((declaration) => !ts.getSourceFileOfNode(declaration).isDeclarationFile) ); } @@ -755,14 +759,14 @@ export class ClassProtocols { !symbol.declarations.some( (declaration) => ts.isInterfaceDeclaration(declaration) && - !declaration.getSourceFile().isDeclarationFile, + !ts.getSourceFileOfNode(declaration).isDeclarationFile, )) ) return false; for (const property of properties) { const memberType = this.checker.getTypeOfSymbol(property); if (this.checker.getCallSignatures(memberType).length === 0) continue; - for (const declaration of this.methods.get(property.name) ?? []) { + for (const declaration of this.methods.get(ts.symbolName(property)) ?? []) { const classSymbol = declaration.name ? this.checker.getSymbolAtLocation(declaration.name) : undefined; diff --git a/packages/compiler/src/frontend/lowering/class-prototypes.ts b/packages/compiler/src/frontend/lowering/class-prototypes.ts index 2787c9021..6c6c7811d 100644 --- a/packages/compiler/src/frontend/lowering/class-prototypes.ts +++ b/packages/compiler/src/frontend/lowering/class-prototypes.ts @@ -1,4 +1,5 @@ import { dynUndefinedExpr, varRef } from "../../ir/build.js"; +import { builtinClassKind, builtinClassValue } from "./builtins/builtin-class-dyn.js"; import * as ts from "../ts7/adapter.js"; import { BOOL, @@ -22,7 +23,7 @@ import { type ClassInfo, } from "./lower-classes.js"; import type { Lowerer } from "./lowerer.js"; -import { newFnCtx } from "./lowerer.js"; +import { PoisonError, newFnCtx } from "./lowerer.js"; import { classMethodValue } from "./class-method-values.js"; /** Data added to a top-level class prototype has shared identity and remains @@ -376,14 +377,37 @@ export function reflectClassPrototype( const method = ts.isMethodDeclaration(member) ? name : `${ts.isGetAccessor(member) ? "get" : "set"}:${name}`; - const value = classMethodValue(lowerer, info.decl, info, method, loc); + // A member whose value or key has no lowering refuses the reflection + // at run time; its own diagnostic must not escape this walk (the + // walk runs from emitReachable, after statement fences are placed). + let before = lowerer.diags.length; + let value: IrExpr | null; + try { + value = classMethodValue(lowerer, info.decl, info, method, loc); + } catch (error) { + if (!(error instanceof PoisonError)) throw error; + lowerer.diags.splice(before); + return refuse( + "prototype reflection with a member that has no lowering is not supported yet", + ); + } if (!value || !lowerer.dynConvertible(value.type)) return refuse( "prototype reflection with a member that cannot cross a checked value boundary is not supported yet", ); - const key: IrExpr = ts.isComputedPropertyName(member.name) - ? lowerer.lowerExpr(member.name.expression) - : { kind: "strLit", value: member.name.text, type: STRING, loc }; + let key: IrExpr; + before = lowerer.diags.length; + try { + key = ts.isComputedPropertyName(member.name) + ? lowerer.lowerExpr(member.name.expression) + : { kind: "strLit", value: member.name.text, type: STRING, loc }; + } catch (error) { + if (!(error instanceof PoisonError)) throw error; + lowerer.diags.splice(before); + return refuse( + "prototype reflection with a computed member key that has no lowering is not supported yet", + ); + } initialization.then.push( define( key, @@ -427,6 +451,19 @@ export function lowerClassPrototypeData( exactClassOfReceiver(lowerer, expr.expression) ?? (stored?.kind === "classval" ? lowerer.classes.get(stored.className) : undefined); if (!info) return null; + // EventEmitter.prototype and the stream prototypes: the untyped + // constructor's prototype object (builtin-class-dyn.ts). + const builtinKind = builtinClassKind(info.def.name); + if (builtinKind !== null) { + const loc = locOf(expr); + return { + kind: "dynKeyGet", + value: builtinClassValue(builtinKind, loc), + key: { kind: "strLit", value: "prototype", type: STRING, loc }, + type: DYN, + loc, + }; + } const parent = expr.parent; const descriptorKey = ts.isCallExpression(parent) && @@ -556,6 +593,15 @@ function prototypeProtectedKeys( const entries: [ts.Symbol, string][] = owner.symbolMethods ? [...owner.symbolMethods] : []; const entry = entries.find(([, field]) => field === name); const declaration = entry ? entry[0].valueDeclaration : undefined; + // A CommonJS key-table property holds its key in the table's global. + const tableKey = + declaration && ts.isPropertyAssignment(declaration) + ? lowerer.globalsBySymbol.get(entry![0]) + : undefined; + if (tableKey) { + keys.push({ kind: "varRef", localId: tableKey.id, type: tableKey.type, loc }); + continue; + } if ( !declaration || !ts.isVariableDeclaration(declaration) || diff --git a/packages/compiler/src/frontend/lowering/class-static-data.ts b/packages/compiler/src/frontend/lowering/class-static-data.ts index af5425727..dd6927d18 100644 --- a/packages/compiler/src/frontend/lowering/class-static-data.ts +++ b/packages/compiler/src/frontend/lowering/class-static-data.ts @@ -38,7 +38,7 @@ export function classStaticDataFor( ): IrExpr | null { if ( !info.decl || - !isJsSourceFile(info.decl.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) || name.startsWith("#") || constructorMembers.has(name) ) diff --git a/packages/compiler/src/frontend/lowering/code-generation.ts b/packages/compiler/src/frontend/lowering/code-generation.ts new file mode 100644 index 000000000..c3a5c0ea7 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/code-generation.ts @@ -0,0 +1,146 @@ +/* Code generation from strings: `eval(...)`, `new Function(...)` and + * `Function(...)`. A compiled binary embeds no JavaScript engine, so scriptc + * answers exactly as `node --disallow-code-generation-from-strings` does: + * every call throws the catchable EvalError "Code generation from strings + * disallowed for this context". Packages that probe for code generation + * (zod 4's `allowsEval`, template engines, CSP-aware JITs) then take their + * interpreted paths. The arguments still evaluate first, in order, and the + * Function constructor converts each to a string before it refuses, as + * V8's CreateDynamicFunction does; eval refuses without converting. */ +import * as ts from "../ts7/adapter.js"; +import type { Lowerer } from "./lowerer.js"; +import { isJsSourceFile, locOf } from "../program.js"; +import { DYN, F64, type IrExpr, type IrStmt, type IrType, STRING } from "../../ir/ir.js"; +import { nodeThrowExpr } from "../../ir/build.js"; +import { ambientUndefReadType, contextualUndefReadType } from "./lower-namespaces.js"; +import { stdlibGlobalNameOf } from "./surfaces.js"; + +export const CODE_GENERATION_DISALLOWED = + "Code generation from strings disallowed for this context"; + +/** The code-generation entry point a callee names: the stdlib `eval` or + * `Function` global (directly, through `globalThis.`, or through a stable + * alias), or the indirect-eval spelling `(0, eval)`. Null otherwise. */ +export function codeGenerationCalleeOf( + lowerer: Lowerer, + callee: ts.Expression, +): "eval" | "Function" | null { + let e = callee; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.CommaToken) { + return codeGenerationCalleeOf(lowerer, e.right) === "eval" ? "eval" : null; + } + // Syntactic pre-check first: every call and construction asks, and only + // these spellings can name the globals (aliases are stdlib-resolved + // symbols, checked below only when the spelling could match). + const text = ts.isIdentifier(e) ? e.text : ts.isPropertyAccessExpression(e) ? e.name.text : null; + if (text === null) return null; + if (text !== "eval" && text !== "Function" && !hasCodeGenerationAlias(lowerer)) return null; + const name = stdlibGlobalNameOf(lowerer, e); + return name === "eval" || name === "Function" ? name : null; +} + +/** Some stable binding aliases `eval` or `Function` (`const F = Function`, + * zod 4's spelling). */ +function hasCodeGenerationAlias(lowerer: Lowerer): boolean { + for (const name of lowerer.stdlibGlobalAliases.values()) { + if (name === "eval" || name === "Function") return true; + } + return false; +} + +/** The comma operands an indirect eval evaluates before the call: `a` in + * `(a, eval)(...)`, outermost first. */ +function indirectEvalPrefix(callee: ts.Expression): ts.Expression[] { + let e = callee; + while (ts.isParenthesizedExpression(e)) e = e.expression; + if (!ts.isBinaryExpression(e) || e.operatorToken.kind !== ts.SyntaxKind.CommaToken) return []; + return [...indirectEvalPrefix(e.left), e.left, ...indirectEvalPrefix(e.right)].filter( + (operand) => { + let o = operand; + while (ts.isParenthesizedExpression(o)) o = o.expression; + return !(ts.isBinaryExpression(o) && o.operatorToken.kind === ts.SyntaxKind.CommaToken); + }, + ); +} + +/** Lowers a code-generation call or construction to Node's refusal under + * --disallow-code-generation-from-strings: the callee's own operands, then + * the arguments in order (converted to strings for the Function + * constructor), then the EvalError. The result carries the use site's type + * (the throw always fires, so the value is never observed). */ +export function lowerCodeGenerationCall( + lowerer: Lowerer, + expr: ts.CallExpression | ts.NewExpression, + kind: "eval" | "Function", +): IrExpr { + const loc = locOf(expr); + const stmts: IrStmt[] = []; + for (const operand of indirectEvalPrefix(expr.expression)) { + stmts.push({ kind: "exprStmt", expr: lowerer.lowerExpr(operand), loc: locOf(operand) }); + } + for (const arg of expr.arguments?.nodes ?? []) { + if (ts.isSpreadElement(arg)) { + lowerer.noLowering( + `${kind === "eval" ? "eval" : "the Function constructor"} with spread arguments`, + arg, + "code generation from strings throws EvalError in compiled programs, as under node --disallow-code-generation-from-strings; pass the arguments directly", + ); + } + const value = lowerer.lowerExpr(arg); + const argLoc = locOf(arg); + if (kind === "Function" && !isPrimitiveIrType(value.type)) { + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "dyn.toStringCoerce", + args: [lowerer.coerceToExpected(value, DYN)], + type: STRING, + loc: argLoc, + }, + loc: argLoc, + }); + } else { + stmts.push({ kind: "exprStmt", expr: value, loc: argLoc }); + } + } + const type = useSiteType(lowerer, expr); + const refusal = nodeThrowExpr(6, "", CODE_GENERATION_DISALLOWED, type, loc); + return stmts.length === 0 ? refusal : { kind: "seqExpr", stmts, result: refusal, type, loc }; +} + +function isPrimitiveIrType(t: IrType): boolean { + return ( + t.kind === "string" || + t.kind === "f64" || + t.kind === "bool" || + t.kind === "void" || + t.kind === "bigint" + ); +} + +function useSiteType(lowerer: Lowerer, expr: ts.Expression): IrType { + // The Function interface has no mapping (mapping it would fence): the + // constructed value is checked-dynamic in JavaScript, and a typed dummy + // elsewhere — the throw always fires first. + const type = lowerer.typeOf(expr); + const symbol = type.getSymbol(); + if ( + symbol !== undefined && + ts.symbolName(symbol) === "Function" && + lowerer.isStdlibSymbol(symbol) + ) { + return isJsSourceFile(ts.getSourceFileOfNode(expr)) ? DYN : F64; + } + const mapped = lowerer.mapTypeOf(type); + if ( + mapped && + mapped.kind !== "void" && + mapped.kind !== "func" && + !lowerer.typeNamesUnregisteredClass(mapped) + ) { + return mapped; + } + return ambientUndefReadType(lowerer, expr) ?? contextualUndefReadType(lowerer, expr) ?? F64; +} diff --git a/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts b/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts index b99c24a15..3d8817ab1 100644 --- a/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts +++ b/packages/compiler/src/frontend/lowering/coercions/function-adapters.ts @@ -4,6 +4,7 @@ import { InternalCompilerError } from "../../../errors.js"; import type { IrExpr, IrParam, IrStmt, IrType, SrcLoc } from "../../../ir/ir.js"; import { canAdaptDynFuncTo, + canDynCheckTo, DYN, F64, isUnitType, @@ -111,12 +112,15 @@ export function coercibleValue(lowerer: Lowerer, src: IrType, dst: IrType): bool } /** Whether all parameters and the result adapt without a trap-only path. - * Rest signatures are excluded; extra destination parameters are ignored. */ + * Rest signatures are excluded, except a typed-rest source in a slot with + * fixed parameters (the rest packing adapter); extra destination + * parameters are ignored. */ export function cleanFuncAdaptable( lowerer: Lowerer, src: IrType & { kind: "func" }, dst: IrType & { kind: "func" }, ): boolean { + if (restPackingAdapterFits(lowerer, src, dst)) return true; if (src.rest === true || dst.rest === true) return false; if (src.params.length > dst.params.length) return false; for (let i = 0; i < src.params.length; i++) { @@ -127,15 +131,145 @@ export function cleanFuncAdaptable( return src.ret.kind === "void" && dst.ret.kind === "dyn"; } +/** Whether a typed-rest function (`(...m: T[]) => R`) can serve a slot + * with fixed parameters (`(m: T) => void`): the slot's parameters convert + * to the function's fixed parameters, then to the rest element type. */ +export function restPackingAdapterFits( + lowerer: Lowerer, + fromT: IrType & { kind: "func" }, + toT: IrType & { kind: "func" }, +): boolean { + if (fromT.rest !== true || fromT.restAbi !== "typed" || toT.rest === true) return false; + const fixed = fromT.params.length - 1; + const restT = fromT.params[fixed]; + if (restT?.kind !== "array" || toT.params.length < fixed) return false; + for (let i = 0; i < toT.params.length; i++) { + if (!lowerer.coercibleValue(toT.params[i]!, i < fixed ? fromT.params[i]! : restT.elem)) + return false; + } + return toT.ret.kind === "void" || lowerer.coercibleValue(fromT.ret, toT.ret); +} + +/** The adapter for restPackingAdapterFits: forwards the fixed arguments and + * packs the remaining ones into the rest array, as a JavaScript call of the + * function with those arguments does. */ +function restPackingAdapter( + lowerer: Lowerer, + fromT: IrType & { kind: "func" }, + toT: IrType & { kind: "func" }, + loc: SrcLoc, +): string { + const key = `fnrestadapt:${typeKey(fromT)}:${typeKey(toT)}`; + const existing = lowerer.coercions.retags.get(key); + if (existing) return existing; + const name = `%fn.adapt.${lowerer.coercions.retags.size}`; + lowerer.coercions.retags.set(key, name); + lowerer.freshClosureAdapters.add(name); + const fixed = fromT.params.length - 1; + const restT = fromT.params[fixed] as IrType & { kind: "array" }; + const params: IrParam[] = toT.params.map((t, i) => ({ + localId: `a.${i}`, + name: `a${i}`, + type: t, + })); + const arg = (i: number, want: IrType): IrExpr => + lowerer.coerceToExpected( + { kind: "varRef", localId: `a.${i}`, type: toT.params[i]!, loc }, + want, + ); + const args: IrExpr[] = []; + for (let i = 0; i < fixed; i++) args.push(arg(i, fromT.params[i]!)); + const packed: IrExpr[] = []; + for (let i = fixed; i < toT.params.length; i++) packed.push(arg(i, restT.elem)); + args.push({ kind: "arrayLit", elems: packed, type: restT, loc }); + const call: IrExpr = { + kind: "callValue", + callee: { kind: "varRef", localId: "f.0", type: fromT, loc }, + receiver: { kind: "libCall", fn: "dyn.this", args: [], type: DYN, loc }, + args, + type: fromT.ret, + loc, + }; + const body: IrStmt[] = + toT.ret.kind === "void" + ? [ + { kind: "exprStmt", expr: call, loc }, + { kind: "return", value: null, loc }, + ] + : [{ kind: "return", value: lowerer.coerceToExpected(call, toT.ret), loc }]; + lowerer.liftedFns.push(...buildFunctionAdapter(name, fromT, toT, params, [], body, loc)); + return name; +} + +/** Adapt a fixed-parameter function to a checked-dynamic rest signature + * without fixed parameters (`() => {}` into mock.fn()'s `(...args: any[]) + * => undefined`): the adapter receives the hidden argument array and + * passes its element i as parameter i, undefined when the caller passed + * fewer arguments, as JS does. Null unless every parameter is + * checked-dynamic, which holds a missing argument as undefined, and the + * result is discarded or converts. */ +function funcIntoDynRestAdapter( + lowerer: Lowerer, + fromT: IrType & { kind: "func" }, + toT: IrType & { kind: "func" }, + loc: SrcLoc, +): string | null { + if (fromT.rest === true || fromT.params.some((p) => p.kind !== "dyn")) return null; + if (toT.ret.kind !== "void" && !lowerer.coercibleValue(fromT.ret, toT.ret)) return null; + const key = `fnadapt:${typeKey(fromT)}:${typeKey(toT)}`; + const existing = lowerer.coercions.retags.get(key); + if (existing) return existing; + const name = `%fn.adapt.${lowerer.coercions.retags.size}`; + lowerer.coercions.retags.set(key, name); + lowerer.freshClosureAdapters.add(name); + const params: IrParam[] = [{ localId: "rest.0", name: "args", type: DYN }]; + const call: IrExpr = { + kind: "callValue", + callee: { kind: "varRef", localId: "f.0", type: fromT, loc }, + receiver: { kind: "libCall", fn: "dyn.this", args: [], type: DYN, loc }, + args: fromT.params.map((_, i): IrExpr => ({ + kind: "dynKeyGet", + value: { kind: "varRef", localId: "rest.0", type: DYN, loc }, + key: { kind: "strLit", value: String(i), type: STRING, loc }, + type: DYN, + loc, + })), + type: fromT.ret, + loc, + }; + let body: IrStmt[]; + if (toT.ret.kind === "void") { + body = [ + { kind: "exprStmt", expr: call, loc }, + { kind: "return", value: null, loc }, + ]; + } else { + const result = lowerer.coerceToExpected(call, toT.ret); + if (!typeEquals(result.type, toT.ret)) + throw new InternalCompilerError("lowerer bug: probed fn-adapter return stopped coercing"); + body = [{ kind: "return", value: result, loc }]; + } + lowerer.liftedFns.push(...buildFunctionAdapter(name, fromT, toT, params, [], body, loc)); + return name; +} + /** Adapt a function value to a destination signature, forwarding and * converting its arguments and result. Unsupported parameter/result pairs - * produce checked traps when called; rest signatures remain refused. */ + * produce checked traps when called. Rest signatures are refused, except a + * typed-rest source in a slot with fixed parameters, which the rest + * packing adapter serves, and a fixed-parameter function into a + * checked-dynamic rest signature without fixed parameters + * (funcIntoDynRestAdapter). */ export function funcCoerceAdapter( lowerer: Lowerer, fromT: IrType & { kind: "func" }, toT: IrType & { kind: "func" }, loc: SrcLoc, ): string | null { + if (restPackingAdapterFits(lowerer, fromT, toT)) + return restPackingAdapter(lowerer, fromT, toT, loc); + if (toT.rest === true && toT.restAbi === undefined && toT.params.length === 0) + return funcIntoDynRestAdapter(lowerer, fromT, toT, loc); if (fromT.rest === true || toT.rest === true) return null; if (fromT.params.length > toT.params.length) return null; // Piece dispositions beyond coercibleValue, all CHECKER-APPROVED @@ -208,6 +342,19 @@ export function funcCoerceAdapter( voidRet = toT.ret.kind === "dyn" ? "dyn" : "strand"; } } + // An untyped result (a generic wrapper's `any` handed back `as F`) takes + // the checked-dynamic extraction to the slot's result: a record checks + // its shape, a Promise keeps its identity and checks its payload when it + // settles. + const checkedRet = + strandRet && + fromT.ret.kind === "dyn" && + canDynCheckTo( + toT.ret, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ); + if (checkedRet) strandRet = false; const key = `fnadapt:${typeKey(fromT)}:${typeKey(toT)}`; const existing = lowerer.coercions.retags.get(key); if (existing) return existing; @@ -307,6 +454,10 @@ export function funcCoerceAdapter( `a '${lowerer.fmt(fromT)}' function invoked through a '${lowerer.fmt(toT)}' slot (the result cannot convert to '${lowerer.fmt(toT.ret)}' — the checker's loose function compatibility admitted the assignment, but the call has no exact lowering)`, ), ]; + } else if (checkedRet) { + body = [ + { kind: "return", value: { kind: "dynCheck", value: call, type: toT.ret, loc }, loc }, + ]; } else { const result = lowerer.coerceToExpected(call, toT.ret); if (!typeEquals(result.type, toT.ret)) diff --git a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts index 2bc991c14..53b0748b4 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural-plans.ts @@ -23,7 +23,19 @@ export function widthLiftPlan(lowerer: Lowerer, src: IrType, dst: IrType): Width } if (dst.kind === "union") { if (src.kind === "union") { - return lowerer.unionRetagMappable(src.unionId, dst.unionId) ? { how: "retag" } : null; + if (lowerer.unionRetagMappable(src.unionId, dst.unionId)) return { how: "retag" }; + // A field the checker types `undefined` alone (an object literal's + // omitted member, normalized to `?: undefined` across a union of + // literal types) carries the unit-only union. Into a slot with an + // undefined arm it lifts as that arm; its null arm cannot occur. + const srcArms = lowerer.unions.get(src.unionId)?.arms ?? []; + if ( + srcArms.length > 0 && + srcArms.every(isUnitType) && + lowerer.armTag(dst.unionId, UNDEFINED_T) >= 0 + ) + return { how: "unitRetag" }; + return null; } // A unit-typed source can't wrap here (unionWrap requires the // LITERAL unit — and no lowered shape carries a bare unit field). diff --git a/packages/compiler/src/frontend/lowering/coercions/structural.ts b/packages/compiler/src/frontend/lowering/coercions/structural.ts index c85b9868d..496a29607 100644 --- a/packages/compiler/src/frontend/lowering/coercions/structural.ts +++ b/packages/compiler/src/frontend/lowering/coercions/structural.ts @@ -125,6 +125,19 @@ export function applyWidthLift( throw new InternalCompilerError("lowerer bug: planned retag lift failed to intern"); return { kind: "call", callee: retag, args: [value], type: dst, loc }; } + case "unitRetag": { + if (dst.kind !== "union" || value.type.kind !== "union") + throw new InternalCompilerError("lowerer bug: unit retag lift shape"); + const fromId = value.type.unionId; + const trappable = new Set(); + (lowerer.unions.get(fromId)?.arms ?? []).forEach((arm, tag) => { + if (lowerer.armTag(dst.unionId, arm) < 0) trappable.add(tag); + }); + const retag = lowerer.unionRetagHelper(fromId, dst.unionId, loc, trappable); + if (!retag) + throw new InternalCompilerError("lowerer bug: planned unit retag lift failed to intern"); + return { kind: "call", callee: retag, args: [value], type: dst, loc }; + } case "liftWrap": { if (dst.kind !== "union") throw new InternalCompilerError("lowerer bug: liftWrap lift against a non-union"); diff --git a/packages/compiler/src/frontend/lowering/collection-ingestion.ts b/packages/compiler/src/frontend/lowering/collection-ingestion.ts index 5449b0943..5d14d5492 100644 --- a/packages/compiler/src/frontend/lowering/collection-ingestion.ts +++ b/packages/compiler/src/frontend/lowering/collection-ingestion.ts @@ -91,7 +91,7 @@ export function collectionInput( const symbol = type.getSymbol(); if ( !symbol || - (symbol.name !== "MapIterator" && symbol.name !== "SetIterator") || + (ts.symbolName(symbol) !== "MapIterator" && ts.symbolName(symbol) !== "SetIterator") || !lowerer.isStdlibSymbol(symbol) ) return null; diff --git a/packages/compiler/src/frontend/lowering/collection-narrowing.ts b/packages/compiler/src/frontend/lowering/collection-narrowing.ts index 29b358e99..6c1ca80f4 100644 --- a/packages/compiler/src/frontend/lowering/collection-narrowing.ts +++ b/packages/compiler/src/frontend/lowering/collection-narrowing.ts @@ -11,9 +11,12 @@ export function concreteCollectionNarrow( ): ts.Type | null { if (!ts.isIdentifier(node) && !ts.isPropertyAccessExpression(node)) return null; const symbol = narrowed.getSymbol(); - const name = symbol?.name; + const name = ts.symbolNameOf(symbol); if (name !== "Map" && name !== "Set") return null; - if (!symbol || !symbol.declarations.some((decl) => lowerer.isStdlibFile(decl.getSourceFile()))) + if ( + !symbol || + !symbol.declarations.some((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) + ) return null; const args = lowerer.checker.getTypeArguments(narrowed as ts.TypeReference); if (args.length === 0 || !args.every((arg) => (arg.flags & ts.TypeFlags.Any) !== 0)) return null; diff --git a/packages/compiler/src/frontend/lowering/construction-escapes.test.ts b/packages/compiler/src/frontend/lowering/construction-escapes.test.ts index 77f72dc15..6807946d0 100644 --- a/packages/compiler/src/frontend/lowering/construction-escapes.test.ts +++ b/packages/compiler/src/frontend/lowering/construction-escapes.test.ts @@ -16,7 +16,7 @@ function analyze(source: string, options: { derived?: boolean; baseEscapes?: boo const ctor = cls.members.nodes.find(ts.isConstructorDeclaration) ?? null; const fields = cls.members.nodes .filter(ts.isPropertyDeclaration) - .map((member) => ({ name: member.name.getText(), initializer: member.initializer })); + .map((member) => ({ name: ts.getTextOfNode(member.name), initializer: member.initializer })); const fieldNames = new Set(fields.map((field) => field.name)); return constructionEscape({ ctor, @@ -30,7 +30,7 @@ function analyze(source: string, options: { derived?: boolean; baseEscapes?: boo const name = call.expression.name.text; const method = cls.members.nodes.find( (member): member is ts.MethodDeclaration => - ts.isMethodDeclaration(member) && member.name.getText() === name, + ts.isMethodDeclaration(member) && ts.getTextOfNode(member.name) === name, ); const isPrivate = method !== undefined && diff --git a/packages/compiler/src/frontend/lowering/construction-escapes.ts b/packages/compiler/src/frontend/lowering/construction-escapes.ts index b9c8d7e07..3fdae4ab4 100644 --- a/packages/compiler/src/frontend/lowering/construction-escapes.ts +++ b/packages/compiler/src/frontend/lowering/construction-escapes.ts @@ -23,7 +23,7 @@ export function mentionsReceiver(node: ts.Node): boolean { found = true; return; } - child.forEachChild(visit); + ts.forEachChild(child, visit); }; visit(node); return found; @@ -68,9 +68,9 @@ function containsReturn(node: ts.Node): boolean { found = true; return; } - child.forEachChild(visit); + ts.forEachChild(child, visit); }; - node.forEachChild(visit); + ts.forEachChild(node, visit); return found; } @@ -100,7 +100,7 @@ export function constructionEscape(inputs: ConstructionInputs): ConstructionEsca return !(ts.isIdentifier(node.name) && assigned.has(name) && inputs.isField(name)); } let found = false; - node.forEachChild((child) => { + ts.forEachChild(node, (child) => { if (!found && mentionsReceiver(child)) found = exposes(child, depth); }); return found; diff --git a/packages/compiler/src/frontend/lowering/constructor-returns.ts b/packages/compiler/src/frontend/lowering/constructor-returns.ts index bee2e7567..0e600e700 100644 --- a/packages/compiler/src/frontend/lowering/constructor-returns.ts +++ b/packages/compiler/src/frontend/lowering/constructor-returns.ts @@ -86,9 +86,9 @@ function ownValueReturns(ctor: ts.ConstructorDeclaration): ts.ReturnStatement[] const visit = (node: ts.Node): void => { if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; if (ts.isReturnStatement(node) && node.expression !== undefined) out.push(node); - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - ctor.body?.forEachChild(visit); + if (ctor.body !== undefined) ts.forEachChild(ctor.body, visit); return out; } @@ -188,9 +188,9 @@ function ownReturns(body: ts.Node): ts.ReturnStatement[] { const visit = (node: ts.Node): void => { if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; if (ts.isReturnStatement(node)) out.push(node); - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return out; } @@ -225,7 +225,7 @@ export function constructorReturnPlan( // the class too (`if (C.instance) return C.instance;`). const runtimeInstance = info.localClass === undefined && - isJsSourceFile(ctor.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(ctor)) && [...returns].some(([stmt, kind]) => kind === "mixed" && !insideFinallyTry(stmt, ctor)); if ([...returns.values()].includes("instance") || runtimeInstance) { slot = { @@ -277,7 +277,7 @@ export function lowerConstructorReturn(lowerer: Lowerer, stmt: ts.ReturnStatemen }; } const feature = foreignObjectFeature(info); - if (kind === "object" || !isJsSourceFile(stmt.getSourceFile())) + if (kind === "object" || !isJsSourceFile(ts.getSourceFileOfNode(stmt))) lowerer.unsupported("SC1090", stmt, feature); // A JavaScript value that is an object only sometimes: a primitive // still answers the constructed instance, an object refuses. @@ -296,7 +296,7 @@ export function lowerConstructorReturn(lowerer: Lowerer, stmt: ts.ReturnStatemen }); const diag = unsupportedDiag("SC1090", loc, feature); lowerer.runtimeFences.push(diag); - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const line = ts.getLineAndCharacterOfPosition(sf, loc.start).line + 1; return { kind: "block", @@ -460,7 +460,7 @@ export function superIntoResultSlot( const slot = plan.slot; const display = base.def.name.replace(/^%|^%m\d+\./, ""); const feature = `extending a class whose constructor can return an existing instance ('super()' would rebind 'this' to the instance '${display}' returns)`; - if (!isJsSourceFile(blame.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(blame))) { lowerer.unsupported("SC1090", blame, feature); return call; } @@ -470,7 +470,8 @@ export function superIntoResultSlot( if (!lowerer.liftedFns.some((fn) => fn.name === name)) { const diag = unsupportedDiag("SC1090", loc, feature); lowerer.runtimeFences.push(diag); - const line = ts.getLineAndCharacterOfPosition(blame.getSourceFile(), loc.start).line + 1; + const line = + ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(blame), loc.start).line + 1; const instance = { id: "instance.0", name: "instance", type, mutable: false }; const returned = { id: "returned.0", name: "returned", type: slot.type, mutable: false }; lowerer.liftedFns.push({ diff --git a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts index 2e93e2fd5..5406c3ec1 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-callbacks.ts @@ -44,6 +44,7 @@ import { lowerArrayCallback, requireProducedArrayElement, callbackArrayElement, + truthyFilterCallback, } from "./callback-arguments.js"; import { reverseArrayLoop, @@ -85,7 +86,9 @@ export function lowerArrayHofCall( const valueT = callbackValueType(lowerer, elem, present); if (booleanFilter) { if (valueT.kind === "union") lowerer.requireTruthyUnion(valueT.unionId, argNode); - if (valueT.kind === "dyn" || valueT.kind === "void" || isUnitType(valueT)) { + // Checked-dynamic elements (`anyText.split("\n").map(...).filter(Boolean)`) + // keep their runtime kind, so their truthiness is the runtime's. + if (valueT.kind === "void" || isUnitType(valueT)) { lowerer.badType(argNode, lowerer.typeOf(argNode)); } } @@ -231,33 +234,6 @@ function filterResultElem( return outElem; } -/** A native closure for the ambient Boolean callback. Reifying the - * JavaScript constructor itself is unnecessary; Array#filter only observes - * its one-argument ToBoolean result. */ -function truthyFilterCallback( - lowerer: Lowerer, - valueT: IrType, - loc: SrcLoc, -): IrExpr & { type: IrType & { kind: "func" } } { - const fnT: IrType & { kind: "func" } = { kind: "func", params: [valueT], ret: BOOL }; - const key = `filterBoolean:${typeKey(valueT)}`; - let name = lowerer.arrHofHelpers.get(key); - if (!name) { - name = `%arr.filterBoolean.${lowerer.arrHofHelpers.size}`; - lowerer.arrHofHelpers.set(key, name); - const value = varRef("v.0", valueT, loc); - lowerer.liftedFns.push({ - name, - params: [{ localId: "v.0", name: "v", type: valueT }], - returnType: BOOL, - locals: [{ id: "v.0", name: "v", type: valueT, mutable: false }], - body: [{ kind: "return", value: { kind: "toBool", operand: value, type: BOOL, loc }, loc }], - loc, - }); - } - return { kind: "closure", fnName: name, captures: [], type: fnT, loc }; -} - function filterPredicateOk(lowerer: Lowerer, ret: IrType): boolean { if (ret.kind === "bool") return true; // VOID is a TYPE erasure, not a runtime value: TS lets a value-returning @@ -328,7 +304,7 @@ function lowerFastCallback( fastLoweringDepth > 0 || slow.arity <= elemIndex || !ts.isArrowFunction(argNode) || - isJsSourceFile(argNode.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(argNode)) || valueT?.kind !== "union" || typeEquals(valueT, elem) || elem.kind === "union" || @@ -1513,7 +1489,7 @@ export function lowerArrayFlatMapCall( kind: "dynInvoke", recv: { kind: "dynFrom", value: receiver, liveRef: true, type: DYN, loc }, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [{ kind: "dynFrom", value: fnArg, type: DYN, loc }], type: DYN, loc, @@ -2109,7 +2085,7 @@ export function lowerArraySortCall( // Arithmetic on inferred JS values retains a checked result because // it can produce BigInt. The sort callback still requires a number. const result = - fnArg.type.ret.kind === "dyn" && isJsSourceFile(argNode.getSourceFile()) + fnArg.type.ret.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(argNode)) ? F64 : fnArg.type.ret; const expected = funcOf( @@ -2247,7 +2223,7 @@ export function lowerDynArrayFilterCall( recv, fnArg, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: arrayOf(elemT), loc, @@ -2288,7 +2264,7 @@ export function lowerDynArrayFlatMapCall( kind: "dynInvoke", recv, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [boxed], type: DYN, loc, @@ -2310,7 +2286,7 @@ export function lowerDynArrayFlatMapCall( recv, fnArg, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: arrayOf(ret.elem), loc, diff --git a/packages/compiler/src/frontend/lowering/containers/array-construction.ts b/packages/compiler/src/frontend/lowering/containers/array-construction.ts index 6072b58b7..53d46d00d 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-construction.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-construction.ts @@ -44,6 +44,7 @@ import { lowerArrayCallback, callbackArrayElement, } from "./callback-arguments.js"; +import { classGeneratorIterable } from "../class-iterables.js"; /** Build the argument array for a mutating/copying operation while keeping * the receiver's scalar payload ABI. A missing read is represented as the @@ -265,6 +266,22 @@ function appendWidenedArrayValues( } /** The Array constructor's element and count forms, shared by calls and new. */ +/** The value of `.fill(v)` when `expr` is that call's receiver. */ +function fillArgumentOfReceiver(expr: ts.Expression): ts.Expression | null { + let receiver: ts.Node = expr; + let parent = expr.parent; + while (ts.isParenthesizedExpression(parent)) { + receiver = parent; + parent = parent.parent; + } + if (!ts.isPropertyAccessExpression(parent) || parent.expression !== receiver) return null; + if (parent.name.text !== "fill" || parent.questionDotToken) return null; + const call = parent.parent; + if (!ts.isCallExpression(call) || call.expression !== parent) return null; + const value = call.arguments.nodes[0]; + return value !== undefined && !ts.isSpreadElement(value) ? value : null; +} + export function lowerArrayConstructor( lowerer: Lowerer, expr: ts.CallExpression | ts.NewExpression, @@ -279,7 +296,7 @@ export function lowerArrayConstructor( // reference. Known element forms retain native arrays, including their // existing sparse storage and indexed property contracts. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (result?.kind !== "array" || result.elem.kind === "dyn") ) { return { @@ -295,7 +312,16 @@ export function lowerArrayConstructor( const contextual = lowerer.checker.getContextualType(expr); if (contextual) result = lowerer.mapTypeOf(contextual); } - if (isJsSourceFile(expr.getSourceFile()) && (result === null || result.kind === "dyn")) + // `Array(n).fill(v)`: the checker types the length-constructed array as + // any[], but fill gives every element v's type. + if (result?.kind !== "array") { + const fillValue = fillArgumentOfReceiver(expr); + const elem = fillValue + ? lowerer.mapTypeOf(lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(fillValue))) + : null; + if (elem !== null && isSupportedArrayElem(elem)) result = arrayOf(elem); + } + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (result === null || result.kind === "dyn")) result = arrayOf(DYN); if (result?.kind !== "array" || !isSupportedArrayElem(result.elem)) { lowerer.badType(expr, lowerer.typeOf(expr)); @@ -797,7 +823,7 @@ export function lowerArrayFromCall( const loc = locOf(call); const args = call.arguments.nodes; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && args.length >= 1 && args.length <= 3 && !args.some(ts.isSpreadElement) @@ -913,7 +939,7 @@ export function lowerArrayFromCall( // and materializes holes while keeping element reference identity. if ((args.length === 1 || args.length === 2) && !ts.isObjectLiteralExpression(args[0]!)) { const source = lowerer.lowerExpr(args[0]!); - const src = + let src = source.type.kind === "string" && args.length === 2 ? strCharsCall(lowerer, source, loc) : source; @@ -1008,6 +1034,9 @@ export function lowerArrayFromCall( if (src.type.kind === "searchParams" && args.length === 1) { return lowerCheckedArrayFrom(lowerer, lowerer.coerceInto(args[0]!, src, DYN), loc); } + // A class whose [Symbol.iterator] is a generator method iterates the + // generator that method returns. + src = classGeneratorIterable(lowerer, src) ?? src; // A generator (or an iterator adapted into one) drains in order. if (src.type.kind === "generator" && args.length === 1) { const arrT = lowerer.mapTypeOf(lowerer.typeOf(call)); diff --git a/packages/compiler/src/frontend/lowering/containers/array-methods.ts b/packages/compiler/src/frontend/lowering/containers/array-methods.ts index 7ef96457c..12078720e 100644 --- a/packages/compiler/src/frontend/lowering/containers/array-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/array-methods.ts @@ -353,7 +353,7 @@ export function lowerArrayMethodCall( // machinery answered string[]): same rule, any-typed spelling. ((lowerer.typeOf(access.expression).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0 && - isJsSourceFile(access.getSourceFile()))) + isJsSourceFile(ts.getSourceFileOfNode(access)))) ) { const probe = lowerer.lowerExpr(access.expression); if (probe.type.kind === "array") { @@ -1039,7 +1039,7 @@ export function lowerArrayMethodCall( kind: "dynInvoke", recv: receiver, method: "join", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -2096,7 +2096,7 @@ export function lowerTupleReadMethodCall( kind: "dynInvoke", recv: { kind: "dynFrom", value: receiver, type: DYN, loc }, method: "flatMap", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [{ kind: "dynFrom", value: fnArg, type: DYN, loc }], type: DYN, loc, diff --git a/packages/compiler/src/frontend/lowering/containers/bytes.ts b/packages/compiler/src/frontend/lowering/containers/bytes.ts index c5a41cf32..f8e49a516 100644 --- a/packages/compiler/src/frontend/lowering/containers/bytes.ts +++ b/packages/compiler/src/frontend/lowering/containers/bytes.ts @@ -120,7 +120,7 @@ function lowerBytesSortCall( fnArg.type.params.every((param) => lowerer.coercibleValue(F64, param)) ) { const result = - fnArg.type.ret.kind === "dyn" && isJsSourceFile(argNode.getSourceFile()) + fnArg.type.ret.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(argNode)) ? F64 : fnArg.type.ret; const expected = funcOf( @@ -187,7 +187,7 @@ export function lowerBytesNew( ): IrExpr | null { if ( symbol != null && - (symbol.name === "ArrayBuffer" || symbol.name === "SharedArrayBuffer") && + (ts.symbolName(symbol) === "ArrayBuffer" || ts.symbolName(symbol) === "SharedArrayBuffer") && lowerer.isStdlibSymbol(symbol) ) { const args = expr.arguments?.nodes ?? []; @@ -203,18 +203,19 @@ export function lowerBytesNew( : dynUndefinedExpr(locOf(expr)); return { kind: "libCall", - fn: symbol.name === "SharedArrayBuffer" ? "sharedArrayBuffer.new" : "arrayBuffer.new", + fn: + ts.symbolName(symbol) === "SharedArrayBuffer" ? "sharedArrayBuffer.new" : "arrayBuffer.new", args: [length], type: DYN, loc: locOf(expr), }; } - if (symbol && symbol.name === "DataView" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "DataView" && lowerer.isStdlibSymbol(symbol)) { return lowerDataViewNew(lowerer, expr); } - const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined; + const elem = symbol ? own(BYTES_CTORS, ts.symbolName(symbol)) : undefined; if (!elem || !symbol || !lowerer.isStdlibSymbol(symbol)) return null; - const name = symbol.name; + const name = ts.symbolName(symbol); const type = bytesOf(elem); const loc = locOf(expr); const args = expr.arguments?.nodes ?? []; @@ -291,7 +292,7 @@ export function lowerBytesStaticCall( } if (access.name.text !== "from" || !ts.isIdentifier(access.expression)) return null; const symbol = lowerer.resolveValueSymbol(access.expression); - const elem = symbol ? own(BYTES_CTORS, symbol.name) : undefined; + const elem = symbol ? own(BYTES_CTORS, ts.symbolName(symbol)) : undefined; if (!elem || !lowerer.isStdlibSymbol(symbol ?? undefined)) return null; if ( call.arguments.nodes.length < 1 || @@ -442,7 +443,7 @@ export function lowerBytesMethodCall( const nArgs = call.arguments.nodes.length; // Encoded stream callbacks can receive strings even when JavaScript's // contextual inference still describes their parameter as a Buffer. - if (name === "toString" && nArgs <= 1 && isJsSourceFile(call.getSourceFile())) { + if (name === "toString" && nArgs <= 1 && isJsSourceFile(ts.getSourceFileOfNode(call))) { const lowered = lowerer.lowerExpr(access.expression); const receiver = lowered.kind === "dynCheck" && lowered.value.type.kind === "dyn" ? lowered.value : lowered; @@ -455,7 +456,7 @@ export function lowerBytesMethodCall( call.arguments.nodes[0] ? lowerer.lowerExprExpecting(call.arguments.nodes[0], DYN) : dynUndefinedExpr(loc), - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: STRING, loc, @@ -1334,7 +1335,8 @@ function lowerBufferStaticValue( args.length <= 3 && !args.some(ts.isSpreadElement) && ((symbol) => - symbol != null && (symbol.name === "ArrayBuffer" || symbol.name === "SharedArrayBuffer"))( + symbol != null && + (ts.symbolName(symbol) === "ArrayBuffer" || ts.symbolName(symbol) === "SharedArrayBuffer"))( lowerer.typeOf(args[0]!).getSymbol(), ) ) { @@ -1650,7 +1652,9 @@ function lowerBufferStaticValue( // flavor. Plain bytes are Uint8Arrays at this boundary; inspecting // only the bytes tag would incorrectly identify them as Buffers. const checked = - isJsSourceFile(call.getSourceFile()) && v.kind === "dynCheck" && v.value.type.kind === "dyn" + isJsSourceFile(ts.getSourceFileOfNode(call)) && + v.kind === "dynCheck" && + v.value.type.kind === "dyn" ? v.value : v; if (checked.type.kind === "dyn") { diff --git a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts index e87a5e1bc..18754f088 100644 --- a/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts +++ b/packages/compiler/src/frontend/lowering/containers/callback-arguments.ts @@ -1,12 +1,18 @@ import * as ts from "../../ts7/adapter.js"; import type { Lowerer } from "../lowerer.js"; +import { isJsSourceFile, locOf } from "../../program.js"; +import { varRef } from "../../../ir/build.js"; +import { typeKey } from "../../type-mapper.js"; import { + BOOL, F64, type IrExpr, type IrType, + type SrcLoc, UNDEFINED_T, funcOf, isSupportedArrayElem, + isUnitType, typeEquals, } from "../../../ir/ir.js"; @@ -44,6 +50,24 @@ export function lowerArrayCallback( bindUntyped = false, expectedReturn?: IrType, ): { fnArg: IrExpr & { type: IrType & { kind: "func" } }; arity: number } { + // The ambient `Boolean` as the callback (`flags.every(Boolean)`, + // `xs.some(Boolean)`, `xs.map(Boolean)`): Boolean(value) answers the + // ToBoolean of its FIRST argument and ignores the rest, so the callback + // is the interned one-parameter truthiness closure over the lead slot. + // The constructor's own generic signature has no compiled value form. + // JavaScript files keep their primitive-constructor closure. + if ( + ts.isIdentifier(argNode) && + argNode.text === "Boolean" && + !isJsSourceFile(ts.getSourceFileOfNode(argNode)) && + lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode) ?? undefined) + ) { + const valueT = lead[0]; + if (valueT === undefined || valueT.kind === "void" || isUnitType(valueT)) + lowerer.badType(argNode, lowerer.typeOf(argNode)); + if (valueT.kind === "union") lowerer.requireTruthyUnion(valueT.unionId, argNode); + return { fnArg: truthyFilterCallback(lowerer, valueT, locOf(argNode)), arity: 1 }; + } const full = [...lead, F64, arrT]; // A DYN-receiver HOF's callback (`parsed.flatMap((value) => ...)`): // the contextual signature types the unannotated param `any` (the @@ -248,3 +272,39 @@ function runtimeOptionalPairVariant(lowerer: Lowerer, expected: IrType, mapped: : t; return lowerer.isRuntimeOptionalRecordVariant(strip(expected), strip(mapped)); } + +/** A native closure for the ambient Boolean callback. Reifying the + * JavaScript constructor itself is unnecessary; Array#filter only observes + * its one-argument ToBoolean result. */ +export function truthyFilterCallback( + lowerer: Lowerer, + valueT: IrType, + loc: SrcLoc, +): IrExpr & { type: IrType & { kind: "func" } } { + const fnT: IrType & { kind: "func" } = { kind: "func", params: [valueT], ret: BOOL }; + const key = `filterBoolean:${typeKey(valueT)}`; + let name = lowerer.arrHofHelpers.get(key); + if (!name) { + name = `%arr.filterBoolean.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(key, name); + const value = varRef("v.0", valueT, loc); + lowerer.liftedFns.push({ + name, + params: [{ localId: "v.0", name: "v", type: valueT }], + returnType: BOOL, + locals: [{ id: "v.0", name: "v", type: valueT, mutable: false }], + body: [ + { + kind: "return", + value: + valueT.kind === "dyn" + ? { kind: "dynTest", test: "truthy", value, type: BOOL, loc } + : { kind: "toBool", operand: value, type: BOOL, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "closure", fnName: name, captures: [], type: fnT, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts b/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts index 7c4ddf220..0bf126830 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-grouping.ts @@ -67,7 +67,7 @@ export function lowerGroupByStaticCall( const fNode = call.arguments.nodes[1]!; // An unannotated JavaScript callback parameter takes the element. const f = - isJsSourceFile(fNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(fNode)) && (ts.isArrowFunction(fNode) || ts.isFunctionExpression(fNode)) ? lowerArrayCallback(lowerer, fNode, [elem], groupT, true).fnArg : lowerer.lowerExpr(fNode); diff --git a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts index a8b782b51..6aa6d6faf 100644 --- a/packages/compiler/src/frontend/lowering/containers/collection-methods.ts +++ b/packages/compiler/src/frontend/lowering/containers/collection-methods.ts @@ -42,6 +42,8 @@ import { import { strCharsCall } from "./array-construction.js"; import { arrayElementRead } from "./array-iteration.js"; import { isSearchParamsIterable } from "../builtins/url.js"; +import { classGeneratorIterable } from "../class-iterables.js"; +import { generatorDrain } from "../iterator-adapters.js"; /** Ambient Map method calls. `get`/`set`/`has`/`delete`/`clear` lower to * mapIntrinsic; `forEach` desugars to a direct call of a synthetic loop @@ -58,7 +60,7 @@ export function lowerMapMethodCall( if (!MAP_METHODS.has(name)) return null; let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); let probedUntyped = false; - if (receiverIr?.kind !== "map" && isJsSourceFile(access.getSourceFile())) { + if (receiverIr?.kind !== "map" && isJsSourceFile(ts.getSourceFileOfNode(access))) { const probed = tryLowerExpression(lowerer, access.expression); if (probed?.type.kind === "map") { receiverIr = probed.type; @@ -425,7 +427,7 @@ export function lowerCollectionSpread( loc, }, then: entries, - else_: nodeThrowExpr(1, "", `${node.getText()} is not iterable`, entries.type, loc), + else_: nodeThrowExpr(1, "", `${ts.getTextOfNode(node)} is not iterable`, entries.type, loc), type: entries.type, loc, }, @@ -542,7 +544,7 @@ export function lowerSetMethodCall( // Set, but the VALUE lowered as a real Set of // identity tokens (the new-Set probe) — the lowered receiver's type // is the honest dispatch key. - if (receiverIr?.kind !== "set" && isJsSourceFile(access.getSourceFile())) { + if (receiverIr?.kind !== "set" && isJsSourceFile(ts.getSourceFileOfNode(access))) { const probed = tryLowerExpression(lowerer, access.expression); if (probed?.type.kind === "set") { receiverIr = probed.type; @@ -971,7 +973,7 @@ export function lowerSetSeedNew( { kind: "libCall", fn: "dyn.iterPack", - args: [source, strLit(node.getText(), loc)], + args: [source, strLit(ts.getTextOfNode(node), loc)], type: DYN, loc, }, @@ -1048,6 +1050,14 @@ function setFromSeedValue( } : null; } + // A generator, or a class whose [Symbol.iterator] is a generator method: + // the Set takes every value it yields, in order. + const generator = + classGeneratorIterable(lowerer, source) ?? (source.type.kind === "generator" ? source : null); + if (generator) { + const drained = generatorDrain(lowerer, generator, setT.elem, loc); + return drained ? { kind: "setNew", seed: drained, type: setT, loc } : null; + } let seed: IrExpr; if (source.type.kind === "set" && typeEquals(source.type, setT)) { return { kind: "setIntrinsic", method: "clone", receiver: source, args: [], type: setT, loc }; @@ -1155,7 +1165,7 @@ export function lowerMapSeedNew( } if (ts.isArrayLiteralExpression(argNode)) { if ( - isJsSourceFile(argNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(argNode)) && argNode.elements.nodes.some((entry) => !ts.isArrayLiteralExpression(entry)) ) { return mapFromSeedValue(lowerer, lowerer.lowerExprExpecting(argNode, DYN), mapT); @@ -1246,6 +1256,24 @@ export function mapFromSeedValue( } : null; } + // A generator of entries, or a class whose [Symbol.iterator] is one: the + // Map takes every [key, value] pair it yields, in order. + const generator = + classGeneratorIterable(lowerer, seed) ?? (seed.type.kind === "generator" ? seed : null); + if (generator) { + const entry: IrType = { + kind: "record", + shapeId: lowerer.shapes.intern( + [ + { name: "0", type: mapT.key }, + { name: "1", type: mapT.value }, + ], + true, + ), + }; + const drained = generatorDrain(lowerer, generator, entry, loc); + return drained ? mapFromSeedValue(lowerer, drained, mapT) : null; + } if (seed.type.kind === "map") { if (!typeEquals(seed.type, mapT)) return null; return { kind: "mapIntrinsic", method: "clone", receiver: seed, args: [], type: mapT, loc }; diff --git a/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts b/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts index 523fda46e..32f90bfe7 100644 --- a/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts +++ b/packages/compiler/src/frontend/lowering/containers/dynamic-receivers.ts @@ -142,7 +142,7 @@ export function dynStringReceiver( args: [ recv, { kind: "strLit", value: access.name.text, type: STRING, loc }, - { kind: "strLit", value: access.getText(), type: STRING, loc }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc }, ], type: STRING, loc, diff --git a/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts b/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts index f2967c298..397bf6ec8 100644 --- a/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts +++ b/packages/compiler/src/frontend/lowering/containers/exec-result-uses.ts @@ -29,7 +29,7 @@ export function execResultCaptureMask(lowerer: Lowerer, call: ts.CallExpression) const symbol = lowerer.resolveValueSymbol(decl.name); if (!symbol) return null; const read = new Set(); - for (const use of identifiersNamed(lowerer, decl.getSourceFile(), decl.name.text)) { + for (const use of identifiersNamed(lowerer, ts.getSourceFileOfNode(decl), decl.name.text)) { if (use === decl.name || lowerer.resolveValueSymbol(use) !== symbol) continue; const ref = outerValue(use); const parent = ref.parent; diff --git a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts index 450e64142..efc173ffc 100644 --- a/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts +++ b/packages/compiler/src/frontend/lowering/containers/indexed-objects.ts @@ -28,6 +28,8 @@ import { tryLowerExpression } from "../expressions/try-lower-expression.js"; import { locOf } from "../../program.js"; import { typeKey } from "../../type-mapper.js"; import { type WidthLift } from "../lowerer.js"; +import { classGeneratorIterable, iteratesThroughGenerator } from "../class-iterables.js"; +import { generatorDrain } from "../iterator-adapters.js"; /** Object() and new Object() preserve known object inputs. Primitive * boxing and unproven checked-dynamic values retain their refusal. */ @@ -148,12 +150,29 @@ export function objectIterOverIndexShape( } // The overflow value must surface as the element type (identity, an // arm of a union element, or a dyn element over a dyn signature). + // A cast that narrows the signature's union (`Object.entries(headers as + // Record)` over a signature that + // also holds numbers): each overflow value checks into the element type, + // so a value of a dropped arm throws instead of mistyping. + let ivNarrows = false; if (valueT) { const ivSurfaces = typeEquals(iv, valueT) || (valueT.kind === "union" && lowerer.armTag(valueT.unionId, iv) >= 0) || (valueT.kind === "dyn" && iv.kind === "dyn"); - if (!ivSurfaces) { + if ( + !ivSurfaces && + iv.kind === "union" && + lowerer.dynConvertible(iv) && + lowerer.dynConvertible(valueT) + ) { + const ivUnion = iv; + const narrowArms = + valueT.kind === "union" ? (lowerer.unions.get(valueT.unionId)?.arms ?? []) : [valueT]; + ivNarrows = + narrowArms.length > 0 && narrowArms.every((a) => lowerer.armTag(ivUnion.unionId, a) >= 0); + } + if (!ivSurfaces && !ivNarrows) { lowerer.unsupported( "SC1090", node, @@ -267,6 +286,21 @@ export function objectIterOverIndexShape( } } } + // A cast that narrows the field's type (the element type keeps + // some of its arms): the value checks into the element type, so a + // dropped arm throws instead of mistyping. + if (lowerer.dynConvertible(f.type) && lowerer.dynConvertible(valueT)) { + const armsOf = (t: IrType): IrType[] => + t.kind === "union" ? (lowerer.unions.get(t.unionId)?.arms ?? []) : [t]; + const target = armsOf(valueT); + if (armsOf(f.type).some((a) => target.some((b) => typeEquals(a, b)))) + return { + kind: "dynCheck", + value: { kind: "dynFrom", value: raw, type: DYN, loc }, + type: valueT, + loc, + }; + } return null; }; const pushedOf = (element: IrExpr): IrExpr => @@ -337,17 +371,37 @@ export function objectIterOverIndexShape( const ksT = arrayOf(STRING); const ksRef = varRef("ks.0", ksT, loc); const kRef = varRef("k.0", STRING, loc); - const readValue: IrExpr | null = valueT - ? { - kind: "recordKeyGet", - obj: rRef, - shapeId: argIr.shapeId, - key: kRef, - overflowOnly: true, - type: valueT, - loc, - } - : null; + const readValue: IrExpr | null = !valueT + ? null + : ivNarrows + ? { + kind: "dynCheck", + value: { + kind: "dynFrom", + value: { + kind: "recordKeyGet", + obj: rRef, + shapeId: argIr.shapeId, + key: kRef, + overflowOnly: true, + type: iv, + loc, + }, + type: DYN, + loc, + }, + type: valueT, + loc, + } + : { + kind: "recordKeyGet", + obj: rRef, + shapeId: argIr.shapeId, + key: kRef, + overflowOnly: true, + type: valueT, + loc, + }; const loopPushed: IrExpr = member === "keys" ? kRef @@ -472,9 +526,37 @@ function lowerTypedObjectFromEntriesCall( if (call.arguments.nodes.length !== 1 || ts.isSpreadElement(call.arguments.nodes[0]!)) return null; const argNode = call.arguments.nodes[0]!; - const argIr = lowerer.mapTypeOf(lowerer.typeOf(argNode)); + let argIr = lowerer.mapTypeOf(lowerer.typeOf(argNode)); + // A generator of entries, or a class whose [Symbol.iterator] is one, + // drains into the entry array the object is built from. + let drained: IrExpr | null = null; + if (argIr?.kind === "generator" || (argIr && iteratesThroughGenerator(lowerer, argIr))) { + const value = lowerer.lowerExpr(argNode); + const generator = + classGeneratorIterable(lowerer, value) ?? (value.type.kind === "generator" ? value : null); + if (generator?.type.kind !== "generator") return null; + // The entries take the result's value type: `[string, V]` for an + // object `{ [k: string]: V }`. + const result = lowerer.mapTypeOf(lowerer.typeOf(call)); + const valueT = + result?.kind === "record" ? lowerer.shapes.get(result.shapeId)?.indexValue : undefined; + if (!valueT) return null; + const entry: IrType = { + kind: "record", + shapeId: lowerer.shapes.intern( + [ + { name: "0", type: STRING }, + { name: "1", type: valueT }, + ], + true, + ), + }; + drained = generatorDrain(lowerer, generator, entry, locOf(call)); + if (!drained) return null; + argIr = drained.type; + } if (argIr?.kind !== "array") return null; - const represented = tryLowerExpression(lowerer, argNode); + const represented = drained ?? tryLowerExpression(lowerer, argNode); if (represented && !typeEquals(represented.type, argIr)) return null; // `Object.fromEntries(rows)` over a `string[][]` VALUE — the env-line // idiom (`envArray.map((env) => env.split('='))`). The checker has no @@ -522,7 +604,7 @@ function lowerTypedObjectFromEntriesCall( // Enumeration/filtering may keep the array in checked-dynamic storage. // Validate its tuple layout before calling a helper with a native-array // ABI; opaque tuple values themselves retain their original references. - const receiver = lowerer.lowerExprExpecting(argNode, argIr); + const receiver = drained ?? lowerer.lowerExprExpecting(argNode, argIr); const key = `obj.fromEntries:${argIr.elem.shapeId}:${resultT.shapeId}`; let helper = lowerer.arrHofHelpers.get(key); if (!helper) { diff --git a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts index 46074573d..c23e6931c 100644 --- a/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts +++ b/packages/compiler/src/frontend/lowering/containers/iterator-helpers.ts @@ -34,6 +34,7 @@ import { genValue, stepDrivable, } from "../lower-generators.js"; +import { classGeneratorIterable, iteratesThroughGenerator } from "../class-iterables.js"; import { exitBeforeReturns } from "./for-of.js"; import { iteratorSlotAdapter, @@ -317,9 +318,11 @@ function isHelperObject(lowerer: Lowerer, expr: ts.Expression): boolean { * it rather than a wrapper. A generator (including a helper object or an * array iterator) is itself; an array, string or typed array gives its * values iterator; a Set its values and a Map its entries, as a generator - * over the live collection. The result converts into the call's - * IteratorObject type. Null for other arguments (objects with their own - * next method, untyped values), which keep the Iterator fence. */ + * over the live collection; a class instance whose `[Symbol.iterator]` is + * a generator method gives the generator it returns. The result converts + * into the call's IteratorObject type. Null for other arguments (objects + * with their own next method, untyped values), which keep the Iterator + * fence. */ export function lowerIteratorFromCall( lowerer: Lowerer, call: ts.CallExpression, @@ -332,7 +335,9 @@ export function lowerIteratorFromCall( const argT = lowerer.mapTypeOf(lowerer.typeOf(argNode)); if (resultT?.kind !== "generator" || resultT.async || argT === null) return null; const iterableKinds = ["generator", "array", "string", "bytes", "set", "map"]; - if (!iterableKinds.includes(argT.kind) || (argT.kind === "generator" && argT.async)) return null; + const classIterable = iteratesThroughGenerator(lowerer, argT); + if (!classIterable && !iterableKinds.includes(argT.kind)) return null; + if (argT.kind === "generator" && argT.async) return null; const loc = locOf(call); const value = lowerer.lowerExpr(argNode); const own = (yieldT: IrType): GenType => ({ @@ -344,6 +349,7 @@ export function lowerIteratorFromCall( let iterator: IrExpr | null = null; const t = value.type; if (t.kind === "generator") iterator = value; + else if (classIterable) iterator = classGeneratorIterable(lowerer, value); else if (t.kind === "array") iterator = lowerArrayIteratorValue(lowerer, value, "values", own(t.elem), loc); else if (t.kind === "string") @@ -678,7 +684,7 @@ function isHelperReceiver(lowerer: Lowerer, expr: ts.Expression): boolean { return ( mapped?.kind === "dyn" && symbol !== undefined && - ADAPTED_ITERATOR_TYPES.has(symbol.name) && + ADAPTED_ITERATOR_TYPES.has(ts.symbolName(symbol)) && lowerer.isStdlibSymbol(symbol) ); } diff --git a/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts b/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts index f936e27cb..794bd27c2 100644 --- a/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts +++ b/packages/compiler/src/frontend/lowering/containers/match-row-uses.ts @@ -242,7 +242,7 @@ function constBindingReadsByElements( } function usesOf(lowerer: Lowerer, name: ts.Identifier, symbol: ts.Symbol): ts.Identifier[] { - return identifiersNamed(lowerer, name.getSourceFile(), name.text).filter( + return identifiersNamed(lowerer, ts.getSourceFileOfNode(name), name.text).filter( (use) => use !== name && lowerer.resolveValueSymbol(use) === symbol, ); } diff --git a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts index 39154a820..50693e5e4 100644 --- a/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts +++ b/packages/compiler/src/frontend/lowering/containers/string-and-regexp.ts @@ -302,6 +302,53 @@ function lowerMethodReceiver( return lowerer.coerceInto(node, lowered, expected); } +/** A `string | undefined | null` receiver of a string method: evaluated + * once, each unit arm throws the TypeError Node's member read throws + * ("Cannot read properties of undefined (reading 'replace')") before the + * arguments evaluate, and the string arm is the receiver. */ +function nullableStringReceiver(lowerer: Lowerer, node: ts.Expression, member: string): IrExpr { + const lowered = lowerer.lowerExpr(node); + if (lowered.type.kind !== "union") return lowerer.coerceInto(node, lowered, STRING); + const loc = locOf(node); + const unionId = lowered.type.unionId; + const local = lowerer.declareHiddenLocal("%nullableReceiver", lowered.type); + const ref: IrExpr = { kind: "varRef", localId: local.id, type: lowered.type, loc }; + const arms = lowerer.unions.get(unionId)?.arms ?? []; + let result: IrExpr = { + kind: "unionNarrow", + unionId, + tag: arms.findIndex((arm) => arm.kind === "string"), + value: ref, + type: STRING, + loc, + }; + for (const [tag, arm] of arms.entries()) { + if (!isUnitType(arm)) continue; + const unit = arm.kind === "nullT" ? "null" : "undefined"; + result = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, value: ref, negated: false, type: BOOL, loc }, + then: nodeThrowExpr( + 1, + "", + `Cannot read properties of ${unit} (reading '${member}')`, + STRING, + loc, + ), + else_: result, + type: STRING, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: lowered, loc }], + result, + type: STRING, + loc, + }; +} + /** Relative string indexing and code-point decoding over the UTF-16 intrinsics. */ export function lowerStringIndexCall( lowerer: Lowerer, @@ -733,6 +780,15 @@ export function lowerStringPaddingCall( return { kind: "call", callee: helper, args: values, type: STRING, loc }; } +/** String methods whose regex-argument forms lower in lowerRegexMethodCall. */ +const REGEX_ARGUMENT_STRING_METHODS = new Set([ + "replace", + "replaceAll", + "split", + "search", + "matchAll", +]); + export function lowerRegexMethodCall( lowerer: Lowerer, call: ts.CallExpression, @@ -747,11 +803,30 @@ export function lowerRegexMethodCall( // by construction — the symbol gate doesn't apply to `any` receivers. if (dynReceiver === undefined && !lowerer.isStdlibMember(access)) return null; const name = access.name.text; - const receiverKind = dynReceiver - ? "string" - : lowerer.mapTypeOf(lowerer.typeOf(access.expression))?.kind; + const mappedReceiver = dynReceiver ? null : lowerer.mapTypeOf(lowerer.typeOf(access.expression)); + // A nullable string receiver of a REGEX-argument call + // (`part.replace(re, "")` where an unchecked index read typed `part` + // `string | undefined`) takes the string path, as + // lowerStringMethodCall's gate does for the string-argument forms: + // lowerMethodReceiver throws Node's "Cannot read properties of + // undefined" on the unit arms. (`match` keeps its own gate below.) + const regexArgument = call.arguments.nodes[0]; + const nullableString = + REGEX_ARGUMENT_STRING_METHODS.has(name) && + regexArgument !== undefined && + !ts.isSpreadElement(regexArgument) && + lowerer.mapTypeOf(lowerer.typeOf(regexArgument))?.kind === "regex" && + mappedReceiver?.kind === "union" && + (lowerer.unions.get(mappedReceiver.unionId)?.arms.some((arm) => arm.kind === "string") ?? + false) && + (lowerer.unions + .get(mappedReceiver.unionId) + ?.arms.every((arm) => arm.kind === "string" || isUnitType(arm)) ?? + false); + const receiverKind = dynReceiver || nullableString ? "string" : mappedReceiver?.kind; const lowerReceiver = (): IrExpr => { if (dynReceiver) return dynReceiver(); + if (nullableString) return nullableStringReceiver(lowerer, access.expression, name); if (receiverKind === "string") return lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text); if (receiverKind === "regex") diff --git a/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts b/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts index e3ca2270d..b80eb7ac0 100644 --- a/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts +++ b/packages/compiler/src/frontend/lowering/deferred-module-initializers.ts @@ -62,8 +62,9 @@ export class DeferredModuleInitializers { const info = exactClassOfReceiver(lowerer, owner); if ( info && - info.decl?.getSourceFile() === statement.getSourceFile() && - info.decl.getStart() < statement.getStart() && + info.decl !== null && + ts.getSourceFileOfNode(info.decl) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(info.decl) < ts.tokenStartOfNode(statement) && !info.localClass && !info.classDecorators && !info.generic && @@ -101,7 +102,7 @@ export class DeferredModuleInitializers { loc: locOf(statement), }; const context = lowerer.ctx; - const moduleIndex = [...lowerer.initNameOf.keys()].indexOf(statement.getSourceFile()); + const moduleIndex = [...lowerer.initNameOf.keys()].indexOf(ts.getSourceFileOfNode(statement)); const initializers = this.pending.get(key) ?? []; initializers.push({ loc: block.loc, @@ -269,14 +270,14 @@ function registryInitializer( return false; expression = expression.arguments.nodes[0]!; } - const packageSource = npmStaticPackageOfPath(statement.getSourceFile().fileName) !== null; + const packageSource = npmStaticPackageOfPath(ts.getSourceFileOfNode(statement).fileName) !== null; const initialized = (declaration: ts.Node): boolean => { - const source = declaration.getSourceFile(); - return source === statement.getSourceFile() - ? declaration.getStart() < statement.getStart() + const source = ts.getSourceFileOfNode(declaration); + return source === ts.getSourceFileOfNode(statement) + ? ts.tokenStartOfNode(declaration) < ts.tokenStartOfNode(statement) : [...lowerer.initNameOf.keys()].indexOf(source) >= 0 && [...lowerer.initNameOf.keys()].indexOf(source) < - [...lowerer.initNameOf.keys()].indexOf(statement.getSourceFile()); + [...lowerer.initNameOf.keys()].indexOf(ts.getSourceFileOfNode(statement)); }; const callable = (callee: ts.Expression): boolean => { if (!ts.isIdentifier(callee)) return false; @@ -324,7 +325,7 @@ function registryInitializer( // bundlers. Arguments still need to be safe to omit: an annotation // never licenses dropping argument effects or a throwing property read. if (packageSource && (ts.isCallExpression(node) || ts.isNewExpression(node))) { - const trivia = node.getSourceFile().text.slice(node.pos, node.getStart()); + const trivia = ts.getSourceFileOfNode(node).text.slice(node.pos, ts.tokenStartOfNode(node)); if (!/[@#]__PURE__/.test(trivia)) return false; if (!callable(node.expression)) return false; return (node.arguments?.nodes ?? []).every((argument) => value(argument, depth + 1)); @@ -337,8 +338,8 @@ function registryInitializer( const info = exactClassOfReceiver(lowerer, node.expression.expression); return ( !!info?.decl && - info.decl.getSourceFile() === statement.getSourceFile() && - info.decl.getStart() < statement.getStart() && + ts.getSourceFileOfNode(info.decl) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(info.decl) < ts.tokenStartOfNode(statement) && classWriteIsUnobserved(lowerer, info, statement) && (findMethodOn(lowerer, info, node.name.text) !== null || findGenericMethodOn(lowerer, info, node.name.text) !== null) @@ -398,7 +399,7 @@ function registryInitializer( if ( functionDeclaration && ts.isFunctionDeclaration(functionDeclaration) && - functionDeclaration.getSourceFile() === statement.getSourceFile() + ts.getSourceFileOfNode(functionDeclaration) === ts.getSourceFileOfNode(statement) ) return true; // Reading an earlier initialized binding executes none of its @@ -410,8 +411,8 @@ function registryInitializer( functionDeclaration && ts.isVariableDeclaration(functionDeclaration) && functionDeclaration.initializer && - functionDeclaration.getSourceFile() === statement.getSourceFile() && - functionDeclaration.getStart() < statement.getStart() + ts.getSourceFileOfNode(functionDeclaration) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(functionDeclaration) < ts.tokenStartOfNode(statement) ) return true; const info = symbol && lowerer.classBySymbol.get(symbol); @@ -421,8 +422,8 @@ function registryInitializer( // decorated classes and local factories keep ordinary initialization. return ( !!declaration && - declaration.getSourceFile() === statement.getSourceFile() && - declaration.getStart() < statement.getStart() && + ts.getSourceFileOfNode(declaration) === ts.getSourceFileOfNode(statement) && + ts.tokenStartOfNode(declaration) < ts.tokenStartOfNode(statement) && ts.isClassDeclaration(declaration) && !info!.classDecorators && !info!.generic && @@ -476,19 +477,20 @@ function registryOwnerIsData( ) ) return false; - if (declaration.getSourceFile() !== statement.getSourceFile()) { + if (ts.getSourceFileOfNode(declaration) !== ts.getSourceFileOfNode(statement)) { const modules = [...lowerer.initNameOf.keys()]; return ( !!frozen && - modules.indexOf(declaration.getSourceFile()) >= 0 && - modules.indexOf(declaration.getSourceFile()) < modules.indexOf(statement.getSourceFile()) + modules.indexOf(ts.getSourceFileOfNode(declaration)) >= 0 && + modules.indexOf(ts.getSourceFileOfNode(declaration)) < + modules.indexOf(ts.getSourceFileOfNode(statement)) ); } - if (declaration.getStart() >= statement.getStart()) return false; + if (ts.tokenStartOfNode(declaration) >= ts.tokenStartOfNode(statement)) return false; if (frozen) return true; let safe = true; const visit = (node: ts.Node): void => { - if (!safe || node.getStart() >= statement.getStart()) return; + if (!safe || ts.tokenStartOfNode(node) >= ts.tokenStartOfNode(statement)) return; if ( ts.isIdentifier(node) && node !== declaration.name && @@ -515,7 +517,7 @@ function registryOwnerIsData( } ts.forEachChild(node, visit); }; - visit(statement.getSourceFile()); + visit(ts.getSourceFileOfNode(statement)); return safe; } @@ -527,7 +529,7 @@ function classWriteIsUnobserved( info: ClassInfo, statement: ts.Statement, ): boolean { - const source = statement.getSourceFile(); + const source = ts.getSourceFileOfNode(statement); const declaration = info.decl!; if ( declaration.members.nodes.some( @@ -543,7 +545,10 @@ function classWriteIsUnobserved( ) return false; for (const earlier of source.statements.nodes) { - if (earlier.getStart() <= declaration.getStart() || earlier.getStart() >= statement.getStart()) + if ( + ts.tokenStartOfNode(earlier) <= ts.tokenStartOfNode(declaration) || + ts.tokenStartOfNode(earlier) >= ts.tokenStartOfNode(statement) + ) continue; if ( ts.isFunctionDeclaration(earlier) || diff --git a/packages/compiler/src/frontend/lowering/divergences.ts b/packages/compiler/src/frontend/lowering/divergences.ts index 36fbf48b9..1c362cb09 100644 --- a/packages/compiler/src/frontend/lowering/divergences.ts +++ b/packages/compiler/src/frontend/lowering/divergences.ts @@ -14,7 +14,6 @@ import * as ts from "../ts7/adapter.js"; import { locOf } from "../program.js"; import { isNodeModulesPath } from "../resolve.js"; import { - dateLiteralDivergenceDiag, keyOrderDivergenceDiag, localeCompareDivergenceDiag, unrelatedClassCastDivergenceDiag, @@ -57,7 +56,6 @@ export function scanDivergences( const literals: ts.ObjectLiteralExpression[] = []; const observers: ts.Expression[] = []; const casts: (ts.AsExpression | ts.TypeAssertion)[] = []; - const dates: { call: ts.CallExpression | ts.NewExpression; arg: ts.Expression }[] = []; const localeCompares: ts.CallExpression[] = []; /** binding name → member names assigned through it (`q.a = 2`). */ const assignedMembers = new Map>(); @@ -99,20 +97,9 @@ export function scanDivergences( member === "dir") ) { for (const a of node.arguments.nodes) if (!isLiteralLike(a)) observers.push(a); - } else if (recv.text === "Date" && member === "parse") { - if (node.arguments.nodes[0] !== undefined) - dates.push({ call: node, arg: node.arguments.nodes[0] }); } } } - } else if (ts.isNewExpression(node)) { - if ( - ts.isIdentifier(node.expression) && - node.expression.text === "Date" && - node.arguments?.nodes.length === 1 - ) { - dates.push({ call: node, arg: node.arguments.nodes[0]! }); - } } else if (ts.isForInStatement(node)) { observers.push(node.expression); } else if (ts.isVariableDeclaration(node)) { @@ -294,7 +281,7 @@ export function scanDivergences( ts.isVariableDeclaration(decl) && decl.initializer !== undefined && ts.isObjectLiteralExpression(decl.initializer) && - decl.getSourceFile() === inner.getSourceFile() + ts.getSourceFileOfNode(decl) === ts.getSourceFileOfNode(inner) ) { lit = decl.initializer; binding = inner.text; @@ -304,7 +291,9 @@ export function scanDivergences( const contextual = safely(() => checker.getContextualType(lit!)); if (contextual === undefined) continue; if (namedObjectDeclaration(checker, contextual) === null) continue; - const declared = safely(() => checker.getPropertiesOfType(contextual).map((p) => p.name)); + const declared = safely(() => + checker.getPropertiesOfType(contextual).map((p) => ts.symbolName(p)), + ); if (declared === undefined) continue; const written = lit.properties.nodes.map((p) => propertyNameText(p)!); const assigned = @@ -399,55 +388,9 @@ export function scanDivergences( } } - // ── Date strings the native parser does not accept ──────────────── - for (const { call, arg } of dates) { - const callee = ts.isNewExpression(call) - ? call.expression - : ts.isPropertyAccessExpression(call.expression) - ? call.expression.expression - : undefined; - if (callee === undefined || !lowerer.isStdlibGlobal(callee, "Date")) continue; - for (const literal of dateStringLiterals(checker, arg)) { - const text = stringLiteralText(literal)!; - if (nativeDateParses(text) || !/\d/.test(text)) continue; - out.push(dateLiteralDivergenceDiag(text, locOf(literal))); - } - } - return out; } -/** The string literals a Date argument can be: the literal itself, or - * each element of a const array of literals a for-of binding iterates. */ -function dateStringLiterals(checker: ts.TypeChecker, arg: ts.Expression): ts.Expression[] { - if (stringLiteralText(arg) !== null) return [arg]; - if (!ts.isIdentifier(arg)) return []; - const symbol = safely(() => checker.getSymbolAtLocation(arg)); - const decl = symbol === undefined ? undefined : symbol.valueDeclaration; - const list = decl?.parent; - const loop = list?.parent; - if ( - decl === undefined || - !ts.isVariableDeclaration(decl) || - list === undefined || - loop === undefined || - !ts.isForOfStatement(loop) || - loop.initializer !== list - ) - return []; - let iterated: ts.Expression = loop.expression; - if (ts.isIdentifier(iterated)) { - const source = safely(() => checker.getSymbolAtLocation(iterated)); - const init = source === undefined ? undefined : source.valueDeclaration; - if (init === undefined || !ts.isVariableDeclaration(init) || init.initializer === undefined) - return []; - if (init.parent === undefined || (init.parent.flags & ts.NodeFlags.Const) === 0) return []; - iterated = init.initializer; - } - if (!ts.isArrayLiteralExpression(iterated)) return []; - return iterated.elements.nodes.filter((e) => stringLiteralText(e) !== null); -} - /** A checker query that fails here only loses a warning. */ function safely(f: () => T): T | undefined { try { @@ -522,7 +465,7 @@ function identityUse(root: ts.Node, name: string, from = -1): IdentityUse | null return cur; }; const textOf = (node: ts.Node): string => { - const text = node.getText(); + const text = ts.getTextOfNode(node); return text.length > 60 ? `${text.slice(0, 57)}...` : text; }; const visit = (node: ts.Node): "skip" | "stop" | undefined => { @@ -650,7 +593,7 @@ function elementWrite(root: ts.Node, name: string, from: number): string | null ) target = node.operand; if (target !== undefined && throughElement(target)) { - found = target.getText(); + found = ts.getTextOfNode(target); return "stop"; } if ( @@ -729,8 +672,8 @@ function widthCopyBetween( // still observes the same writes: no divergence to report. const accessor = ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor; if (sourceProps.some((p) => (p.flags & accessor) !== 0)) return null; - const sourceNames = new Set(sourceProps.map((p) => p.name)); - const targetNames = checker.getPropertiesOfType(t).map((p) => p.name); + const sourceNames = new Set(sourceProps.map((p) => ts.symbolName(p))); + const targetNames = checker.getPropertiesOfType(t).map((p) => ts.symbolName(p)); if (targetNames.length === sourceNames.size && targetNames.every((n) => sourceNames.has(n))) return null; } @@ -743,7 +686,7 @@ function isProgramClassType(checker: ts.TypeChecker, type: ts.Type): boolean { return ( symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0 && - symbol.declarations.some((d) => !d.getSourceFile().isDeclarationFile) + symbol.declarations.some((d) => !ts.getSourceFileOfNode(d).isDeclarationFile) ); } @@ -757,7 +700,7 @@ function isPlainRecordType(checker: ts.TypeChecker, type: ts.Type): boolean { if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Class) !== 0) return false; // Library-declared object types (Map, Date, Promise, ...) are not records. const decls = symbol === undefined ? [] : symbol.declarations; - if (decls.some((d) => d.getSourceFile().isDeclarationFile)) return false; + if (decls.some((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) return false; return true; } @@ -770,7 +713,7 @@ function namedObjectDeclaration(checker: ts.TypeChecker, type: ts.Type): ts.Node if (symbol === undefined) return null; if ((symbol.flags & ts.SymbolFlags.Class) !== 0) return null; const decl = symbol.declarations[0]; - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl; } @@ -824,7 +767,7 @@ function classDeclarationOfTypeNode( const symbol = lowerer.checker.getSymbolAtLocation(node.typeName); if (symbol === undefined || (symbol.flags & ts.SymbolFlags.Class) === 0) return null; const decl = symbol.declarations.find((d) => ts.isClassDeclaration(d) || ts.isClassExpression(d)); - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl as ts.ClassLikeDeclaration; } @@ -835,7 +778,7 @@ function classDeclarationOfType( const symbol = type.getSymbol(); if (symbol === undefined || (symbol.flags & ts.SymbolFlags.Class) === 0) return null; const decl = symbol.declarations.find((d) => ts.isClassDeclaration(d) || ts.isClassExpression(d)); - if (decl === undefined || decl.getSourceFile().isDeclarationFile) return null; + if (decl === undefined || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; return decl as ts.ClassLikeDeclaration; } @@ -870,20 +813,3 @@ function classExtends( function className(decl: ts.ClassLikeDeclaration): string { return decl.name?.text ?? "(anonymous class)"; } - -const MONTHS = ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]; - -/** The date-string grammar the native runtime accepts (scr_lib.c - * scr_date_parse_get_time): ECMAScript's date-time format with an explicit - * offset (date-only forms are UTC) and the certificate-time shape - * "Mon DD HH:MM:SS YYYY GMT". Everything else parses to NaN natively. */ -export function nativeDateParses(text: string): boolean { - const iso = - /^(?:[+-]\d{6}|\d{4})(?:-(\d{2})(?:-(\d{2}))?)?$/.test(text) || - /^(?:[+-]\d{6}|\d{4})-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{3})?)?(?:Z|[+-]\d{2}:\d{2})$/.test( - text, - ); - if (iso) return true; - const cert = /^([A-Za-z]{3}) {1,2}\d{1,2} \d{2}:\d{2}:\d{2} \d{4} GMT$/.exec(text); - return cert !== null && MONTHS.includes(cert[1]!.toLowerCase()); -} diff --git a/packages/compiler/src/frontend/lowering/dyn-streams.ts b/packages/compiler/src/frontend/lowering/dyn-streams.ts new file mode 100644 index 000000000..2ac09e8e5 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/dyn-streams.ts @@ -0,0 +1,326 @@ +/* Checked-dynamic stream slots (TypeMapperCtx.dynNodeStreams). + * + * Under the general stream representation (general-streams.ts), + * NodeJS.ReadableStream and NodeJS.WritableStream map to the runtime stream + * classes. JavaScript code also hands such slots its own stream objects: + * constructor-function streams over EventEmitter or the legacy Stream, plain + * objects implementing on/pipe/write/end, streams of bundled npm code. A + * program that moves an untyped value into a stream slot therefore lowers + * again with checked-dynamic slots: those two interfaces map to dyn, so a + * slot holds either a boxed native stream (its typed capsule, which unboxes + * to the same stream) or the JavaScript object itself, and a value returned + * to JavaScript is the object that came in. + * + * A member call on such a slot dispatches per value at run time. A native + * stream unboxes and takes the ordinary class-method lowering (the same + * stream surface a typed stream has); any other value calls its own member + * with JavaScript's dynamic semantics (dynInvoke). Programs without such a + * flow never take this pass, so their IR is unchanged. */ +import type { IrExpr, IrStmt, IrType } from "../../ir/ir.js"; +import { dynUndefinedExpr, numLit, strLit, varRef } from "../../ir/build.js"; +import { DYN } from "../../ir/ir.js"; +import * as ts from "../ts7/adapter.js"; +import { locOf } from "../program.js"; +import { isDeclaredInAmbientNamespace } from "../type-mapper.js"; +import { resultIsDiscarded } from "./call-position.js"; +import { lowerObjectMethodCall } from "./lower-calls.js"; +import type { Lowerer } from "./lowerer.js"; + +/** The side a NodeJS stream interface names: "r" for NodeJS.ReadableStream, + * "w" for NodeJS.WritableStream (through null/undefined and intersections), + * or null for any other type. */ +export function nodeStreamInterfaceSide(lowerer: Lowerer, type: ts.Type): "r" | "w" | null { + const parts = (t: ts.Type): ts.Type[] => + (t.flags & ts.TypeFlags.Intersection) !== 0 ? ts.constituentTypes(t).flatMap(parts) : [t]; + for (const part of parts(lowerer.checker.getNonNullableType(type))) { + const psym = part.getSymbol() ?? part.getAliasSymbol(); + if ( + (psym === undefined || ts.symbolName(psym) !== "ReadableStream") && + (psym === undefined || ts.symbolName(psym) !== "WritableStream") + ) + continue; + const declared = psym.declarations.some( + (d) => + ts.isInterfaceDeclaration(d) && + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)) && + isDeclaredInAmbientNamespace(d, "NodeJS"), + ); + if (declared) return ts.symbolName(psym) === "ReadableStream" ? "r" : "w"; + } + return null; +} + +/** The runtime class a native value of the side unboxes to. */ +export function nativeStreamClass(side: "r" | "w"): IrType { + return { kind: "object", className: side === "r" ? "%Readable" : "%Writable" }; +} + +/** The native stream of a checked-dynamic value (+1, typed as the side's + * class) or null: the stream.dynNative libCall, which tests the value's + * typed capsule without throwing. */ +function dynNativeStream( + lowerer: Lowerer, + value: IrExpr, + side: "r" | "w", + fn: "stream.dynNative" | "stream.dynNativePipe" = "stream.dynNative", +): IrExpr { + const native = nativeStreamClass(side); + return { + kind: "libCall", + fn, + args: [value, numLit(side === "r" ? 1 : 2, value.loc)], + type: { kind: "union", unionId: lowerer.unions.intern([native, { kind: "nullT" }]) }, + loc: value.loc, + }; +} + +/** Runs `lower` with `node` lowering to `value` and its member calls taking + * `type` as the receiver representation. */ +function withStreamReceiver( + lowerer: Lowerer, + node: ts.Expression, + value: IrExpr, + type: IrType, + lower: () => T, +): T { + const previous = lowerer.receiverTypeOverrides.get(node); + lowerer.receiverTypeOverrides.set(node, type); + try { + return lowerer.withExpressionOverride(node, value, lower); + } finally { + if (previous) lowerer.receiverTypeOverrides.set(node, previous); + else lowerer.receiverTypeOverrides.delete(node); + } +} + +/** `stream.member(args)` on a NodeJS.ReadableStream / NodeJS.WritableStream + * receiver under checked-dynamic stream slots, or null when the call is not + * one. */ +export function lowerDynStreamMemberCall( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (!lowerer.dynNodeStreams) return null; + // The native arm re-enters the call lowering with the receiver bound. + if (lowerer.chainRecvByNode.has(access.expression)) return null; + if (access.questionDotToken || call.questionDotToken || !ts.isIdentifier(access.name)) + return null; + if (call.arguments.nodes.some(ts.isSpreadElement)) return null; + const side = nodeStreamInterfaceSide(lowerer, lowerer.typeOf(access.expression)); + if (side === null) return null; + const loc = locOf(call); + const receiverValue = lowerer.lowerExpr(access.expression); + const receiver = lowerer.declareHiddenLocal("%streamReceiver", receiverValue.type); + const receiverRef = varRef(receiver.id, receiver.type, locOf(access.expression)); + const stmts: IrStmt[] = [{ kind: "varDecl", localId: receiver.id, init: receiverValue, loc }]; + const seq = (result: IrExpr): IrExpr => ({ + kind: "seqExpr", + stmts, + result, + type: result.type, + loc, + }); + // A typed stream whose declared type is one of the interfaces (a child + // stdio read): the class-method path on its own representation. + if (receiverValue.type.kind !== "dyn") { + const typed = + receiverValue.type.kind === "object" + ? withStreamReceiver(lowerer, access.expression, receiverRef, receiverValue.type, () => + lowerObjectMethodCall(lowerer, call, access), + ) + : lowerer.withExpressionOverride(access.expression, receiverRef, () => + lowerer.lowerCall(call), + ); + if (typed === null) lowerer.noLowering(`${access.name.text} on this stream`, call); + return seq(typed); + } + const nativeType = nativeStreamClass(side); + const nullable = dynNativeStream(lowerer, receiverRef, side); + const nativeOrNull = lowerer.declareHiddenLocal("%streamNative", nullable.type); + stmts.push({ kind: "varDecl", localId: nativeOrNull.id, init: nullable, loc }); + const unionId = nullable.type.kind === "union" ? nullable.type.unionId : ""; + const tag = lowerer.armTag(unionId, nativeType); + const native = lowerer.declareHiddenLocal("%streamNativeValue", nativeType); + const nativeRef = varRef(native.id, nativeType, locOf(access.expression)); + const nativeDecl: IrStmt = { + kind: "varDecl", + localId: native.id, + init: { + kind: "unionNarrow", + unionId, + tag, + value: varRef(nativeOrNull.id, nullable.type, loc), + type: nativeType, + loc, + }, + loc, + }; + const typed = withStreamReceiver(lowerer, access.expression, nativeRef, nativeType, () => + lowerObjectMethodCall(lowerer, call, access), + ); + if (typed === null) + lowerer.noLowering( + `${side === "r" ? "NodeJS.ReadableStream" : "NodeJS.WritableStream"}.${access.name.text}`, + call, + ); + // Any other value calls its own member, with the arguments as untyped + // values (typed callbacks box behind checked thunks). + const invoked: IrExpr = { + kind: "dynInvoke", + recv: receiverRef, + method: access.name.text, + calleeName: ts.getTextOfNode(access), + args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), + type: DYN, + loc, + }; + const cond: IrExpr = { + kind: "unionIsTag", + unionId, + tag, + negated: false, + value: varRef(nativeOrNull.id, nullable.type, loc), + type: { kind: "bool" }, + loc, + }; + const resultType = resultIsDiscarded(call) ? null : lowerer.irTypeOf(call); + if (resultType === null || resultType.kind === "void") { + stmts.push({ + kind: "if", + cond, + then: [nativeDecl, { kind: "exprStmt", expr: typed, loc }], + else_: [{ kind: "exprStmt", expr: invoked, loc }], + loc, + }); + return seq(dynUndefinedExpr(loc)); + } + const result = lowerer.declareHiddenLocal("%streamResult", resultType); + result.mutable = true; + stmts.push( + { kind: "varDecl", localId: result.id, init: null, loc }, + { + kind: "if", + cond, + then: [ + nativeDecl, + { + kind: "assign", + localId: result.id, + value: lowerer.coerceInto(call, typed, resultType), + loc, + }, + ], + else_: [ + { + kind: "assign", + localId: result.id, + value: lowerer.coerceInto(call, invoked, resultType), + loc, + }, + ], + loc, + }, + ); + return seq(varRef(result.id, resultType, loc)); +} + +/** `for await (const chunk of stream)` over a checked-dynamic + * NodeJS.ReadableStream: a native stream iterates through the Readable + * lowering; any other value throws the not-supported Error (async + * iteration of JavaScript stream objects has no lowering). */ +export function lowerForAwaitDynStream( + lowerer: Lowerer, + stmt: ts.ForOfStatement, + lowerLoop: () => IrStmt | null, +): IrStmt | null { + if (!lowerer.dynNodeStreams || lowerer.chainRecvByNode.has(stmt.expression)) return null; + if (nodeStreamInterfaceSide(lowerer, lowerer.typeOf(stmt.expression)) !== "r") return null; + const loc = locOf(stmt.expression); + const value = lowerer.lowerExpr(stmt.expression); + const native: IrType = + value.type.kind === "dyn" ? { kind: "object", className: "%Duplex" } : value.type; + const local = lowerer.declareHiddenLocal("%streamIterated", native); + const init: IrExpr = + value.type.kind === "dyn" + ? { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + value, + strLit( + "for await over stream objects other than native streams is not supported yet", + loc, + ), + ], + type: native, + loc, + } + : value; + const loop = withStreamReceiver( + lowerer, + stmt.expression, + varRef(local.id, native, loc), + native, + lowerLoop, + ); + if (loop === null) return null; + return { + kind: "block", + body: [{ kind: "varDecl", localId: local.id, init, loc }, loop], + loc: locOf(stmt), + }; +} + +/** A statement that runs `onNative` with the native stream a + * checked-dynamic pipe or unpipe destination stands for (typed as the + * side's class), or `otherwise` for any other value. The destination's + * stream is a boxed native one or a constructor-function stream's + * (stream.dynNativePipe): only the runtime's pipe reads it, while member + * calls on a slot keep a constructor-function stream's own methods. */ +export function dynNativeBranch( + lowerer: Lowerer, + value: IrExpr, + side: "r" | "w", + onNative: (native: IrExpr) => IrStmt, + otherwise: IrStmt, +): IrStmt { + const loc = value.loc; + const nativeType = nativeStreamClass(side); + const nullable = dynNativeStream(lowerer, value, side, "stream.dynNativePipe"); + const nativeOrNull = lowerer.declareHiddenLocal("%streamNative", nullable.type); + const unionId = nullable.type.kind === "union" ? nullable.type.unionId : ""; + const tag = lowerer.armTag(unionId, nativeType); + const native = lowerer.declareHiddenLocal("%streamNativeValue", nativeType); + const nullableRef = varRef(nativeOrNull.id, nullable.type, loc); + return { + kind: "block", + body: [ + { kind: "varDecl", localId: nativeOrNull.id, init: nullable, loc }, + { + kind: "if", + cond: { + kind: "unionIsTag", + unionId, + tag, + negated: false, + value: nullableRef, + type: { kind: "bool" }, + loc, + }, + then: [ + { + kind: "varDecl", + localId: native.id, + init: { kind: "unionNarrow", unionId, tag, value: nullableRef, type: nativeType, loc }, + loc, + }, + onNative(varRef(native.id, nativeType, loc)), + ], + else_: [otherwise], + loc, + }, + ], + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/error-name-order.ts b/packages/compiler/src/frontend/lowering/error-name-order.ts new file mode 100644 index 000000000..7453d123e --- /dev/null +++ b/packages/compiler/src/frontend/lowering/error-name-order.ts @@ -0,0 +1,98 @@ +import * as ts from "../ts7/adapter.js"; +import { isClassOwnEnumerableFieldName } from "../../ir/ir.js"; +import type { ClassInfo } from "./lower-classes.js"; + +type ErrorClassLink = Pick & { + base: ErrorClassLink | null; +}; + +/** The property key a member or element-access name spells statically: + * an identifier, a quoted string, a number in its canonical spelling, or a + * computed string literal. Null for other keys. */ +function staticKeyText(name: ts.Node): string | null { + if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text; + if (ts.isNumericLiteral(name)) return String(Number(name.text)); + if (ts.isComputedPropertyName(name)) return staticKeyText(name.expression); + return null; +} + +/** Whether `node` is an assignment to `this.` or `this[""]`, + * and the name. */ +function thisAssignmentName(node: ts.Node): string | null { + if (!ts.isBinaryExpression(node) || node.operatorToken.kind !== ts.SyntaxKind.EqualsToken) + return null; + const target = node.left; + if (ts.isPropertyAccessExpression(target) && target.expression.kind === ts.SyntaxKind.ThisKeyword) + return ts.isIdentifier(target.name) ? target.name.text : null; + if (ts.isElementAccessExpression(target) && target.expression.kind === ts.SyntaxKind.ThisKeyword) + return staticKeyText(target.argumentExpression); + return null; +} + +/** Where an Error subclass instance's own `name` sits among its fields when + * a class of its chain gives it one during construction. A property's + * position is fixed when it is created: a `name` field takes its place + * among the fields (parameter properties first, then the declared ones), + * and `this.name = ...` in a constructor follows the fields created so far, + * including the ones that constructor created before it. The answer is the + * field `name` follows, null when it precedes every field, and undefined + * when no class of the chain sets it during construction. `fields` are the + * instance's own enumerable fields. */ +export function errorNameAfterField( + info: ErrorClassLink, + fields: ReadonlySet, +): string | null | undefined { + const chain: ErrorClassLink[] = []; + for (let current: ErrorClassLink | null | undefined = info; current; current = current.base) + if (current.decl) chain.unshift(current); + const created = new Set(); + let last: string | null = null; + const create = (name: string): void => { + if (!fields.has(name) || created.has(name)) return; + created.add(name); + last = name; + }; + for (const link of chain) { + for (const property of link.paramProps ?? []) create(property.name); + for (const member of link.decl!.members.nodes) { + if (!ts.isPropertyDeclaration(member)) continue; + const key = staticKeyText(member.name); + if (key === null) continue; + if ( + member.modifiers?.nodes.some( + (m) => m.kind === ts.SyntaxKind.StaticKeyword || m.kind === ts.SyntaxKind.DeclareKeyword, + ) + ) + continue; + if (key === "name") return last; + create(key); + } + const body = link.ctor?.body; + if (!body) continue; + let found = false; + ts.walkPreorder(body, (node) => { + if (ts.isTypeNode(node)) return "skip"; + if (ts.isFunctionLike(node) && !ts.isArrowFunction(node)) return "skip"; + const name = thisAssignmentName(node); + if (name === "name") { + found = true; + return "stop"; + } + if (name !== null) create(name); + return undefined; + }); + if (found) return last; + } + return undefined; +} + +/** The fields an Error subclass's own-property view lists. */ +export function errorViewFields(fields: readonly { name: string }[]): Set { + return new Set( + fields + .map((field) => field.name) + .filter( + (name) => isClassOwnEnumerableFieldName(name) && name !== "name" && name !== "message", + ), + ); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts index ef2aa9778..a80367dd0 100644 --- a/packages/compiler/src/frontend/lowering/expressions/array-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/array-literals.ts @@ -30,6 +30,7 @@ import { generatorDrain } from "../iterator-adapters.js"; import { isSearchParamsIterable } from "../builtins/url.js"; import { varRef } from "../../../ir/build.js"; import { arrayValueStore } from "../array-values.js"; +import { classGeneratorIterable } from "../class-iterables.js"; /** JavaScript: whether `expr` is or holds an existing checked-dynamic * object — a binding stored as a dyn value whose checker type is an @@ -82,7 +83,7 @@ export function lowerArrayLiteral( // structurally equal checker types. Their native class ABIs still differ. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array" && mapped.elem.kind === "classval" ) @@ -201,7 +202,7 @@ export function lowerArrayLiteral( // keep the checked-dynamic fallback below. if ( lowerer.implicitParamTypes !== null && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null && (mapped === null || mapped.kind === "dyn" || (mapped.kind === "array" && mapped.elem.kind === "dyn")) && @@ -221,7 +222,7 @@ export function lowerArrayLiteral( mapped = arrayOf(first.type); } } - if (!expected && isJsSourceFile(expr.getSourceFile()) && mapped?.kind === "array") { + if (!expected && isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array") { const elem = mapped.elem; const arms = elem.kind === "union" ? lowerer.unions.get(elem.unionId)?.arms : [elem]; if (arms?.length && arms.every((arm) => arm.kind === "classval")) mapped = DYN; @@ -233,7 +234,7 @@ export function lowerArrayLiteral( } if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.elements.nodes.some( (element) => ts.isSpreadElement(element) && @@ -246,7 +247,7 @@ export function lowerArrayLiteral( // only in a checked-dynamic array: a static element type would copy it. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind !== "dyn" && expr.elements.nodes.some( (element) => !ts.isSpreadElement(element) && jsHoldsDynObject(lowerer, element), @@ -268,7 +269,7 @@ export function lowerArrayLiteral( // real keys. A structural union can otherwise drop descriptor fields. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && mapped?.kind === "array" && mapped.elem.kind === "union" && expr.elements.nodes.some(ts.isObjectLiteralExpression) @@ -315,7 +316,7 @@ export function lowerArrayLiteral( // to DYN wholesale now (mapType's dyn-element array rule), so the // literal IS the dyn array. if ( - isJsSourceFile(expr.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(expr)) || mapped?.kind === "dyn" || lowerer.checkerAnyArray(expr) ) { @@ -431,6 +432,9 @@ export function lowerArrayLiteral( loc: locOf(el), }; } + // `[...obj]` over a class whose [Symbol.iterator] is a generator + // method spreads the generator that method returns. + src = classGeneratorIterable(lowerer, src) ?? src; // `[...it]` over a generator (or an iterator adapted into one): // drain it once, in order, into a fresh element array. if (src.type.kind === "generator") { diff --git a/packages/compiler/src/frontend/lowering/expressions/binary.ts b/packages/compiler/src/frontend/lowering/expressions/binary.ts index 2a8de78b9..07d983cc0 100644 --- a/packages/compiler/src/frontend/lowering/expressions/binary.ts +++ b/packages/compiler/src/frontend/lowering/expressions/binary.ts @@ -1,5 +1,6 @@ /* Binary operators: arithmetic, comparisons and equality, logical and nullish pairs, and string conversion for concatenation. */ import * as ts from "../../ts7/adapter.js"; +import { cleanFuncAdaptable } from "../coercions/function-adapters.js"; import { type Lowerer, jsFuncNameOf } from "../lowerer.js"; import { BIGINT_T, @@ -66,6 +67,10 @@ import { lowerAbstractEquality } from "../abstract-equality.js"; import { lowerInExpression } from "./relational.js"; import { lowerDynTypeofTest, lowerErrorCodeTypeofTest } from "./typeof.js"; import { builtinClassRefOf, sameBuiltinClass } from "../builtins/class-values.js"; +import { + lowerRuntimeConstructorIs, + runtimeConstructorReceiver, +} from "../builtins/runtime-constructor.js"; import { tryLowerIndexedComparison } from "../indexed-comparison.js"; import { lowerUnionEquality, tagEqualityMayMissAlias } from "../strict-equality.js"; import { withUnitArm } from "../../type-mapper.js"; @@ -477,6 +482,22 @@ export function lowerNullishPair( ); } } + // An optional callback defaulted to a function of another signature + // (`o.tags ?? (() => ({}))`, fewer parameters or a narrower result): the + // result is the callback's own function type, and the default adapts into + // it (JavaScript ignores surplus arguments). The checker's merged union + // signature would lose the callback's result shape. + if (present?.kind === "func" && present.rest !== true) { + const raw = lowerer.lowerExpr(expr.right); + if ( + raw.type.kind === "func" && + (typeEquals(raw.type, present) || cleanFuncAdaptable(lowerer, raw.type, present)) + ) { + const right = lowerer.coerceInto(expr.right, raw, present); + lowerer.nullishFuncResults.add(expr); + return { kind: "nullish", left, right, type: present, loc }; + } + } lowerer.unsupported( "SC1090", expr, @@ -510,7 +531,7 @@ export function lowerNativeFunctionAssignment( ): IrExpr | null { const target = expr.left; if ( - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || (!ts.isPropertyAccessExpression(target) && !ts.isElementAccessExpression(target)) || target.questionDotToken ) @@ -882,7 +903,7 @@ export function lowerCompoundValueToTarget( // catchable TypeError, as the expanded assignment does). if ( (target.type.kind === "dyn" || rhs.type.kind === "dyn") && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || (lowerer.dynConvertible(read.type) && lowerer.dynConvertible(rhs.type))) ) { const left = lowerer.coerceInto(expr.left, read, DYN); @@ -1121,7 +1142,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr recv && (recv.type.kind === "dyn" || (recv.type.kind === "array" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(recv.type))) ) { return lowerDynMemberAssignment( @@ -1322,6 +1343,24 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr lowerUnionTypeofTest(lowerer, expr, loc); if (test) return test; } + // `x.constructor === Readable` for an instance of EventEmitter or a + // stream class: the instance's own class, compared without a class value. + if ( + op === ts.SyntaxKind.EqualsEqualsEqualsToken || + op === ts.SyntaxKind.ExclamationEqualsEqualsToken + ) { + for (const [side, other] of [ + [expr.left, expr.right], + [expr.right, expr.left], + ] as const) { + const ctor = runtimeConstructorReceiver(lowerer, side); + const target = ctor ? builtinClassRefOf(lowerer, other) : null; + if (ctor && (target?.kind === "emitter" || target?.kind === "stream")) { + const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken; + return lowerRuntimeConstructorIs(lowerer, ctor.receiver, target.info, negated, loc); + } + } + } // `C === EventEmitter` between two builtin class references: the // classes are compile-time values, so the answer folds. if ( @@ -1495,7 +1534,7 @@ export function lowerBinary(lowerer: Lowerer, expr: ts.BinaryExpression): IrExpr // Numeric operators dispatch on ToNumeric's actual Number/BigInt // result, including values flowing through native callable properties. if ( - isJsSourceFile(expr.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(expr)) || op === ts.SyntaxKind.PlusToken || [ ts.SyntaxKind.LessThanToken, @@ -2370,7 +2409,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE } if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && op === ts.SyntaxKind.AmpersandAmpersandToken && left.type.kind === "union" && lowerer.unions.get(left.type.unionId)?.arms.some((arm) => arm.kind === "bool") && @@ -2418,7 +2457,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE op === ts.SyntaxKind.AmpersandAmpersandToken && target === null && lowerer.implicitParamTypes !== null && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null && (lowerer.typeOf(expr).flags & ts.TypeFlags.Any) !== 0 && left.type.kind === "union" ) { @@ -2579,7 +2618,7 @@ function lowerLogicalPair(lowerer: Lowerer, expr: ts.BinaryExpression, left: IrE // When both operands have native checked representations, preserve // the deciding value and short-circuit evaluation in that domain. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type) ) { diff --git a/packages/compiler/src/frontend/lowering/expressions/element-access.ts b/packages/compiler/src/frontend/lowering/expressions/element-access.ts index 144742257..7ef4b9f25 100644 --- a/packages/compiler/src/frontend/lowering/expressions/element-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/element-access.ts @@ -63,6 +63,8 @@ import { npmStaticPackageOfPath } from "../../npm-static.js"; import { isSafeToRepeat } from "./evaluation-safety.js"; import { lowerClassPrototypeComputedAssignment } from "../class-prototypes.js"; import { isClassCallback } from "../class-callbacks.js"; +import { isKeyedFieldAccess } from "./keyed-field-targets.js"; +import { lowerFieldCompoundValue } from "./updates.js"; /** `a[i]` reads. Only f64 indices into array receivers are modeled; JS * string-key element access (`a["length"]`) and string indexing (`s[0]` @@ -229,7 +231,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre let receiverIr = neverTaintedJsType(lowerer, expr.expression, lowerer.typeOf(expr.expression)) ? null : lowerer.mapTypeOf(lowerer.typeOf(expr.expression)); - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const receiver = tryLowerExpression(lowerer, expr.expression); if (receiver?.type.kind === "dyn") { const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN); @@ -314,7 +316,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre locOf(expr.argumentExpression), expr.argumentExpression, ); - if (isJsSourceFile(expr.getSourceFile()) && recv.type.kind === "bytes") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "bytes") { return { kind: "dynKeyGet", value: lowerer.coerceInto(expr.expression, recv, DYN), @@ -563,7 +565,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre if ( receiver?.type.kind === "dyn" || (receiver && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (receiver.type.kind === "union" || receiver.type.kind === "bytes") && lowerer.dynConvertible(receiver.type)) ) { @@ -767,7 +769,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre if ( arr.type.kind === "array" && (index.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && index.type.kind !== "f64" && lowerer.dynConvertible(index.type))) ) { @@ -820,7 +822,7 @@ export function lowerElementAccess(lowerer: Lowerer, expr: ts.ElementAccessExpre arr.type.kind === "array" && arr.kind === "arrIntrinsic" && arr.method === "slice" && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null ) { const safe = lowerSafeIndexRead(lowerer, arr, index, locOf(expr)); if (safe) return safe; @@ -1304,7 +1306,11 @@ export function lowerRecordKeyRead( ) { declared = shape.fields[0]!.type; } - if (!declared && isJsSourceFile(expr.getSourceFile()) && recordKeyResultOk(lowerer, shape, DYN)) + if ( + !declared && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + recordKeyResultOk(lowerer, shape, DYN) + ) declared = DYN; if (!declared) { lowerer.unsupported( @@ -1417,8 +1423,13 @@ export function lowerElementCompoundParts( const target = expr.left as ts.ElementAccessExpression; fenceNodeModuleMutation(lowerer, target, "assignment"); const loc = locOf(expr); + // A record through a computed key (`totals[side] += n` with a literal + // union key, `counts[name] += 1` on an index-signature record): the + // member compound's receiver, key, old value, rhs, write sequence. + if (isKeyedFieldAccess(lowerer, target)) + return { expr: lowerFieldCompoundValue(lowerer, target, op, expr.right, loc), stmts: null }; const checked = lowerer.lowerExpr(target.expression); - if (checked?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (checked?.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const stmts: IrStmt[] = []; const save = (value: IrExpr, name: string): IrExpr => { const local = lowerer.declareHiddenLocal(name, value.type); @@ -1509,7 +1520,7 @@ export function lowerElementCompoundParts( const rhs = lowerer.lowerExpr(expr.right); const numericRhs = rhs.type.kind === "dyn" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && receiver.type.kind === "bytes" ? { kind: "libCall" as const, fn: "dyn.toNumberCoerce" as const, args: [rhs], type: F64, loc } : lowerOptionalNumber(lowerer, rhs, loc); @@ -1559,7 +1570,7 @@ export function lowerElementCompoundParts( type: F64, loc, }; - } else if (valueType.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + } else if (valueType.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const left = lowerer.coerceToExpected(oldRef(), DYN), right = lowerer.coerceToExpected(rhs, DYN); computed = dynCompoundValue(op, left, right, loc); @@ -1693,7 +1704,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): loc: locOf(expr), }; if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && receiver.type.kind === "object" && lowerer.dynConvertible(receiver.type) ) { @@ -1717,7 +1728,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): loc: locOf(expr), }; } - if (checkedReceiver?.type.kind === "object" && isJsSourceFile(expr.getSourceFile())) { + if (checkedReceiver?.type.kind === "object" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const info = lowerer.classes.get(checkedReceiver.type.className); const key = recordKeyLiteralText(target.argumentExpression) ?? @@ -1732,7 +1743,7 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression): } } let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(target.expression)); - if (isJsSourceFile(expr.getSourceFile()) && checkedReceiver) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && checkedReceiver) { const represented = checkedReceiver.type.kind === "union" ? lowerer.stripUndefinedArm(checkedReceiver.type) diff --git a/packages/compiler/src/frontend/lowering/expressions/hosted-narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/hosted-narrowing.ts new file mode 100644 index 000000000..87a7ba38e --- /dev/null +++ b/packages/compiler/src/frontend/lowering/expressions/hosted-narrowing.ts @@ -0,0 +1,157 @@ +/* Narrowings that refine record arms without naming a stored arm. */ +import type { Lowerer } from "../lowerer.js"; +import { type IrExpr, type IrType, type SrcLoc, typeEquals } from "../../../ir/ir.js"; + +/** A checker narrowing can refine the arms of a union of records with an + * intersection: a predicate `r is Route & { path: string }`, or the + * `"path" in r` narrowing, which intersects arms that do not declare the + * name with `Record<"path", unknown>`. Each narrowed arm then maps to a + * new record shape that is not an arm of the stored union, although the + * value never changed layout. A stored record arm HOSTS a narrowed record + * when both name the same members, each narrowed member refined to a + * subset of the stored slot type (or to `unknown`); a member an assertion + * attached to the stored shape (assertion-members.ts) may be missing from + * the narrowed one. A required member the stored arm does not declare + * cannot exist on its values: native records are closed, so `in` already + * answers false there and the narrowed arm is uninhabited. + * + * Only unions of two or more record arms without a literal discriminant + * qualify: discriminated unions narrow by their discriminant, and a + * record with unit arms keeps its own predicate-strengthening reads. + * + * Returns the stored arms that can hold a value of the narrowed type, or + * null when the union does not qualify, when some narrowed arm is neither + * a stored arm nor a record, or when no stored arm is excluded (there is + * nothing to narrow). */ +export function hostedArmTags( + lowerer: Lowerer, + fromId: string, + narrowed: IrType, +): Set | null { + const from = lowerer.unions.get(fromId); + if (!from || !recordUnion(lowerer, fromId)) return null; + const narrowedArms = + narrowed.kind === "union" ? lowerer.unions.get(narrowed.unionId)?.arms : [narrowed]; + if (!narrowedArms || narrowedArms.length === 0) return null; + const allowed = new Set(); + let refined = false; + for (const arm of narrowedArms) { + const direct = lowerer.armTag(fromId, arm); + if (direct >= 0) { + allowed.add(direct); + continue; + } + if (arm.kind !== "record") return null; + refined = true; + from.arms.forEach((stored, tag) => { + if (stored.kind === "record" && recordHosts(lowerer, stored.shapeId, arm.shapeId)) { + allowed.add(tag); + } + }); + } + if (!refined || allowed.size === 0 || allowed.size === from.arms.length) return null; + return allowed; +} + +/** A union of at least two record arms without a literal discriminant. */ +function recordUnion(lowerer: Lowerer, unionId: string): boolean { + const def = lowerer.unions.get(unionId); + if (!def || def.discriminant) return false; + return def.arms.filter((arm) => arm.kind === "record").length >= 2; +} + +/** Whether every value of `narrowed` (a checker refinement of a value + * stored as `storage`) lives in the stored layout: each narrowed arm is a + * stored arm or a record a stored record arm hosts (see hostedArmTags), + * and at least one needs the hosting. A union storage must qualify as in + * hostedArmTags; a single record only when an assertion attached members + * to it. A binding initialized by such an assertion keeps the stored type, + * so its member writes reach the asserted object. */ +export function hostsAll(lowerer: Lowerer, storage: IrType, narrowed: IrType): boolean { + if (typeEquals(storage, narrowed)) return false; + let storedArms: readonly IrType[]; + if (storage.kind === "union") { + if (!recordUnion(lowerer, storage.unionId)) return false; + storedArms = lowerer.unions.get(storage.unionId)!.arms; + } else if (storage.kind === "record" && lowerer.shapes.attachedNames(storage.shapeId).length) { + storedArms = [storage]; + } else { + return false; + } + const narrowedArms = + narrowed.kind === "union" ? lowerer.unions.get(narrowed.unionId)?.arms : [narrowed]; + if (!narrowedArms || narrowedArms.length === 0) return false; + let refined = false; + for (const arm of narrowedArms) { + if (storedArms.some((stored) => typeEquals(stored, arm))) continue; + if (arm.kind !== "record") return false; + const hosted = storedArms.some( + (stored) => stored.kind === "record" && recordHosts(lowerer, stored.shapeId, arm.shapeId), + ); + if (!hosted) return false; + refined = true; + } + return refined; +} + +function recordHosts(lowerer: Lowerer, storedId: string, narrowedId: string): boolean { + const stored = lowerer.shapes.get(storedId); + const narrowed = lowerer.shapes.get(narrowedId); + if (!stored || !narrowed || stored.tuple || narrowed.tuple) return false; + if (stored.indexValue || narrowed.indexValue) return false; + const attached = lowerer.shapes.attachedNames(storedId); + for (const sf of stored.fields) { + if (!narrowed.fields.some((nf) => nf.name === sf.name) && !attached.includes(sf.name)) + return false; + } + for (const nf of narrowed.fields) { + const sf = stored.fields.find((f) => f.name === nf.name); + if (!sf || !slotRefines(lowerer, nf.type, sf.type)) return false; + } + return true; +} + +/** Whether a stored slot of type `stored` can be read as `narrowed`: the + * same type, `unknown`, or a subset of the stored union's arms (a read + * site narrows from the stored slot exactly as it does for a predicate + * strengthening one record). */ +function slotRefines(lowerer: Lowerer, narrowed: IrType, stored: IrType): boolean { + if (typeEquals(narrowed, stored) || narrowed.kind === "dyn") return true; + if (stored.kind !== "union") return false; + if (narrowed.kind !== "union") return lowerer.armTag(stored.unionId, narrowed) >= 0; + const arms = lowerer.unions.get(narrowed.unionId)?.arms; + return !!arms && arms.every((arm) => lowerer.armTag(stored.unionId, arm) >= 0); +} + +/** The stored view of a hosted narrowing (see hostedArmTags): one stored + * record arm extracted with the checked helper, or the sub-union of the + * hosting arms re-tagged with the excluded arms as traps. Both keep the + * value's stored layout, so member reads and writes reach the same + * object. Null when the narrowing is not a hosted one. */ +export function hostedStorageView( + lowerer: Lowerer, + value: IrExpr, + narrowed: IrType, + loc: SrcLoc, +): IrExpr | null { + if (value.type.kind !== "union") return null; + const fromId = value.type.unionId; + const allowed = hostedArmTags(lowerer, fromId, narrowed); + if (!allowed) return null; + const from = lowerer.unions.get(fromId)!; + if (allowed.size === 1) { + const arm = from.arms[[...allowed][0]!]!; + const helper = lowerer.narrowedArmHelper(fromId, arm, loc); + return helper ? lowerer.checkedNarrowCall(helper, value, arm, loc) : null; + } + const arms = from.arms.filter((_, tag) => allowed.has(tag)); + const subId = lowerer.unions.transform(from, arms); + const trappable = new Set(); + from.arms.forEach((_, tag) => { + if (!allowed.has(tag)) trappable.add(tag); + }); + const helper = lowerer.unionRetagHelper(fromId, subId, loc, trappable); + if (!helper) return null; + const type: IrType = { kind: "union", unionId: subId }; + return { kind: "call", callee: helper, args: [value], type, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts index 5a8169300..b5f99d682 100644 --- a/packages/compiler/src/frontend/lowering/expressions/identifiers.ts +++ b/packages/compiler/src/frontend/lowering/expressions/identifiers.ts @@ -1,5 +1,8 @@ /* Identifier references in expression position: lowerExprInner's identifier branch and its helpers. */ +import { cjsMemberAliasValueOf } from "../cjs-exports-object.js"; +import { builtinClassValue, builtinMemberClassKind } from "../builtins/builtin-class-dyn.js"; import * as ts from "../../ts7/adapter.js"; +import { InternalCompilerError } from "../../../errors.js"; import * as posix from "node:path/posix"; import { type Lowerer, PoisonError } from "../lowerer.js"; import { @@ -20,21 +23,31 @@ import { import { lowerNodeModuleIdentifier } from "../lower-node-module.js"; import { lowerCjsExportsObjectValue } from "../cjs-exports-object.js"; import { captureContextArguments } from "../function-context.js"; -import { isCjsExportTableLiteral, isJsSourceFile, isNodeEsmFile, locOf } from "../../program.js"; +import { argumentsOwner, typedArgumentsCandidate } from "../typed-arguments.js"; +import { + isCjsExportTableLiteral, + isCjsJsFile, + isJsSourceFile, + isNodeEsmFile, + locOf, +} from "../../program.js"; import { builtinClassAliasOwnsNoSlot, runtimeClassValueOf } from "../builtins/class-values.js"; import { cjsAccessorCall, + cjsAccessorSnapshotOf, primitiveCtorClosure, runtimeOptionalElementKey, runtimeOptionalUseOf, } from "../lower-exprs.js"; import { runtimeOptionalReceiverRead } from "./narrowing.js"; +import { builtinGlobalValueAt } from "./property-access.js"; import { varRef } from "../../../ir/build.js"; import { ambientUndefReadType, ambientUndefinedFnSymbolOf, contextualUndefReadType, fenceEarlyAliasUse, + isTemporalGlobalRoot, lowerNsIdentifierValue, nsUndefRead, } from "../lower-namespaces.js"; @@ -86,7 +99,18 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // type and lowerLambda declared the local. if (expr.text === "arguments") { const binding = captureContextArguments(lowerer.fnStack); - if (binding) return { kind: "varRef", localId: binding.id, type: DYN, loc }; + if (binding && lowerer.ctx.pureTuples.has(binding.id)) { + throw new InternalCompilerError("lowerer bug: forwarded-only 'arguments' read as a value"); + } + if (binding) return { kind: "varRef", localId: binding.id, type: binding.type, loc }; + const owner = argumentsOwner(expr); + if (owner && typedArgumentsCandidate(owner)) { + lowerer.unsupported( + "SC1090", + expr, + "'arguments' in a function expression whose value is not asserted to one fixed-arity signature (assert it, as in `function () { … } as F`, and the function lowers against that signature)", + ); + } } // A compiler-projected adapter as a VALUE: the adapter has no // runtime function object (its calls rewrite directly) — call it @@ -109,7 +133,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: ); } if (projection?.kind === "builtin-function") { - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { const callable = lowerer.lowerBuiltinCallableValue( { module: projection.module, member: projection.member }, loc, @@ -125,7 +149,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // `EventEmitter`, `Readable` or `TypeError` (or a const alias of // one) as a value in a TypeScript file: the runtime class's class // object. JavaScript files keep the identity token. - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { const runtimeClass = runtimeClassValueOf(lowerer, expr); if (runtimeClass) return lowerer.classValueRef(runtimeClass, expr); } @@ -163,7 +187,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: const clause = decl.parent.parent.parent; if ( (ts.isCaseClause(clause) || ts.isDefaultClause(clause)) && - !(expr.getStart() >= clause.getStart() && expr.end <= clause.end) + !(ts.tokenStartOfNode(expr) >= ts.tokenStartOfNode(clause) && expr.end <= clause.end) ) { lowerer.unsupported( "SC1090", @@ -351,19 +375,25 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // MODULE compile-time constants, the containing file's location // (Node's exact values when the same tree runs in place; WASI uses // the equivalent guest-visible path; locals shadowing the names - // resolved above). In a file with ESM syntax Node never defines them - // (ReferenceError) — fence rather than invent a value there. + // resolved above), the same strings as import.meta.dirname/filename. + // In a JavaScript ES module Node never defines them, so the read throws + // Node's catchable ReferenceError. A .ts/.tsx file keeps them even when + // Node's strip-types rule would load it as ESM: TypeScript projects are + // commonly built as CommonJS bundles (tsc, esbuild, tsx) where these + // globals exist, and @types/node declares them for every file. if ( (expr.text === "__dirname" || expr.text === "__filename") && lowerer.isStdlibGlobal(expr, expr.text) ) { - const sf = expr.getSourceFile(); - if (isNodeEsmFile(sf, lowerer.program)) { - lowerer.unsupported( - "SC1090", - expr, - `'${expr.text}' in an ES module (Node defines the CJS module globals only in CommonJS modules)`, - ); + const sf = ts.getSourceFileOfNode(expr); + if (isNodeEsmFile(sf, lowerer.program) && !/\.tsx?$/.test(sf.fileName)) { + return { + kind: "libCall", + fn: "global.undefRead", + args: [{ kind: "strLit", value: expr.text, type: STRING, loc }], + type: STRING, + loc, + }; } // The WASI runner mounts the host cwd at guest `/` and the host temp // directory at `/tmp`. Bake the matching GUEST spelling so resource @@ -522,13 +552,22 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // JavaScript sources: an otherwise unsupported builtin VALUE is // the same identity-token story as stdlib globals above (the // harness adds worker_threads.Worker to its identity Set). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { + // EventEmitter and the stream classes are the runtime's untyped + // constructors, so constructor functions can inherit from them. + const classKind = builtinMemberClassKind(bi.module, bi.member); + if (classKind !== null) return builtinClassValue(classKind, loc); // A function member is a function value that fences when called. if (builtinModuleFnOf(lowerer, bi.module, bi.member)) return builtinFunctionFenceValue(lowerer, bi, expr, loc); return { kind: "strLit", - value: `[builtin ${bi.module}.${bi.member}]`, + // node:stream/web exports the web stream globals themselves: + // the same identity token as the global spelling. + value: + bi.module === "stream/web" + ? `[builtin ${bi.member}]` + : `[builtin ${bi.module}.${bi.member}]`, type: STRING, loc, }; @@ -613,7 +652,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: // type-mapper.ts pins the one concrete signature `(value: string) => // primitive`; direct calls `String(x)` never reach here — the call // lowering intercepts them with the wider static coercions). - const jsPrimitiveCtorSelector = isJsSourceFile(expr.getSourceFile()); + const jsPrimitiveCtorSelector = isJsSourceFile(ts.getSourceFileOfNode(expr)); const primitiveName = stdlibGlobalNameOf(lowerer, expr); if ( primitiveName === "String" || @@ -625,7 +664,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: } if (lowerer.isStdlibGlobal(expr, "ArrayBuffer")) return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; - if (isJsSourceFile(expr.getSourceFile()) && lowerer.isStdlibGlobal(expr, "Array")) + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.isStdlibGlobal(expr, "Array")) return { kind: "libCall", fn: "dyn.arrayConstructor", args: [], type: DYN, loc }; if ( Object.values(BYTES_ELEMENT_NAME).includes(expr.text) && @@ -708,9 +747,26 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: } { const sym = lowerer.checker.getSymbolAtLocation(expr); + // The temporal global is typed (TS 7's lib slice) but Node 24 does + // not define it: the read throws Node's ReferenceError. + if (isTemporalGlobalRoot(lowerer, expr, sym)) { + const t = + ambientUndefReadType(lowerer, expr) ?? contextualUndefReadType(lowerer, expr) ?? F64; + return nsUndefRead(lowerer, expr.text, expr, t); + } if (lowerer.isStdlibSymbol(sym) || expr.text === "globalThis") { - if (isJsSourceFile(expr.getSourceFile())) { - const canonical = stdlibGlobalNameOf(lowerer, expr) ?? expr.text; + const canonical = stdlibGlobalNameOf(lowerer, expr) ?? expr.text; + // Builtin constructors have real function values in both source + // languages (TypeScript reads them as dynamic values). + const builtinValue = builtinGlobalValueAt( + lowerer, + expr, + canonical, + loc, + isJsSourceFile(ts.getSourceFileOfNode(expr)), + ); + if (builtinValue !== null) return builtinValue; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc }; } // The families with a WHY: each hint states what makes the @@ -859,6 +915,11 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc: lowerer.checker.getSymbolAtLocation(expr), ); } + // An import of a CommonJS member export whose value is a binding of the + // exporting module (`module.exports = Client; module.exports.StatsD = + // Client`): the member holds that binding's value. + const memberAlias = cjsMemberAliasValueOf(lowerer, expr); + if (memberAlias !== null) return lowerer.lowerExpr(memberAlias); // Preflight guarantees no unresolved identifiers; anything else here // is a blocked declaration's binding (the SC2004 cascade) or a // binding form we don't model yet. @@ -918,5 +979,18 @@ function cjsExportAccessorRead(lowerer: Lowerer, ident: ts.Identifier): IrExpr | if (!ts.isObjectLiteralExpression(getter.parent) || !isCjsExportTableLiteral(getter.parent)) { return null; } + // An ES-module import binding reads the value Node copied when the + // importer linked the CommonJS module (cjsAccessorSnapshotStores). + const raw = lowerer.checker.getSymbolAtLocation(ident); + const binding = raw ? raw.declarations[0] : undefined; + if ( + binding !== undefined && + (ts.isImportSpecifier(binding) || ts.isExportSpecifier(binding)) && + !isCjsJsFile(ts.getSourceFileOfNode(binding), lowerer.program) + ) { + const snapshot = cjsAccessorSnapshotOf(lowerer, getter); + if (snapshot !== null) + return { kind: "varRef", localId: snapshot.id, type: snapshot.type, loc: locOf(ident) }; + } return cjsAccessorCall(lowerer, getter, locOf(ident)); } diff --git a/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts b/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts new file mode 100644 index 000000000..e53d27aa9 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/expressions/keyed-field-targets.ts @@ -0,0 +1,271 @@ +/* Read-modify-write targets for computed record keys: `r[k] op= v`, `r[k]++` and `r[k] ??= v`. */ +import * as ts from "../../ts7/adapter.js"; +import { + BOOL, + F64, + STRING, + typeEquals, + type IrExpr, + type IrStmt, + type SrcLoc, +} from "../../../ir/ir.js"; +import { isJsSourceFile } from "../../program.js"; +import { constituentTypes } from "../../ts7/checker.js"; +import type { FieldTarget } from "../lower-exprs.js"; +import type { Lowerer } from "../lowerer.js"; + +export type KeyedFieldTarget = FieldTarget & { container: "recordKeyed" }; + +/** The property names a computed key's type allows: one or more string + * literals (a literal union such as `"sent" | "received"`, enum string + * members, or a type parameter bound to one inside a generic instance). + * Null for any other key type. */ +export function literalFieldKeys(lowerer: Lowerer, keyNode: ts.Expression): string[] | null { + let t: ts.Type = lowerer.typeOf(keyNode); + if (t.flags & ts.TypeFlags.TypeParameter) { + const bound = lowerer.typeParamTsResolver(t); + if (!bound) return null; + t = bound; + } + const parts = t.flags & ts.TypeFlags.Union ? constituentTypes(t) : [t]; + const keys: string[] = []; + for (const part of parts) { + if (!part.isStringLiteralType()) return null; + if (!keys.includes(part.value)) keys.push(part.value); + } + return keys.length > 0 ? keys : null; +} + +/** Does `r[k]` read and write through declared record storage by key? A + * checker-only test (nothing lowers), so a caller can route before it + * evaluates the receiver: TypeScript sources only, a record whose declared + * fields include every name the key's literal type allows, or a pure + * index-signature record with a string key. */ +export function isKeyedFieldAccess(lowerer: Lowerer, access: ts.ElementAccessExpression): boolean { + if ( + access.questionDotToken || + isJsSourceFile(ts.getSourceFileOfNode(access)) || + lowerer.isProcessEnv(access.expression) + ) + return false; + const keys = literalFieldKeys(lowerer, access.argumentExpression); + if (!keys && !(lowerer.typeOf(access.argumentExpression).flags & ts.TypeFlags.StringLike)) + return false; + const receiver = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); + if (receiver?.kind !== "record") return false; + const shape = lowerer.shapes.get(receiver.shapeId); + if (!shape || shape.tuple) return false; + if (shape.fields.length === 0) return shape.indexValue !== undefined; + return keys !== null && keys.every((k) => shape.fields.some((f) => f.name === k)); +} + +/** The target of a read-modify-write through `r[k]`, or null when + * isKeyedFieldAccess declines. A literal-union key dispatches over the + * declared fields it names, which must share one type; a string key on a + * pure index-signature record reads and writes its entries. The receiver + * and key may arrive already lowered (and saved) by the caller; otherwise + * both lower here, unsaved. A caller that evaluates the target more than + * once saves `obj` and then `key`, the JavaScript evaluation order. With + * `numeric`, a missing index-signature entry reads as NaN (every numeric + * update applies ToNumber to it) instead of a value armed with undefined. */ +export function keyedFieldTarget( + lowerer: Lowerer, + access: ts.ElementAccessExpression, + numeric: boolean, + lowered?: { obj: IrExpr; key: IrExpr }, +): KeyedFieldTarget | null { + if (!isKeyedFieldAccess(lowerer, access)) return null; + const keyNode = access.argumentExpression; + const keys = literalFieldKeys(lowerer, keyNode); + const obj = lowered?.obj ?? lowerer.lowerExpr(access.expression); + if (obj.type.kind !== "record") { + lowerer.unsupported( + "SC1090", + access.expression, + `updating a computed key of a '${lowerer.fmt(obj.type)}' value (narrow the receiver to its record type first)`, + ); + } + const shapeId = obj.type.shapeId; + const shape = lowerer.shapes.get(shapeId)!; + // A single literal spelled at the site (or an identifier whose type is + // that one literal) names the field statically: identifier evaluation + // is pure, so skipping it matches JavaScript. + const indexed = shape.fields.length === 0; + const staticKey = + !indexed && keys?.length === 1 && (ts.isStringLiteralLike(keyNode) || ts.isIdentifier(keyNode)); + const key = staticKey ? null : (lowered?.key ?? lowerer.lowerExpr(keyNode)); + if (key && key.type.kind !== "string") { + lowerer.unsupported( + "SC1090", + keyNode, + `updating a record through a '${lowerer.fmt(key.type)}' key (convert the key to a string first)`, + ); + } + if (keys && !indexed) { + const fields = keys.map((k) => shape.fields.find((f) => f.name === k)); + const first = fields[0]; + if (!first || fields.some((f) => !f || !typeEquals(f.type, first.type))) { + lowerer.unsupported( + "SC1090", + access, + `updating '${lowerer.fmt(obj.type)}' through a key whose fields are not all stored with one type (${keys.map((k) => `'${k}'`).join(", ")}; switch over the key and update each field)`, + ); + } + return { + container: "recordKeyed", + obj, + shapeId, + field: keys[0]!, + keys, + key, + fieldType: first.type, + }; + } + if (!indexed || !shape.indexValue) { + lowerer.unsupported( + "SC1090", + access, + `updating '${lowerer.fmt(obj.type)}' through a runtime key (the stored record declares fields)`, + ); + } + return { + container: "recordKeyed", + obj, + shapeId, + field: "", + keys: null, + key, + fieldType: shape.indexValue, + ...(numeric && shape.indexValue.kind === "f64" ? { missNaN: true as const } : {}), + }; +} + +/** Does `key` name `field`? */ +function keyIs(key: IrExpr, field: string, loc: SrcLoc): IrExpr { + return { + kind: "strEq", + negated: false, + left: key, + right: { kind: "strLit", value: field, type: STRING, loc }, + type: BOOL, + loc, + }; +} + +/** A key outside its literal type (smuggled through a cast) names no + * declared field: a fixed-shape record cannot add the property, so the + * update throws the TypeError of a keyed write miss. */ +function keyMiss(key: IrExpr, target: KeyedFieldTarget, loc: SrcLoc): IrExpr { + const message: IrExpr = { + kind: "strConcat", + left: { + kind: "strConcat", + left: { kind: "strLit", value: "Cannot add property '", type: STRING, loc }, + right: key, + type: STRING, + loc, + }, + right: { kind: "strLit", value: "' to a fixed-shape object", type: STRING, loc }, + type: STRING, + loc, + }; + return { + kind: "libCall", + fn: "error.nodeThrow", + args: [ + { kind: "numLit", value: 1, type: F64, loc }, + { kind: "strLit", value: "", type: STRING, loc }, + message, + ], + type: target.fieldType, + loc, + }; +} + +/** The current value of a keyed target. */ +export function keyedFieldRead(lowerer: Lowerer, target: KeyedFieldTarget, loc: SrcLoc): IrExpr { + const { obj, shapeId, key, fieldType } = target; + if (!target.keys) { + const type = target.missNaN + ? fieldType + : fieldType.kind === "dyn" + ? fieldType + : lowerer.withUndefinedArmOf(fieldType); + if (!type || !key) throw new Error("keyed index-signature target without a key or read type"); + return { + kind: "recordKeyGet", + obj, + shapeId, + key, + ...(target.missNaN ? { missNaN: true as const } : {}), + type, + loc, + }; + } + const get = (field: string): IrExpr => ({ + kind: "recordGet", + obj, + shapeId, + field, + type: fieldType, + loc, + }); + if (!key) return get(target.keys[0]!); + return target.keys.reduceRight( + (otherwise, field) => ({ + kind: "ternary", + cond: keyIs(key, field, loc), + then: get(field), + else_: otherwise, + type: fieldType, + loc, + }), + keyMiss(key, target, loc), + ); +} + +/** Store `value` (already of the target's field type) through a keyed + * target. A multi-key dispatch references the value once per field, so a + * value that is not a plain local is saved first. */ +export function keyedFieldWrite( + lowerer: Lowerer, + target: KeyedFieldTarget, + value: IrExpr, + loc: SrcLoc, +): IrStmt { + const { obj, shapeId, key } = target; + if (!target.keys) { + if (!key) throw new Error("keyed index-signature target without a key"); + return { kind: "recordKeySet", obj, shapeId, key, value, loc }; + } + if (!key) return { kind: "recordSet", obj, shapeId, field: target.keys[0]!, value, loc }; + const prefix: IrStmt[] = []; + let stored = value; + if (value.kind !== "varRef") { + const local = lowerer.declareHiddenLocal("%keyedValue", value.type); + prefix.push({ kind: "varDecl", localId: local.id, init: value, loc }); + stored = { kind: "varRef", localId: local.id, type: value.type, loc }; + } + const chain = target.keys.reduceRight( + (otherwise, field) => [ + { + kind: "if", + cond: keyIs(key, field, loc), + then: [{ kind: "recordSet", obj, shapeId, field, value: stored, loc }], + else_: otherwise, + loc, + }, + ], + [{ kind: "exprStmt", expr: keyMiss(key, target, loc), loc }], + ); + return { kind: "block", body: [...prefix, ...chain], loc }; +} + +/** Save a keyed target's key after its receiver, so a multi-key dispatch + * reads and writes the field the key named when it was evaluated. */ +export function saveKeyedFieldKey( + target: FieldTarget, + save: (value: IrExpr, name: string) => IrExpr, +): void { + if (target.container === "recordKeyed" && target.key) target.key = save(target.key, "%keyedKey"); +} diff --git a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts index 207000717..a70f0dfa9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/narrowing.ts +++ b/packages/compiler/src/frontend/lowering/expressions/narrowing.ts @@ -10,6 +10,7 @@ import { type IrLocal, type IrType, NULL_T, + STRING, type SrcLoc, UNDEFINED_T, arrayOf, @@ -25,10 +26,15 @@ import { hasRuntimeStatics } from "../class-runtime-statics.js"; import { jsBindingHasOpenWrites } from "../lower-stmts.js"; import { narrowClassUnion, narrowStoredClassValue, structuralNarrowing } from "../class-unions.js"; import { checkedClassAssertion, checkedClassUnionAssertion } from "../class-assertions.js"; -import { isOptionalProcessStreamProperty } from "../builtins/process.js"; +import { + isOptionalProcessStreamProperty, + isOptionalProcessVersionsRead, +} from "../builtins/process.js"; +import { requestGeneralStreamSurface } from "../general-streams.js"; import { literalValues } from "../../literal-values.js"; import { literalUnionArm } from "../../union-discriminants.js"; import { checkedArmExtraction } from "../coercions/unions.js"; +import { hostedStorageView, hostsAll } from "./hosted-narrowing.js"; import { nodeThrowExpr, varRef } from "../../../ir/build.js"; import { InternalCompilerError } from "../../../errors.js"; import { @@ -90,7 +96,7 @@ export function lowerTernary( ) { falseArmNarrows.push(n); } - n.forEachChild(collect); + ts.forEachChild(n, collect); }; collect(expr.whenFalse); } @@ -331,7 +337,7 @@ export function lowerTernary( // call ABI. A checker join can erase a rest/arguments pack even though // a later call supplies additional observable arguments. const callableJoin = - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && thenRaw.type.kind === "func" && elseRaw.type.kind === "func" && !typeEquals(thenRaw.type, elseRaw.type) && @@ -341,7 +347,7 @@ export function lowerTernary( // a conditional's type even after that field acquired other values. // A checked arm must retain its actual representation at the join. const checkedJsJoin = - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (thenRaw.type.kind === "dyn" || elseRaw.type.kind === "dyn") && lowerer.dynConvertible(thenRaw.type) && lowerer.dynConvertible(elseRaw.type); @@ -383,7 +389,7 @@ export function lowerTernary( export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrExpr { if ( (expr.type.kind === "union" || expr.type.kind === "dyn") && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node) ) { const symbol = lowerer.checker.getSymbolAtLocation(node.name); @@ -394,7 +400,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx ts.isPropertyDeclaration(decl) && !decl.initializer && !decl.type && - isJsSourceFile(decl.getSourceFile()), + isJsSourceFile(ts.getSourceFileOfNode(decl)), ) ) { // Inference describes later writes, not the initial undefined. @@ -411,10 +417,13 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // instead of misreading the payload (the dyn boundary's usual stance). // Object/array narrowings stay dyn-typed and keep their fences. if (expr.type.kind === "dyn") { + // A process.versions entry declared as a string can be absent here. + if (isOptionalProcessVersionsRead(lowerer, node)) + return lowerer.coerceToExpected(expr, lowerer.withUndefinedArm(STRING)); // An inferred JS member's storage may be deliberately wider than its // declaration (prototype-selected buffers, null-initialized scratch // objects). Extract at the consuming operation, not at the read. - if (isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node)) { const receiver = lowerer.mapTypeOf(lowerer.typeOf(node.expression)); if (receiver?.kind === "object") { const info = lowerer.classes.get(receiver.className); @@ -430,7 +439,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx } } if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) ) { let receiver = node.expression; @@ -459,7 +468,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx if (bound && lowerer.mapTypeOf(bound)?.kind === "dyn") return expr; } } - if (isJsSourceFile(node.getSourceFile()) && ts.isIdentifier(node)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isIdentifier(node)) { const symbol = lowerer.resolveValueSymbol(node); if ( symbol && @@ -493,7 +502,9 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx decl.initializer && !decl.type && !/@(?:param|type)\b/.test( - decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()), + ts + .getSourceFileOfNode(decl) + .text.slice(decl.parent?.pos ?? decl.pos, ts.tokenStartOfNode(decl)), ), ) ) @@ -520,7 +531,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // has no such property. Preserve the checked read for JS capability // probes instead of treating a declaration as a runtime type guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isPropertyAccessExpression(node) && ["isTTY", "columns", "rows"].includes(node.name.text) && lowerer.isStdlibMember(node) @@ -530,7 +541,7 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx // checker flow calls its non-null branch never. The live checked // value remains authoritative; never is not a numeric guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (lowerer.typeOf(node).flags & ts.TypeFlags.Never) !== 0 ) return expr; @@ -587,6 +598,7 @@ 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; + if (isOptionalProcessVersionsRead(lowerer, node)) return expr; // Represented fields and index values can include undefined even when // checker inference or lib.d.ts declares strings (RegExp captures). @@ -659,6 +671,12 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx }; } } + // A narrowing that refines record arms with an intersection keeps the + // stored layout: view the value as the stored arms that can hold it. + if (narrowed?.kind === "union") { + const hosted = hostedStorageView(lowerer, expr, narrowed, expr.loc); + if (hosted) return hosted; + } // A checker type narrowed to a UNIT arm (the `=== undefined` branch) // also stays union-typed: a unit arm has no payload to extract, and // nothing useful reads such a value anyway. (Standalone undefined maps @@ -668,6 +686,14 @@ export function maybeNarrow(lowerer: Lowerer, expr: IrExpr, node: ts.Node): IrEx return expr; } const tag = lowerer.armTag(expr.type.unionId, narrowed); + if ( + tag < 0 && + narrowed.kind === "record" && + !lowerer.unions.get(expr.type.unionId)?.discriminant + ) { + const hosted = hostedStorageView(lowerer, expr, narrowed, expr.loc); + if (hosted) return hosted; + } if (tag < 0) { // The checker may refine fields inside a discriminated record too. // That creates a different mapped shape, but does not change the @@ -823,11 +849,11 @@ export function v8CalleeText(node: ts.Node): string { const object = v8CalleeText(node.expression); return ts.isStringLiteral(key) || ts.isNoSubstitutionTemplateLiteral(key) ? `${object}.${key.text}` - : `${object}[${key.getText()}]`; + : `${object}[${ts.getTextOfNode(key)}]`; } if (ts.isPropertyAccessExpression(node) && !node.questionDotToken) return `${v8CalleeText(node.expression)}.${node.name.text}`; - return node.getText(); + return ts.getTextOfNode(node); } /** Node's TypeError for the operation consuming `node` when it holds a @@ -926,6 +952,10 @@ export function runtimeOptionalReceiverRead( loc, ); if (!helper) { + // Intersection-refined record arms read through their stored + // layout (the predicate `r is Route & { path: string }`). + const hosted = hostedStorageView(lowerer, varRef(local.id, local.type, loc), narrowed, loc); + if (hosted) return hosted; lowerer.unsupported( "SC1090", expr, @@ -946,6 +976,10 @@ export function runtimeOptionalReceiverRead( // path below; mixed storage also needs to validate its concrete class. if (view && (lowerer.unions.get(local.type.unionId)?.arms.length ?? 0) > 2) return view; } + if (narrowed.kind === "record" && lowerer.armTag(local.type.unionId, narrowed) < 0) { + const hosted = hostedStorageView(lowerer, varRef(local.id, local.type, loc), narrowed, loc); + if (hosted) return hosted; + } const undefTag = lowerer.armTag(local.type.unionId, UNDEFINED_T); const def = lowerer.unions.get(local.type.unionId); if (!def || undefTag < 0) @@ -1151,6 +1185,68 @@ export function withRuntimeOptionalNarrowed( * memory); * - dyn → anything else (closures, class instances, void) is rejected: * those types cannot be found inside a JSON dyn. */ +/** A class instance, or a union of class instances and unit arms (a + * runtime-optional binding such as `const node = stack.pop()!`). */ +function classInstanceOrAbsent(lowerer: Lowerer, type: IrType): boolean { + if (type.kind === "object") return true; + if (type.kind !== "union") return false; + const arms = lowerer.unions.get(type.unionId)?.arms ?? []; + return ( + arms.some((arm) => arm.kind === "object") && + arms.every((arm) => arm.kind === "object" || isUnitType(arm)) + ); +} + +/** `Object.fromEntries(...) as T` with T a declared record (the generic + * `transform>(t: T): T` idiom): the + * entries build a pure index-signature record, which has no layout in + * common with T's struct. Node keeps the object itself, with exactly the + * keys and the order the entries gave it. The result of fromEntries is a + * fresh object nothing else references, so the checked-dynamic bridge, + * which keeps keys and their order the same way `JSON.parse(text) as T` + * does, cannot be told apart from the original object; a value outside + * T throws the catchable TypeError of every checked cast. The entries' + * values must be scalars or checked-dynamic values, which the bridge + * passes by value or by identity; native arrays and records would be + * copied. Other sources may be aliased, where a rebuilt object would sever + * mutation, so they keep the exact-shape fence. */ +function freshEntriesRecordAssertion( + lowerer: Lowerer, + expr: ts.AsExpression | ts.TypeAssertion, + inner: IrExpr, + target: IrType | null, +): IrExpr | null { + if (inner.type.kind !== "record" || target?.kind !== "record") return null; + const source = lowerer.shapes.get(inner.type.shapeId); + const dest = lowerer.shapes.get(target.shapeId); + if (!source?.indexValue || source.tuple || source.fields.length > 0) return null; + if (!dest || dest.tuple || dest.indexValue || shapeHasAccessorSlots(dest)) return null; + // Native arrays and records in the entries would be copied on the way + // through the checked-dynamic bridge, severing their identity: only + // slots of scalars or checked-dynamic values pass. + if (extractionCopiesReference(lowerer, source.indexValue)) return null; + let operand: ts.Expression = expr.expression; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + if ( + !ts.isCallExpression(operand) || + !ts.isPropertyAccessExpression(operand.expression) || + operand.expression.name.text !== "fromEntries" || + !lowerer.isStdlibGlobal(operand.expression.expression, "Object") + ) + return null; + if (!lowerer.dynConvertible(inner.type)) return null; + if ( + !canDynCheckTo( + target, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) + return null; + const loc = locOf(expr); + return { kind: "dynCheck", value: lowerer.coerceToExpected(inner, DYN), type: target, loc }; +} + /** `e as T` and the old-style assertion `e` — one node shape (both * carry `.type` and `.expression`), one lowering. */ export function lowerAsExpression( @@ -1202,12 +1298,14 @@ export function lowerAsExpression( // the requested shape; an assertion does not bypass runtime checks. const narrowed = lowerer.mapTypeOf(lowerer.typeOf(expr.expression)); const bridged = - narrowed && - (narrowed.kind === "f64" || - narrowed.kind === "bool" || - narrowed.kind === "string" || - narrowed.kind === "object" || - narrowed.kind === "dyn"); + (narrowed && + (narrowed.kind === "f64" || + narrowed.kind === "bool" || + narrowed.kind === "string" || + narrowed.kind === "object" || + narrowed.kind === "dyn")) || + // `catch (e: any)`: the binding reads as its checked-dynamic value. + (narrowed === null && (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Any) !== 0); if (!bridged) lowerer.unsupported("SC1063", expr); } const inner = lowerer.lowerExpr(expr.expression); @@ -1228,8 +1326,10 @@ export function lowerAsExpression( if (target?.kind === "dyn" && (targetTs0.flags & ts.TypeFlags.Object) !== 0) { // A class instance asserted to a class-backed protocol view (a type // class instances flow into): the same identity-keeping box an - // implicit flow into the view uses. - if (inner.type.kind === "object" && lowerer.classImplementedProtocol(targetTs0)) + // implicit flow into the view uses. A runtime-optional binding of a + // class (`const node = stack.pop()!`) boxes the same way, and its + // undefined arm stays undefined, as in Node. + if (classInstanceOrAbsent(lowerer, inner.type) && lowerer.classImplementedProtocol(targetTs0)) return lowerer.coerceInto(expr, inner, DYN); const sourceShape = inner.type.kind === "record" ? lowerer.shapes.get(inner.type.shapeId) : undefined; @@ -1286,6 +1386,14 @@ export function lowerAsExpression( ) { return lowerer.coerceInto(expr, inner, target); } + const rebuilt = freshEntriesRecordAssertion(lowerer, expr, inner, target); + if (rebuilt) return rebuilt; + // `job as Job & { startedAt: number }` on a record: the asserted + // record refines the stored one, so the assertion keeps the object + // (a width copy would sever writes through the asserted view). + if (inner.type.kind === "record" && target && hostsAll(lowerer, inner.type, target)) { + return inner; + } // Static assertions normally erase, but record layouts are // monomorphic: a consumer selected from the asserted shape must see // that shape physically. Reuse the ordinary slot coercion so plain @@ -1312,6 +1420,12 @@ export function lowerAsExpression( return lowerer.checkedNarrowCall(helper, inner, target, locOf(expr)); } } + // An intersection refining the stored record arms keeps the value + // (hosted-narrowing.ts): the stored arms that can hold it. + if (target && (target.kind === "union" || target.kind === "record")) { + const hosted = hostedStorageView(lowerer, inner, target, locOf(expr)); + if (hosted) return hosted; + } } // `node.parent as Sub | undefined`: a union target whose class arm is // narrower than the stored class gets the same checked class view as @@ -1327,6 +1441,12 @@ export function lowerAsExpression( return inner; // erasure, unchanged } + // `value as never` is the escape hatch that makes an unknown value fit + // any slot (`tagSuccess(value as never)`). No value has type never, so + // checking against its placeholder layout would reject every value. + // Erase it instead: the consuming slot applies the same validated + // extraction an unknown value gets wherever the checker admits it. + if (expr.type.kind === ts.SyntaxKind.NeverKeyword) return inner; const targetTs = lowerer.checker.getTypeFromTypeNode(expr.type); const target = lowerer.mapTypeOf(targetTs) ?? dynFallbackType(lowerer, expr.type, targetTs); if (!target) lowerer.badType(expr.type, targetTs); @@ -1401,6 +1521,9 @@ export function lowerAsExpression( if (DYN_HANDLE_KINDS.has(target.kind)) { return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) }; } + // `value as NodeJS.ReadableStream`: the general stream classes take + // the checked cast (general-streams.ts). + requestGeneralStreamSurface(lowerer, expr); lowerer.unsupported( "SC1090", expr, diff --git a/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts b/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts index e3b9bf025..51b7f4028 100644 --- a/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts +++ b/packages/compiler/src/frontend/lowering/expressions/native-proxy.ts @@ -25,7 +25,7 @@ export function lowerNativeProxy(lowerer: Lowerer, expr: ts.NewExpression): IrEx if (args.length !== 2 || args.some(ts.isSpreadElement)) lowerer.noLowering("new Proxy with this argument list", expr); const handler = args[1]!; - if (isJsSourceFile(expr.getSourceFile()) && ts.isObjectLiteralExpression(handler)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && ts.isObjectLiteralExpression(handler)) { for (const prop of handler.properties.nodes) { const callback = ts.isMethodDeclaration(prop) ? prop diff --git a/packages/compiler/src/frontend/lowering/expressions/nullish-assignment.ts b/packages/compiler/src/frontend/lowering/expressions/nullish-assignment.ts index 5ef3991da..81b3b9d69 100644 --- a/packages/compiler/src/frontend/lowering/expressions/nullish-assignment.ts +++ b/packages/compiler/src/frontend/lowering/expressions/nullish-assignment.ts @@ -18,6 +18,11 @@ import { arrayNumberRead, arrayValueRead, arrayValueStore } from "../array-value import { fenceNodeModuleMutation } from "../lower-node-module.js"; import { globalSymbolKey } from "./global-symbols.js"; import { tryLowerExpression } from "./try-lower-expression.js"; +import { + type KeyedFieldTarget, + keyedFieldTarget, + saveKeyedFieldKey, +} from "./keyed-field-targets.js"; import { eventAccessorRead, eventAccessorWrite, @@ -147,6 +152,7 @@ export function lowerShortCircuitAssignment(lowerer: Lowerer, expr: ts.BinaryExp } else { const receiver = save(lowerer.lowerExpr(target.expression), "%shortCircuitReceiver"); let key = lowerer.lowerExpr(target.argumentExpression); + let keyed: KeyedFieldTarget | null = null; if (receiver.type.kind === "dyn") { key = save( { @@ -214,6 +220,48 @@ export function lowerShortCircuitAssignment(lowerer: Lowerer, expr: ts.BinaryExp !nullish && !and && element.kind === "f64" ? arrayNumberRead(receiver, key, elementLoc) : arrayValueRead(lowerer, receiver, key, element, elementLoc); + } else if ( + receiver.type.kind === "record" && + key.type.kind === "string" && + pureIndexShapeOf(lowerer, receiver.type.shapeId) !== null + ) { + // `timings[phase] ??= v` over a pure index-signature record + // (`Record`): the keyed read answers the value or + // undefined for a missing key, and the write inserts or replaces + // the key, exactly the two halves `o[k]` and `o[k] = v` lower to. + const shapeId = receiver.type.shapeId; + const indexValue = pureIndexShapeOf(lowerer, shapeId)!; + key = save(key, "%shortCircuitKey"); + const readType = + indexValue.kind === "dyn" ? indexValue : lowerer.withUndefinedArmOf(indexValue); + if (!readType) { + lowerer.unsupported( + "SC1090", + target, + "short-circuit assignment through this indexed receiver", + ); + } + storage = indexValue; + read = { kind: "recordKeyGet", obj: receiver, shapeId, key, type: readType, loc }; + write = (value) => ({ + kind: "recordKeySet", + obj: receiver, + shapeId, + key, + value: lowerer.coerceToExpected(value, indexValue), + loc, + }); + } else if ( + receiver.type.kind === "record" && + (keyed = keyedFieldTarget(lowerer, target, false, { obj: receiver, key })) + ) { + // `totals[side] ??= v` through a literal-union key: the read and + // the write dispatch over the declared fields the key can name. + const field = keyed; + saveKeyedFieldKey(field, save); + storage = field.fieldType; + read = lowerer.fieldGetExpr(field, locOf(target), target); + write = (value) => lowerer.fieldSetStmt(field, value, loc, target); } else { lowerer.unsupported( "SC1090", @@ -265,3 +313,11 @@ export function lowerShortCircuitAssignment(lowerer: Lowerer, expr: ts.BinaryExp } return finish({ kind: "nullish", left: read, right, type: read.type, loc }); } + +/** The value type of a pure index-signature record shape (no declared + * fields, not a tuple), or null. */ +function pureIndexShapeOf(lowerer: Lowerer, shapeId: string): IrType | null { + const shape = lowerer.shapes.get(shapeId); + if (!shape || shape.tuple || shape.fields.length > 0 || !shape.indexValue) return null; + return shape.indexValue; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts index 6238479e0..e276326c9 100644 --- a/packages/compiler/src/frontend/lowering/expressions/object-literals.ts +++ b/packages/compiler/src/frontend/lowering/expressions/object-literals.ts @@ -276,7 +276,7 @@ export function lowerDynObjectLiteral( const raw = builtin === "path" || builtin === "path/posix" || builtin === "path/win32" ? pathModuleValue(lowerer, builtin === "path" ? "path/posix" : builtin, locOf(prop)) - : isJsSourceFile(expr.getSourceFile()) + : isJsSourceFile(ts.getSourceFileOfNode(expr)) ? lowerer.lowerExprExpecting(prop.expression, DYN) : lowerer.lowerExpr(prop.expression); fenceSymbolFieldCopy(lowerer, prop.expression, raw.type); @@ -410,7 +410,7 @@ export function lowerDynObjectLiteral( // Repeated references must compare equal and mutations remain shared. if ( !boxValue && - isJsSourceFile(prop.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(prop)) && v.kind === "dynFrom" && v.value.type.kind === "array" ) { @@ -495,7 +495,7 @@ function fenceClosureProbe( slotType: IrType | undefined, attempt: () => IrExpr, ): IrExpr | null { - if (!isJsSourceFile(node.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) return null; let inner: ts.Node = node; while (ts.isParenthesizedExpression(inner)) inner = inner.expression; if ( @@ -580,7 +580,10 @@ function literalUnionArmOf( if (own.isUnionType()) return null; props.length = 0; for (const property of lowerer.checker.getPropertiesOfType(own)) { - props.push({ name: property.name, type: lowerer.checker.getTypeOfSymbol(property) }); + props.push({ + name: ts.symbolName(property), + type: lowerer.checker.getTypeOfSymbol(property), + }); } } /** Cheap literal discriminants avoid checker traffic for unrelated arms. @@ -640,6 +643,74 @@ function literalUnionArmOf( /** Object spread treats nullish sources as empty. Preserve evaluation once, * then turn `Record | undefined` into either that record or a fresh * empty record of the same represented shape. */ +/** `{ ...unknownValue }` into a pure index-signature record (headers + * forwarded from an untyped parameter): Node copies the source's own + * enumerable keys in order, and spreads nothing for null, undefined, + * numbers and booleans. An object source converts through the checked + * extraction into the target's own shape, which keeps its keys and order + * and checks each value against the slot type (a value outside it throws + * the catchable TypeError of every unknown-to-typed conversion). A string + * source takes the same extraction, which rejects it, rather than + * silently dropping the characters Node would spread. Null for other + * sources and targets. */ +function dynIndexSpreadSource( + lowerer: Lowerer, + source: IrExpr, + target: IrType, + loc: SrcLoc, +): IrExpr | null { + if (source.type.kind !== "dyn" || target.kind !== "record") return null; + const shape = lowerer.shapes.get(target.shapeId); + if (!shape?.indexValue || shape.fields.length > 0 || shape.tuple) return null; + if ( + !canDynCheckTo( + target, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) + return null; + const local = lowerer.declareHiddenLocal("%dynSpread", DYN); + const ref = varRef(local.id, DYN, loc); + const test = (kind: "object" | "string" | "nullish"): IrExpr => ({ + kind: "dynTest", + test: kind, + value: ref, + type: BOOL, + loc, + }); + const extract: IrExpr = { kind: "dynCheck", value: ref, type: target, loc }; + const empty: IrExpr = { kind: "recordLit", fields: [], type: target, loc }; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: source, loc }], + result: { + kind: "ternary", + cond: test("nullish"), + then: empty, + else_: { + kind: "ternary", + cond: { + kind: "logical", + op: "||", + left: test("object"), + right: test("string"), + type: BOOL, + loc, + }, + then: extract, + else_: empty, + type: target, + loc, + }, + type: target, + loc, + }, + type: target, + loc, + }; +} + function lowerOptionalIndexSpreadSource( lowerer: Lowerer, source: IrExpr, @@ -698,7 +769,7 @@ export function lowerObjectLiteral( // Preserve their checked arrays instead of assigning never[] a numeric ABI. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (prop) => ts.isPropertyAssignment(prop) && @@ -711,7 +782,7 @@ export function lowerObjectLiteral( } if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (prop) => ts.isPropertyAssignment(prop) && @@ -726,7 +797,7 @@ export function lowerObjectLiteral( // the represented binding rather than checking it back into that type. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some((prop) => { let value = ts.isPropertyAssignment(prop) ? prop.initializer @@ -754,7 +825,7 @@ export function lowerObjectLiteral( // JavaScript object methods/accessors carry a live receiver. Keep the // literal in checked native storage instead of erasing its prototype ABI. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.properties.nodes.length === 0 || expr.properties.nodes.some( (prop) => @@ -770,7 +841,7 @@ export function lowerObjectLiteral( // binding. Construct in that representation: an intermediate record would // check nested aliases back into inferred shapes and lose their identity. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && ts.isVariableDeclaration(expr.parent) && expr.parent.initializer === expr && ts.isIdentifier(expr.parent.name) && @@ -782,7 +853,7 @@ export function lowerObjectLiteral( // methods absent from its inferred record shape. Copy the real properties. if ( !expected && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some( (property) => ts.isSpreadAssignment(property) && @@ -802,7 +873,7 @@ export function lowerObjectLiteral( // values. TypeScript keeps the record world and its fence: this shape // only arises in checked-dynamic JS. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.some((p) => { const n = ts.isSpreadAssignment(p) ? undefined : p.name; return n !== undefined && ts.isComputedPropertyName(n); @@ -849,7 +920,7 @@ export function lowerObjectLiteral( // receiver (capturing the record under construction would be an // RC cycle), and the generic lexical-this walk would incorrectly // capture an ENCLOSING method's `this` — the object-method rule. - if (!isJsSourceFile(expr.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) { rejectThisInObjectAccessor(lowerer, prop.body ?? prop); } } @@ -925,7 +996,7 @@ export function lowerObjectLiteral( // `null`, which maps to nothing on its own). if ( tsType.isUnionType() && - ts.constituentTypes(tsType).some((t) => t.getSymbol()?.name === "PromiseLike") + ts.constituentTypes(tsType).some((t) => ts.symbolNameOf(t.getSymbol()) === "PromiseLike") ) { tsType = lowerer.checker.getAwaitedType(tsType) ?? tsType; } @@ -946,7 +1017,7 @@ export function lowerObjectLiteral( // A JavaScript call can contextually type an object literal as string even // though the literal itself is a record. Keep its own shape so a caller // performing ToString can convert it after the value is built. - if (mapped?.kind === "string" && isJsSourceFile(expr.getSourceFile())) { + if (mapped?.kind === "string" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const own = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (own?.kind === "record") mapped = own; } @@ -1079,7 +1150,7 @@ export function lowerObjectLiteral( // (rest-param function types) narrow away. SEMANTICS.md documents the // enumeration divergence. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (!mapped || expr.properties.nodes.some( (p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p), @@ -1119,7 +1190,7 @@ export function lowerObjectLiteral( // consumers ride the keyed-dyn paths. TypeScript keeps the fence. if ( mapped?.kind === "dyn" || - (!mapped && isJsSourceFile(expr.getSourceFile())) || + (!mapped && isJsSourceFile(ts.getSourceFileOfNode(expr))) || (!expected && mapped && jsOpenObjectType(expr, mapped, lowerer.shapes, lowerer.unions).kind === "dyn") @@ -1135,7 +1206,7 @@ export function lowerObjectLiteral( // extra fields, spreads, accessors, and keyed shapes retain their paths. if ( expected && - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.properties.nodes.every( (p) => ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p), ) @@ -1187,7 +1258,7 @@ export function lowerObjectLiteral( // JavaScript Dates carry native object identity in checked storage. An // inferred scalar Date field would erase that identity before boxing. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && shape.fields.some((field) => field.type.kind === "date") ) { return lowerDynObjectLiteral(lowerer, expr); @@ -1197,7 +1268,7 @@ export function lowerObjectLiteral( // so the literal's shape NARROWS to its plain fields (reads resolve // through the lifted accessors; Object.keys over the value omits // accessor names — the documented divergence). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const accessorNames = new Set( expr.properties.nodes .filter((p) => ts.isGetAccessorDeclaration(p) || ts.isSetAccessorDeclaration(p)) @@ -1233,7 +1304,7 @@ export function lowerObjectLiteral( ); if ( topLevelJsDecl && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && assignedProperties.length === expr.properties.nodes.length ) { const provided = new Set(assignedProperties.map((prop) => propNameText(lowerer, prop.name))); @@ -1260,7 +1331,7 @@ export function lowerObjectLiteral( // multiple spreads, spread-after-explicit order, and shape-changing width // copies retain their existing lowering and diagnostics. if ( - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && !shape.indexValue && !shape.tuple && !shapeHasAccessorSlots(shape) && @@ -1372,7 +1443,7 @@ export function lowerObjectLiteral( vNode, ); contributors.push({ - kind: "field", + kind: "condField", name: csProp.name.text, value: { kind: "ternary", @@ -1387,6 +1458,7 @@ export function lowerObjectLiteral( } let src = lowerer.lowerExpr(prop.expression); src = lowerOptionalIndexSpreadSource(lowerer, src, locOf(prop)) ?? src; + src = dynIndexSpreadSource(lowerer, src, type, locOf(prop)) ?? src; if (src.type.kind !== "record") { lowerer.unsupported( "SC1090", @@ -1631,7 +1703,11 @@ export function lowerObjectLiteral( } shapeMismatch(prop); } - const absent = lowerer.absentFieldValue(fieldType, locOf(prop)); + // An 'unknown' slot holds the checked-dynamic undefined for the + // empty arm, the absent-property read the width copy uses too. + const absent = + lowerer.absentFieldValue(fieldType, locOf(prop)) ?? + (fieldType.kind === "dyn" ? dynUndefinedExpr(locOf(prop)) : null); if (!absent) { lowerer.unsupported( "SC1090", @@ -2146,7 +2222,7 @@ export function lowerObjectLiteral( // closures is side-effect-free, so their position among the data // fields' source-order evaluation is unobservable — exactly JS, // where accessor definitions evaluate nothing. - if (isJsSourceFile(expr.getSourceFile())) continue; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) continue; const name = propNameText(lowerer, prop.name); // key-form-checked above const slotName = `${ts.isGetAccessorDeclaration(prop) ? "%get" : "%set"}:${name}`; const slotT = fieldTypes.get(slotName); @@ -2268,7 +2344,7 @@ export function lowerObjectLiteral( if ( !(err instanceof PoisonError) || !pureMember || - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || lowerer.diagSink !== null || lowerer.diags.length <= propDiagsBefore || lowerer.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001") @@ -2363,7 +2439,7 @@ export function lowerObjectLiteral( * their identity. Changing a discriminator or adding variant-specific * fields needs a different result layout and stays on the ordinary path. */ function lowerUnionRecordClone(lowerer: Lowerer, expr: ts.ObjectLiteralExpression): IrExpr | null { - if (isJsSourceFile(expr.getSourceFile())) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const spread = expr.properties.nodes[0]; if (!spread || !ts.isSpreadAssignment(spread)) return null; let sourceNode = spread.expression; @@ -2559,6 +2635,14 @@ export function andSpreadOf( while (ts.isParenthesizedExpression(right)) right = right.expression; if (!ts.isObjectLiteralExpression(right)) return null; const spreadType = lowerer.mapTypeOf(lowerer.typeOf(e)); + // Every falsy value is a primitive, which spreads nothing, so `c && { + // ... }` contributes the literal's members exactly when c is truthy, + // whatever c's type. The checker's union of the two decides the + // representation where it maps; where it does not (a member typed by a + // checked-dynamic value such as an AbortSignal), the syntax decides. + if (spreadType === null || spreadType.kind === "dyn") { + return syntacticAndSpread(right, e.left); + } if (spreadType?.kind !== "union") return null; const arms = lowerer.unions.get(spreadType.unionId)?.arms ?? []; if ( @@ -2590,6 +2674,26 @@ export function andSpreadOf( return { cond: e.left, props, whenTrue: true }; } +function syntacticAndSpread( + right: ts.ObjectLiteralExpression, + cond: ts.Expression, +): { cond: ts.Expression; props: CondSpreadProp[]; whenTrue: boolean } | "unsupported" { + if (right.properties.nodes.length === 0) return "unsupported"; + const props: CondSpreadProp[] = []; + for (const p of right.properties.nodes) { + if ( + (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && + p.name && + (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) + ) { + props.push(p as CondSpreadProp); + } else { + return "unsupported"; + } + } + return { cond, props, whenTrue: true }; +} + /** A conditional spread's carrier property: `name: value` or shorthand, * with an identifier/string-literal name. */ export type CondSpreadProp = (ts.PropertyAssignment | ts.ShorthandPropertyAssignment) & { @@ -3089,6 +3193,22 @@ export function lowerShorthandValue( `generic functions as values (call '${propName.text}' directly)`, ); } + // A program class name (`{ parse, TomlError }`, an imported class + // included): the class object, exactly the long form `{ C: C }`. + // Generic classes keep the identifier path's contextual pinning. + const classInfo = lowerer.classBySymbol.get(resolved); + if (classInfo && !classInfo.generic) { + const decoratedGlobal = classInfo.classDecorators?.valueGlobalId; + if (decoratedGlobal !== undefined) { + return { + kind: "varRef", + localId: decoratedGlobal, + type: { kind: "classval", className: classInfo.def.name }, + loc, + }; + } + return lowerer.classValueRef(classInfo, propName); + } } lowerer.rejectUnresolvedSymbol( symbol diff --git a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts index 930e1a7cb..5dc512a6c 100644 --- a/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts +++ b/packages/compiler/src/frontend/lowering/expressions/optional-chains.ts @@ -1,3 +1,5 @@ +import { isSafeToDiscard } from "./evaluation-safety.js"; +import { completeFuncValueArgs } from "../call-arguments.js"; import { dynUndefinedExpr } from "../../../ir/build.js"; import * as ts from "../../ts7/adapter.js"; import { DYN, STRING, UNDEFINED_T, VOID, isUnitType, typeEquals } from "../../../ir/ir.js"; @@ -90,7 +92,7 @@ export function isRequireMainFilename(lowerer: Lowerer, expr: ts.Expression): bo const main = expr.expression; if (!ts.isPropertyAccessExpression(main) || main.name.text !== "main") return false; if (!lowerer.isStdlibGlobal(main.expression, "require")) return false; - return !isNodeEsmFile(expr.getSourceFile(), lowerer.program); + return !isNodeEsmFile(ts.getSourceFileOfNode(expr), lowerer.program); } export function isOptionalChainTail(lowerer: Lowerer, expr: ts.Expression): boolean { @@ -255,14 +257,14 @@ export function lowerOptionalChain( const spreads: { arg: number; what: string }[] = []; const args = expr.arguments.nodes.map((arg, index) => { if (!ts.isSpreadElement(arg)) return lowerer.lowerExprExpecting(arg, DYN); - spreads.push({ arg: index, what: arg.expression.getText() }); + spreads.push({ arg: index, what: ts.getTextOfNode(arg.expression) }); return lowerer.lowerExprExpecting(arg.expression, DYN); }); const body: IrExpr = { kind: "dynCall", callee, ...(thisValue ? { receiver: thisValue } : {}), - calleeName: recvNode.getText(), + calleeName: ts.getTextOfNode(recvNode), args, ...(spreads.length ? { spreads } : {}), type: DYN, @@ -429,7 +431,22 @@ export function lowerOptionalChain( if (dotNode === expr && ts.isCallExpression(expr)) { if (narrowed.kind !== "func") lowerer.badType(recvNode, lowerer.typeOf(recvNode)); const params = narrowed.params; + // Forwarded fixed tuples (`f?.(...args)`) complete into the slots. + if (expr.arguments.nodes.some(ts.isSpreadElement)) { + const completed = completeFuncValueArgs(lowerer, expr, narrowed, loc); + const call: IrExpr = { + kind: "callValue", + callee: recvRef, + args: completed, + type: narrowed.ret, + loc, + }; + return lowerer.finishOptionalChain(expr, id, receiver, call, loc); + } const args = expr.arguments.nodes.map((a, i) => lowerer.lowerExprExpecting(a, params[i])); + // Effect-free surplus arguments (a dropped `void` slot) are ignored. + while (narrowed.rest !== true && args.length > params.length && isSafeToDiscard(args.at(-1)!)) + args.pop(); const body: IrExpr = { kind: "callValue", callee: recvRef, args, type: narrowed.ret, loc }; return lowerer.finishOptionalChain(expr, id, receiver, body, loc); } diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 7f0b2ddbb..36ea0e8c2 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -1,5 +1,10 @@ /* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */ import * as ts from "../../ts7/adapter.js"; +import { lowerHistogramProperty } from "../builtins/performance.js"; +import { staticFunctionNameRead } from "../static-function-names.js"; +import { typedArgumentsArity } from "../typed-arguments.js"; +import { isThisParameter } from "../call-signatures.js"; +import { localPrivateBrandClass } from "../private-brands.js"; import { type Lowerer, PoisonError, jsFuncNameOf, neverTaintedJsType, own } from "../lowerer.js"; import { ARRAY_METHODS, @@ -76,6 +81,11 @@ import { lifetimeRegionMemberOf } from "../../experimental-import.js"; import { lowerInternTableProperty } from "../intern-tables.js"; import { lowerThreadSlotsProperty } from "../thread-slots.js"; import { builtinClassRefOf, runtimeClassValueOfType } from "../builtins/class-values.js"; +import { + lowerRuntimeConstructor, + lowerRuntimeConstructorName, + runtimeConstructorReceiver, +} from "../builtins/runtime-constructor.js"; import { findGenericMethodOn, lowerStaticFieldRead } from "../lower-classes.js"; import { lowerClassPrototypeData } from "../class-prototypes.js"; import { lowerEnumAccess } from "../lower-enums.js"; @@ -90,6 +100,7 @@ import { lowerRequireMainProperty, } from "../lower-node-module.js"; import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../../ir/build.js"; +import { TEMPORAL_GLOBAL } from "../../builtin-modules.js"; import { tryLowerExpression } from "./try-lower-expression.js"; import { cjsClassExprWholeExportOf, @@ -148,6 +159,7 @@ import { errorPropertyRead } from "../error-methods.js"; import { isSafeToRepeat } from "./evaluation-safety.js"; import { isZlibStreamMember, lowerZlibStreamBytesWritten } from "../builtins/compression.js"; import { isFsStreamMember, lowerFsStreamProperty } from "../builtins/fs-streams.js"; +import { keyedFieldRead } from "./keyed-field-targets.js"; /** True when a property access names an ABSTRACT property declaration * (`abstract p: number` — a PropertyDeclaration, not an accessor). @@ -193,7 +205,7 @@ function isParseArgsDynProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpress const sym = lowerer.checker.getSymbolAtLocation(expr.name); if (!sym) return false; return sym.declarations.some((d) => { - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let inParseArgsType = false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if ( @@ -221,7 +233,7 @@ function isStatFsDynProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpression return ( !!symbol && symbol.declarations.some((declaration) => { - if (!lowerer.isStdlibFile(declaration.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration))) return false; const parent = declaration.parent; if (!ts.isInterfaceDeclaration(parent)) return false; if ( @@ -248,6 +260,23 @@ export function lowerPropertyAccessExpr( const navigator = lowerNavigatorMember(lowerer, expr, NODE_COMPAT_VERSION); if (navigator) return navigator; } + // `fn.name` on a function parameter of a generic instance keyed by + // its argument's static name (call-site specialization). + { + const staticName = staticFunctionNameRead(lowerer, expr); + if (staticName !== null) return { kind: "strLit", value: staticName, type: STRING, loc }; + } + // An IntervalHistogram's readings (min, max, mean, stddev, count, exceeds). + { + const reading = lowerHistogramProperty(lowerer, expr); + if (reading) return reading; + } + // `.length` of a forwarded fixed tuple (a typed `arguments`, a tuple + // rest parameter, an alias of either): the signature's arity. + if (expr.name.text === "length" && !expr.questionDotToken) { + const arity = typedArgumentsArity(lowerer, expr.expression); + if (arity !== null) return { kind: "numLit", value: arity, type: F64, loc }; + } // `require("./x").member` over a program module's export table: the // module's %init at this position, then the member read exactly like // a `const m = require("./x")` binding's (cjs-inline-require.ts). @@ -290,6 +319,16 @@ export function lowerPropertyAccessExpr( const ref = builtinClassRefOf(lowerer, expr.expression); if (ref !== null) return { kind: "strLit", value: ref.name, type: STRING, loc }; } + // `x.constructor.name` and `x.constructor` on an instance of + // EventEmitter or a stream class (builtins/runtime-constructor.ts). + if (expr.name.text === "name" && !expr.questionDotToken) { + const ctor = runtimeConstructorReceiver(lowerer, expr.expression); + if (ctor) return lowerRuntimeConstructorName(lowerer, ctor.receiver, loc); + } + { + const ctor = runtimeConstructorReceiver(lowerer, expr); + if (ctor) return lowerRuntimeConstructor(lowerer, expr, ctor.info); + } // `C.name` through a runtime-provided class VALUE (a `typeof Readable` // parameter): the class object's stored name. if ( @@ -497,7 +536,7 @@ export function lowerPropertyAccessExpr( if ( represented && isMatchSliceType(lowerer, represented.type) && - (lowerer.isStdlibMember(expr) || isJsSourceFile(expr.getSourceFile())) + (lowerer.isStdlibMember(expr) || isJsSourceFile(ts.getSourceFileOfNode(expr))) ) { const recv = represented; const key: IrExpr = { kind: "strLit", value: expr.name.text, type: STRING, loc }; @@ -530,7 +569,7 @@ export function lowerPropertyAccessExpr( ts.isIdentifier(expr.expression) && expr.expression.text === "arguments" && !lowerer.ctx.argumentsLocal && - lowerer.typeOf(expr.expression).getSymbol()?.name === "IArguments" && + ts.symbolNameOf(lowerer.typeOf(expr.expression).getSymbol()) === "IArguments" && lowerer.isStdlibSymbol(lowerer.typeOf(expr.expression).getSymbol()) ) { let fn: ts.Node | undefined = expr.parent; @@ -553,9 +592,12 @@ export function lowerPropertyAccessExpr( p.dotDotDotToken === undefined, ); if (fixedArity) { + // A `this` parameter is type-only: callers never pass it. return { kind: "numLit", - value: (fn as ts.FunctionLikeDeclaration).parameters.nodes.length, + value: (fn as ts.FunctionLikeDeclaration).parameters.nodes.filter( + (p) => !isThisParameter(p), + ).length, type: F64, loc, }; @@ -598,9 +640,9 @@ export function lowerPropertyAccessExpr( if ( !expr.questionDotToken && isModuleExportsAccess(expr) && - isCjsJsFile(expr.getSourceFile(), lowerer.program) + isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) ) { - const whole = cjsClassExprWholeExportOf(expr.getSourceFile()); + const whole = cjsClassExprWholeExportOf(ts.getSourceFileOfNode(expr)); if (whole) { let inBody = false; for ( @@ -613,7 +655,7 @@ export function lowerPropertyAccessExpr( break; } } - if (inBody || expr.getStart() > whole.stmt.getEnd()) { + if (inBody || ts.tokenStartOfNode(expr) > whole.stmt.end) { return lowerer.classValueRef(lowerer.lowerClassExpressionInfo(whole.classExpr), expr); } } @@ -725,6 +767,12 @@ export function lowerPropertyAccessExpr( lowerStreamObjectProperty(lowerer, expr) ?? lowerEventObjectProperty(lowerer, expr) ?? lowerer.lowerUnionProperty(expr) ?? + // A builtin constructor reached through another binding (`const D = + // Date; D.prototype`) holds its interned function value, which + // models only name, length and constructor: a static member or the + // prototype that no lowering above claimed keeps the SC2020 fence + // rather than reading undefined through the field and dynamic reads. + fenceBuiltinConstructorStaticRead(lowerer, expr) ?? lowerer.lowerFieldRead(expr); // A union-typed field read narrows like an identifier when the // checker has narrowed this use to one arm. @@ -754,11 +802,13 @@ export function lowerPropertyAccessExpr( if ( !expr.questionDotToken && stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis" && - isJsSourceFile(expr.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(expr)) ) { const canonical = stdlibGlobalNameOf(lowerer, expr); if (canonical !== null) { if (canonical === "globalThis") return lowerGlobalValue(lowerer, expr); + // Typed for the programs that name it, absent in Node 24. + if (canonical === TEMPORAL_GLOBAL) return dynUndefinedExpr(loc); if (canonical === "ArrayBuffer") return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc }; if (canonical === "fetch") @@ -771,7 +821,14 @@ export function lowerPropertyAccessExpr( type: DYN, loc, }; - return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc }; + return ( + builtinGlobalValueAt(lowerer, expr, canonical, loc, true) ?? { + kind: "strLit", + value: `[builtin ${canonical}]`, + type: STRING, + loc, + } + ); } } // A JS receiver whose CHECKER type has no mapping (`mustCallChecks @@ -793,7 +850,7 @@ export function lowerPropertyAccessExpr( // them, and calls dispatch through the dyn method machinery // instead. if ( - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (lowerer.mapTypeOf(lowerer.typeOf(expr.expression)) === null || (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Never) !== 0 || // A never-tainted receiver type maps (never rides as f64) but @@ -834,7 +891,7 @@ export function lowerPropertyAccessExpr( : { kind: "strIntrinsic", method: "length", receiver: recv, args: [], type: F64, loc }; } } - if (isJsSourceFile(expr.getSourceFile()) && expr.name.text === "constructor") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.name.text === "constructor") { const receiver = tryLowerExpression(lowerer, expr.expression); if ( receiver?.type.kind === "dyn" || @@ -850,6 +907,43 @@ export function lowerPropertyAccessExpr( }; } } + // A plain record's inherited Object.prototype members READ as values + // (`o.constructor`, `o.valueOf`, `({}).toString`, the isPlainObject and + // deep-equal idioms): the record boxes into its dynamic view, whose + // read answers the interned Object constructor and Object.prototype + // methods — the same values `Object` and `Object.prototype.valueOf` + // read, so identity comparisons are exact. Calls keep their own + // lowerings; a record declaring the name reads its field. + if ( + OBJECT_PROTOTYPE_VALUE_READS.has(expr.name.text) && + !isCalleeOf(expr) && + // `X.prototype.toString` keeps the builtin prototype-method lowering. + !(ts.isPropertyAccessExpression(expr.expression) && expr.expression.name.text === "prototype") + ) { + const receiver = tryLowerExpression(lowerer, expr.expression); + const name = expr.name.text; + const shape = + receiver?.type.kind === "record" ? lowerer.shapes.get(receiver.type.shapeId) : undefined; + const info = + receiver?.type.kind === "object" ? lowerer.classes.get(receiver.type.className) : undefined; + const inherits = + receiver?.type.kind === "dyn" || + (shape !== undefined && !shape.tuple && !shape.fields.some((field) => field.name === name)) || + (info !== undefined && + name !== "constructor" && + !info.fields.has(name) && + !lowerer.findMethodOn(info, name) && + !lowerer.findMethodOn(info, `get:${name}`)); + if (receiver !== null && receiver !== undefined && inherits) { + return { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(receiver, DYN), + key: { kind: "strLit", value: name, type: STRING, loc }, + type: DYN, + loc, + }; + } + } // `f.name` / `f.length` / own properties on a FUNCTION-typed JS // value (the mustCall wrapper's function-instance members): read // through the dyn box — the closure's own-property table answers @@ -861,7 +955,7 @@ export function lowerPropertyAccessExpr( // storage can expose its own name and length in both source languages. if ( !["apply", "bind", "call", "toString", "caller", "arguments"].includes(expr.name.text) && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.name.text === "name" || expr.name.text === "length" || expr.name.text === "constructor") @@ -874,7 +968,9 @@ export function lowerPropertyAccessExpr( const receiverSymbol = receiverType.getSymbol(); const checkedFunction = lowerer.checker.getCallSignatures(receiverType).length > 0 || - (receiverSymbol?.name === "Function" && lowerer.isStdlibSymbol(receiverSymbol)); + (receiverSymbol !== undefined && + ts.symbolName(receiverSymbol) === "Function" && + lowerer.isStdlibSymbol(receiverSymbol)); if (probed?.type.kind === "dyn" && checkedFunction) { const key: IrExpr = { kind: "strLit", @@ -896,7 +992,7 @@ export function lowerPropertyAccessExpr( ); } if ( - (isJsSourceFile(expr.getSourceFile()) || expr.name.text === "constructor") && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.name.text === "constructor") && probed?.type.kind === "func" && canBoxFuncIntoDyn( probed.type, @@ -930,17 +1026,17 @@ export function lowerPropertyAccessExpr( if (expr.name.text === "done" || expr.name.text === "value") { const receiverType = lowerer.typeOf(expr.expression); const recvSym = - receiverType.getAliasSymbol()?.name === "IteratorResult" + ts.symbolNameOf(receiverType.getAliasSymbol()) === "IteratorResult" ? receiverType.getAliasSymbol() : receiverType.getSymbol(); if ( - (recvSym?.name === "IteratorResult" || - recvSym?.name === "IteratorYieldResult" || - recvSym?.name === "IteratorReturnResult") && + ((recvSym !== undefined && ts.symbolName(recvSym) === "IteratorResult") || + (recvSym !== undefined && ts.symbolName(recvSym) === "IteratorYieldResult") || + (recvSym !== undefined && ts.symbolName(recvSym) === "IteratorReturnResult")) && recvSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const recv = lowerer.lowerExpr(expr.expression); @@ -991,7 +1087,8 @@ export function lowerPropertyAccessExpr( // members owned by these implemented families bypass the stdlib fence. const segmentDataSymbol = lowerer.typeOf(expr.expression).getSymbol(); const segmentDataProperty = - segmentDataSymbol?.name === "SegmentData" && + segmentDataSymbol !== undefined && + ts.symbolName(segmentDataSymbol) === "SegmentData" && lowerer.isStdlibSymbol(segmentDataSymbol) && ["segment", "index", "input", "isWordLike"].includes(expr.name.text); // os.constants members vary by platform (the declared groups list @@ -1048,6 +1145,23 @@ export function lowerPropertyAccessExpr( ); } } + // A stdlib-declared member (`message` of an interface extending Error) + // on a catch binding narrowed to a structural type: the binding reads + // as its checked-dynamic value, so the member does too. + if (!expr.questionDotToken && lowerer.caughtLocalOf(expr.expression)) { + const recv = lowerer.lowerExpr(expr.expression); + if (recv.kind === "caughtToDyn") { + const read: IrExpr = { + kind: "dynKeyGet", + key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) }, + value: recv, + type: DYN, + loc, + }; + const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); + return declared && declared.kind !== "void" ? lowerer.coerceToExpected(read, declared) : read; + } + } // The lib fence's PROPERTY chokepoint: a stdlib-declared member that // no lowering above claimed ([1,2].entries, Math.SQRT2, Promise.all, // re.exec as a value, ...) reports SC2020 here. @@ -1081,7 +1195,7 @@ export function lowerPropertyAccessExpr( // validated dynCheck as usual. if ( recvLowered.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && (["f64", "bool", "bigint", "string", "symbol"].includes(recvLowered.type.kind) || (recvLowered.type.kind === "union" && lowerer.dynConvertible(recvLowered.type)))) ) { @@ -1149,8 +1263,8 @@ export function lowerPropertyAccessExpr( recvShape && !recvShape.tuple && lowerer.implicitParamTypes !== null && - isJsSourceFile(expr.getSourceFile()) && - npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(expr).fileName) !== null ) { return { kind: "seqExpr", @@ -1239,6 +1353,29 @@ export function lowerPropertyAccessExpr( const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr)); return mapped && mapped.kind !== "void" ? lowerer.coerceInto(expr, read, mapped) : read; } + const asserted = assertedMemberRead(lowerer, expr, recvLowered, loc); + if (asserted) return asserted; + // A checker-`any` receiver whose value is a FRESH typed result of an + // earlier step in the chain (`out.split(" ").length`, where split + // extracted the string and answered a native string[]): the member read + // goes through the boxed checked-dynamic value, as the method-call twin + // does. The box copies; a call result has no other holder to observe it. + if ( + (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Any) !== 0 && + isFreshCallResult(expr.expression) && + lowerer.dynConvertible(recvLowered.type) + ) { + return lowerer.maybeNarrow( + { + kind: "dynKeyGet", + value: lowerer.coerceToExpected(recvLowered, DYN), + key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, + type: DYN, + loc, + }, + expr, + ); + } lowerer.unsupported( "SC1090", expr, @@ -1246,6 +1383,108 @@ export function lowerPropertyAccessExpr( ); } +/** A call expression, through parentheses: its value has no other holder. */ +function isFreshCallResult(node: ts.Expression): boolean { + let e = node; + while (ts.isParenthesizedExpression(e)) e = e.expression; + return ts.isCallExpression(e); +} + +/** `(source as { repoName: string }).repoName` where the assertion names a + * member some arms of the stored union of records do not declare: Node + * reads the property off whichever object it is, so arms that declare it + * answer their slot, and the others answer undefined (native records are + * closed, so they cannot hold it). The result keeps that undefined arm + * whatever the assertion claims; a consumer that needs the asserted type + * checks it like any other narrowing. The arms that declare the member + * must agree on its type. Null when the receiver is not such an + * assertion over records, or no arm declares the member. */ +function assertedMemberRead( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, + recv: IrExpr, + loc: SrcLoc, +): IrExpr | null { + let receiverNode: ts.Expression = expr.expression; + while (ts.isParenthesizedExpression(receiverNode)) receiverNode = receiverNode.expression; + if (!ts.isAsExpression(receiverNode) && !ts.isTypeAssertion(receiverNode)) return null; + if (recv.type.kind !== "union") return null; + const unionId = recv.type.unionId; + const arms = lowerer.unions.get(unionId)?.arms ?? []; + const name = expr.name.text; + let resultType: IrType | null = null; + let records = 0; + const declaring: { tag: number; shapeId: string; type: IrType }[] = []; + const units: number[] = []; + for (let tag = 0; tag < arms.length; tag++) { + const arm = arms[tag]!; + if (isUnitType(arm)) { + units.push(tag); + continue; + } + if (arm.kind !== "record") return null; + records++; + const shape = lowerer.shapes.get(arm.shapeId); + if (!shape || shape.tuple || shape.indexValue) return null; + const field = shape.fields.find((f) => f.name === name); + if (!field) continue; + // The declaring arms agree up to an undefined arm (`string` beside + // `string | undefined`). + const armResult = lowerer.withUndefinedArmOf(field.type); + if (!armResult || (resultType && !typeEquals(resultType, armResult))) return null; + resultType = armResult; + declaring.push({ tag, shapeId: arm.shapeId, type: field.type }); + } + if (!resultType || declaring.length === records) return null; + const stable = lowerer.declareHiddenLocal("%assertedRecv", recv.type); + const ref = (): IrExpr => ({ kind: "varRef", localId: stable.id, type: recv.type, loc }); + let result: IrExpr | null = lowerer.wrappedUndefined(resultType, loc); + if (!result) return null; + // A unit arm the checker narrowed away reads like Node would read it. + for (const tag of units) { + const unit = arms[tag]!.kind === "nullT" ? "null" : "undefined"; + result = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, negated: false, value: ref(), type: BOOL, loc }, + then: nodeThrowExpr( + 1, + "", + `Cannot read properties of ${unit} (reading '${name}')`, + resultType, + loc, + ), + else_: result, + type: resultType, + loc, + }; + } + for (const { tag, shapeId, type: fieldType } of declaring) { + const read: IrExpr = { + kind: "recordGet", + obj: { kind: "unionNarrow", unionId, tag, value: ref(), type: arms[tag]!, loc }, + shapeId, + field: name, + type: fieldType, + loc, + }; + result = { + kind: "ternary", + cond: { kind: "unionIsTag", unionId, tag, negated: false, value: ref(), type: BOOL, loc }, + then: lowerer.coerceToExpected(read, resultType), + else_: result, + type: resultType, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: stable.id, init: recv, loc }], + result, + type: resultType, + loc, + }; +} + function importMetaProperty(lowerer: Lowerer, expr: ts.PropertyAccessExpression): IrExpr | null { return isImportMeta(expr.expression) ? importMetaField(lowerer, expr, expr.name.text) : null; } @@ -1272,7 +1511,12 @@ export function lowerIntrinsicProperty( | "BigInt" )[]) { if (!lowerer.chainBlocked(expr) && lowerer.isStdlibGlobal(expr, name)) { - return primitiveCtorClosure(lowerer, name, locOf(expr), isJsSourceFile(expr.getSourceFile())); + return primitiveCtorClosure( + lowerer, + name, + locOf(expr), + isJsSourceFile(ts.getSourceFileOfNode(expr)), + ); } } if (lowerer.stdlibGlobalMember(expr, "Reflect") === "ownKeys") { @@ -1289,6 +1533,13 @@ export function lowerIntrinsicProperty( return { kind: "dynFrom", value, fnName: "ownKeys", type: DYN, loc: locOf(expr) }; } if (lowerer.chainBlocked(expr)) return null; + // Object.prototype.constructor: the Object constructor value. + if ( + expr.name.text === "constructor" && + ts.isPropertyAccessExpression(expr.expression) && + lowerer.stdlibGlobalMember(expr.expression, "Object") === "prototype" + ) + return builtinGlobalValue("Object", locOf(expr)); const builtinPrototype = builtinPrototypeMethod(lowerer, expr); if (builtinPrototype) return { @@ -1443,7 +1694,7 @@ export function lowerIntrinsicProperty( ) return null; const name = expr.name.text; - if (kind === "array" && name === "constructor" && isJsSourceFile(expr.getSourceFile())) { + if (kind === "array" && name === "constructor" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const receiver = lowerer.lowerExpr(expr.expression); if (receiver.type.kind === "dyn") return { @@ -1471,7 +1722,7 @@ export function lowerIntrinsicProperty( const strictReceiver = (expected: IrType): IrExpr => { const value = lowerer.lowerExpr(expr.expression); if (value.type.kind === "dyn") { - if (isJsSourceFile(expr.getSourceFile()) && name === "length") return value; + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && name === "length") return value; return lowerer.coerceInto(expr.expression, value, expected); } return lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, expected, name) ?? value; @@ -1991,7 +2242,7 @@ function lowerReplacedExportsRead( if (expr.questionDotToken) return null; const recv = expr.expression; if (!ts.isIdentifier(recv) || recv.text !== "exports") return null; - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); if (!isJsSourceFile(sf) || isNodeEsmFile(sf, lowerer.program)) return null; if (lowerer.peekLocal(recv) || lowerer.globalOf(recv)) return null; // a user binding shadows // Write position is the export-assignment machinery's territory. @@ -2028,7 +2279,7 @@ function lowerCjsExportTableThisMember( expr: ts.PropertyAccessExpression, ): IrExpr | null { if (expr.expression.kind !== ts.SyntaxKind.ThisKeyword || expr.questionDotToken) return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; // The enclosing accessor, crossing only arrow boundaries (arrows // inherit `this`; any other function form rebinds it). let n: ts.Node | undefined = expr.parent; @@ -2228,7 +2479,8 @@ function lowerMatchGroupsRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressio const groups = matchResultNamedGroupsOf(lowerer, expr.expression); // An untraced regex: TypeScript reaches the member fence; JavaScript // would otherwise read the slice's absent expando as undefined. - if (groups === null && isJsSourceFile(expr.getSourceFile())) lowerer.stdlibMemberFence(expr); + if (groups === null && isJsSourceFile(ts.getSourceFileOfNode(expr))) + lowerer.stdlibMemberFence(expr); if (groups === null) return null; if (groups.length === 0) { // Node: a match result of a group-less regex answers undefined — @@ -2245,7 +2497,7 @@ function lowerMatchGroupsRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressio } const recv = lowerer.lowerExpr(expr.expression); // A JavaScript callback parameter holds the row as a dynamic array. - const dynRow = recv.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()); + const dynRow = recv.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)); if (!dynRow && !isMatchSliceType(lowerer, recv.type)) return null; return lowerGroupsProjection(lowerer, recv, groups, loc); } @@ -2568,6 +2820,27 @@ export function lowerFieldRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressi loc: locOf(expr), }; } + // A member the checker declares on the receiver, stored in a pure + // index-signature record (an object literal that spread one, see + // dictSpreadLiteralShape): the key is always present, so the keyed + // read answers the member through the slot type. + if ( + actual?.type.kind === "record" && + shape.fields.length === 0 && + declaredMemberOf(lowerer, expr.expression, expr.name.text) + ) { + return lowerer.maybeNarrow( + { + kind: "recordKeyGet", + obj: actual, + shapeId: actual.type.shapeId, + key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) }, + type: shape.indexValue, + loc: locOf(expr), + }, + expr, + ); + } lowerer.unsupported( "SC1090", expr, @@ -2801,7 +3074,7 @@ export function lowerUnionProperty( // instance argument (e.g. Matrix4.makeTranslation). Preserve primitive // missing-property semantics and dispatch class data through its native // capsule instead of demanding a field shared by every union arm. - if (isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(value.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(value.type)) { return { kind: "dynKeyGet", value: lowerer.coerceToExpected(value, DYN), @@ -2953,7 +3226,7 @@ export function classFieldTarget( const represented = saved ?? lowerer.lowerExpr(receiverNode); if ( represented.type.kind === "dyn" && - isJsSourceFile(receiverNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(receiverNode)) && !fieldName.startsWith("#") ) { return { container: "dynamic", obj: represented, field: fieldName, fieldType: DYN }; @@ -3000,7 +3273,7 @@ export function classFieldTarget( if (fieldType) { const obj = lowerObjectReceiver(); if ( - isJsSourceFile(receiverNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(receiverNode)) && !fieldName.startsWith("#") && [...lowerer.classes.values()].some( (candidate) => @@ -3038,7 +3311,7 @@ export function classFieldTarget( fieldType: getF ? getF.sig.ret : setF!.sig.params[0]!.type, }; } - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const property = lowerer.checker.getPropertyOfType( lowerer.checker.getTypeAtLocation(receiverNode), fieldName, @@ -3087,6 +3360,22 @@ export function fieldTarget( fieldType: F64, }; } + // `this.#x` in a function-local class's prototype method: the field of + // the brand-checked receiver (checkedPrivateReceiver). + if ( + ts.isPrivateIdentifier(access.name) && + access.expression.kind === ts.SyntaxKind.ThisKeyword && + lowerer.resolveThis()?.type.kind === "dyn" + ) { + const owner = localPrivateBrandClass(lowerer, access.name); + if (owner) { + const ownerType: IrType & { kind: "object" } = { + kind: "object", + className: owner.info.def.name, + }; + return classFieldTarget(lowerer, access.expression, ownerType, access.name.text); + } + } const stored = ts.isIdentifier(access.expression) ? (lowerer.peekLocal(access.expression)?.type ?? lowerer.globalOf(access.expression)?.type) : undefined; @@ -3241,7 +3530,7 @@ export function fieldTarget( : [declared]; return declaredArms.every((a) => ivArms.some((b) => typeEquals(a, b))); }; - if (!nameSym || nameSym.name === ts.InternalSymbolName.Index || canonicalized()) { + if (!nameSym || ts.symbolName(nameSym) === ts.InternalSymbolName.Index || canonicalized()) { let obj = lowerer.lowerExpr(access.expression); if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) { const present = lowerer.stripUndefinedArm(obj.type); @@ -3298,6 +3587,7 @@ export function fieldGetExpr( ); if (checked) target = { ...target, obj: checked }; } + if (target.container === "recordKeyed") return keyedFieldRead(lowerer, target, loc); if (target.container === "dynamic") return lowerer.coerceInto( blame, @@ -3352,7 +3642,7 @@ export function fieldGetExpr( } if ( !target.field.startsWith("#") && - isJsSourceFile(blame.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(blame)) && lowerer.dynConvertible(getF.sig.ret) ) { const value: IrExpr = { @@ -3525,6 +3815,225 @@ function lowerStreamObjectProperty( return lowerStreamProperty(lowerer, expr, info); } +/** The builtin constructors JavaScript sources read as real function + * values rather than identity tokens: the runtime interns one function per + * constructor, and dynamic values answer `.constructor` with the same one, + * so `o.constructor === Object` is an exact identity test. */ +const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([ + "Object", + "Function", + "Date", + "RegExp", + "Map", + "Set", + "Promise", + "WeakMap", + "WeakSet", +]); + +/** The builtin global functions JavaScript sources read as interned + * function values (the `cond ? setImmediate : process.nextTick` feature + * probe of bcryptjs and async libraries): typeof answers "function", + * identity holds across spellings, and calls run the timer APIs. */ +const GLOBAL_FUNCTION_VALUES: ReadonlySet = new Set([ + "setTimeout", + "setInterval", + "setImmediate", + "clearTimeout", + "clearInterval", + "clearImmediate", + "queueMicrotask", +]); + +/** A builtin function value: one interned runtime function per name. */ +export function builtinFunctionValue(name: string, loc: SrcLoc): IrExpr { + return { + kind: "libCall", + fn: "dyn.builtinFunction", + args: [{ kind: "strLit", value: name, type: STRING, loc }], + type: DYN, + loc, + }; +} + +/** The value a source reads for the stdlib global `canonical` (builtin + * constructors in both languages, builtin functions in JavaScript), or + * null when it stays an identity token (JavaScript) or fenced. */ +export function builtinGlobalValue(canonical: string, loc: SrcLoc, js = false): IrExpr | null { + if (js && GLOBAL_FUNCTION_VALUES.has(canonical)) return builtinFunctionValue(canonical, loc); + if (GLOBAL_CONSTRUCTOR_VALUES.has(canonical)) { + return { + kind: "libCall", + fn: "dyn.globalConstructor", + args: [{ kind: "strLit", value: canonical, type: STRING, loc }], + type: DYN, + loc, + }; + } + return null; +} + +/** The members the interned builtin function values model (the runtime + * answers each one as Node does); any other member read through them would + * answer undefined, so it keeps its static lowering or its fence. */ +const BUILTIN_VALUE_MEMBERS: ReadonlySet = new Set(["name", "length", "constructor"]); + +/** The Object statics whose answers for an interned builtin function value + * match Node's (identity, and the empty enumerable own-key lists). */ +const BUILTIN_VALUE_REFLECTION: ReadonlySet = new Set(["is", "keys", "values", "entries"]); + +/** The value `builtinGlobalValue` gives the stdlib global `expr` read at its + * use site: null when the use reads the global's members (they keep their + * lowerings and fences), and SC2020 when it is the target of reflection or + * redefinition (`Object.hasOwn(Object, "hasOwn")`, + * `Object.defineProperty(Promise, ...)`, `hasOwnProperty.call(Map, ...)`), + * which would observe or change members the interned value does not + * model. */ +export function builtinGlobalValueAt( + lowerer: Lowerer, + expr: ts.Expression, + canonical: string, + loc: SrcLoc, + js: boolean, +): IrExpr | null { + if (readsBuiltinGlobalMembers(expr)) return null; + const value = builtinGlobalValue(canonical, loc, js); + if (value === null) return null; + let node: ts.Node = expr; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const call = node.parent; + if ( + call !== undefined && + ts.isCallExpression(call) && + call.expression !== node && + ts.isPropertyAccessExpression(call.expression) + ) { + const callee = call.expression; + const owner = stdlibGlobalNameOf(lowerer, callee.expression); + const reflects = + owner === "Reflect" || + (owner === "Object" && !BUILTIN_VALUE_REFLECTION.has(callee.name.text)) || + ((callee.name.text === "call" || callee.name.text === "apply") && + call.arguments.nodes[0] === node && + lowerer.isStdlibSymbol( + lowerer.checker.getSymbolAtLocation( + ts.isPropertyAccessExpression(callee.expression) + ? callee.expression.name + : callee.expression, + ), + )); + if (reflects) { + const name = + owner !== null + ? `${owner}.${callee.name.text}` + : ts.getTextOfNode(callee).replace(/\s+/g, ""); + lowerer.noLowering( + `${name}(${canonical}, ...)`, + expr, + "builtin constructor and function values model only name, length and constructor, so reflecting on or redefining their members has no lowering", + ); + } + } + return value; +} + +/** Whether the stdlib global `expr` is read for its members (`Map.prototype`, + * `Date["UTC"]`, `"prototype" in Map`) rather than as a value. Those reads + * keep the global's member lowerings and fences: its interned function value + * models only BUILTIN_VALUE_MEMBERS. */ +function readsBuiltinGlobalMembers(expr: ts.Expression): boolean { + let node: ts.Node = expr; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + if (parent === undefined) return false; + if (ts.isPropertyAccessExpression(parent) && parent.expression === node) + return !BUILTIN_VALUE_MEMBERS.has(parent.name.text); + if (ts.isElementAccessExpression(parent) && parent.expression === node) return true; + return ( + ts.isBinaryExpression(parent) && + parent.operatorToken.kind === ts.SyntaxKind.InKeyword && + parent.right === node + ); +} + +/** Reports SC2020 when `access` reads a stdlib static member or the + * prototype of a builtin constructor with an interned function value through + * a receiver the checker types as the constructor's static side + * (`MapConstructor`); null otherwise. Calls that lower statically never + * reach the member read. */ +function fenceBuiltinConstructorStaticRead( + lowerer: Lowerer, + access: ts.PropertyAccessExpression, +): null { + if (BUILTIN_VALUE_MEMBERS.has(access.name.text)) return null; + const symbol = lowerer.typeOf(access.expression).getSymbol(); + if ( + symbol !== undefined && + ts.symbolName(symbol).endsWith("Constructor") && + GLOBAL_CONSTRUCTOR_VALUES.has(ts.symbolName(symbol).slice(0, -"Constructor".length)) && + lowerer.isStdlibSymbol(symbol) + ) + lowerer.stdlibMemberFence(access); + return null; +} + +/** Object.prototype members a plain record inherits, read as values. */ +const OBJECT_PROTOTYPE_VALUE_READS: ReadonlySet = new Set([ + "constructor", + "valueOf", + "toString", + "hasOwnProperty", + "propertyIsEnumerable", +]); + +/** Whether `expr` is the callee of a call (`o.toString()`), which keeps its + * call lowering rather than reading the member as a value. */ +function isCalleeOf(expr: ts.PropertyAccessExpression): boolean { + let node: ts.Node = expr; + while (node.parent !== undefined && ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + return ( + parent !== undefined && + (ts.isCallExpression(parent) || ts.isNewExpression(parent)) && + parent.expression === node + ); +} + +/** The constructor a primitive dynamic value answers for `.constructor`: + * the same interned closure the `Number`/`String`/`Boolean`/`BigInt` + * identifiers read (dequal's `(ctor = foo.constructor) === bar.constructor` + * over numbers), or `otherwise` for non-primitives. `value` must be safe to + * read repeatedly. */ +export function primitiveConstructorOr( + lowerer: Lowerer, + value: IrExpr, + otherwise: IrExpr, + loc: SrcLoc, +): IrExpr { + let result = otherwise; + for (const [test, name] of [ + ["bigint", "BigInt"], + ["boolean", "Boolean"], + ["string", "String"], + ["number", "Number"], + ] as const) { + result = { + kind: "ternary", + cond: { kind: "dynTest", test, value, type: BOOL, loc }, + then: { + kind: "dynFrom", + value: primitiveCtorClosure(lowerer, name, loc, true), + type: DYN, + loc, + }, + else_: result, + type: DYN, + loc, + }; + } + return result; +} + /** 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 @@ -3538,7 +4047,7 @@ function jsFunctionExpandoMember(lowerer: Lowerer, access: ts.PropertyAccessExpr return ( decls.length > 0 && decls.every((d) => { - if (!isJsSourceFile(d.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(d))) return false; const assign = ts.isBinaryExpression(d) ? d : d.parent; return ( assign !== undefined && @@ -3549,3 +4058,10 @@ function jsFunctionExpandoMember(lowerer: Lowerer, access: ts.PropertyAccessExpr }) ); } + +/** Whether the checker declares `name` as a property of `receiver`'s type + * (as opposed to reading it through an index signature). */ +export function declaredMemberOf(lowerer: Lowerer, receiver: ts.Expression, name: string): boolean { + const symbol = lowerer.checker.getPropertyOfType(lowerer.typeOf(receiver), name); + return !!symbol && symbol.declarations.length > 0; +} diff --git a/packages/compiler/src/frontend/lowering/expressions/relational.ts b/packages/compiler/src/frontend/lowering/expressions/relational.ts index 55baa8014..f8b8c7604 100644 --- a/packages/compiler/src/frontend/lowering/expressions/relational.ts +++ b/packages/compiler/src/frontend/lowering/expressions/relational.ts @@ -1,5 +1,12 @@ /* instanceof and in, including private-name and runtime-key in. */ import * as ts from "../../ts7/adapter.js"; +import { builtinClassInstanceOf, builtinClassKind } from "../builtins/builtin-class-dyn.js"; +import { + checkedPrivateIn, + localPrivateBrandClass, + primitiveOperand, + privateInMessage, +} from "../private-brands.js"; import type { Lowerer } from "../lowerer.js"; import { BOOL, @@ -17,20 +24,22 @@ import { UNDEFINED_T, isUnitType, typeEquals, + typeKey, } from "../../../ir/ir.js"; -import { refuseWebAssemblyUse } from "../builtins/webassembly.js"; +import { lowerWebAssemblyInstanceof, refuseWebAssemblyUse } from "../builtins/webassembly.js"; import { lowerSocketInstanceOf } from "../lower-server.js"; import { builtinClassRefOf } from "../builtins/class-values.js"; import { lowerZlibInstanceOf } from "../builtins/compression.js"; -import { varRef } from "../../../ir/build.js"; +import { numLit, strLit, varRef } from "../../../ir/build.js"; import { lowerDisposableStackInstanceOf } from "../builtins/disposable-stack.js"; import { lowerBlobInstanceof } from "../builtins/blob.js"; +import { lowerReadlineInstanceof } from "../builtins/readline.js"; import { lowerKeyObjectInstanceof } from "../builtins/key-object.js"; import { lowerCryptoKeyInstanceof } from "../builtins/webcrypto.js"; import { lowerNetAddressInstanceof } from "../builtins/net-address.js"; import { fenceEarlyNsMemberRef, nsMemberIdentOf } from "../lower-namespaces.js"; import { maybePromiseInstanceOf } from "../maybe-promise.js"; -import { isSafeToDiscard } from "./evaluation-safety.js"; +import { isSafeToDiscard, isSafeToRepeat } from "./evaluation-safety.js"; import { type ClassInfo, storedClassValueType } from "../lower-classes.js"; import { classInstanceOf } from "../class-dynamic-dispatch.js"; import { InternalCompilerError } from "../../../errors.js"; @@ -54,6 +63,8 @@ import { nullProtoRecordShape, recordNullProtoTest } from "../null-prototype.js" * Folding is limited to side-effect-free operands — dropping a computed * operand would skip its effects, so those are rejected instead. */ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc: SrcLoc): IrExpr { + const webAssembly = lowerWebAssemblyInstanceof(lowerer, expr); + if (webAssembly !== null) return webAssembly; refuseWebAssemblyUse(lowerer, expr.right); // `x instanceof net.Socket` over a union with a netSocket arm — the // h2 compat 'connect' narrowing (lower-server.ts): a union tag test. @@ -172,6 +183,8 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc if (cryptoKey) return cryptoKey; const netAddress = lowerNetAddressInstanceof(lowerer, expr); if (netAddress) return netAddress; + const readlineInterface = lowerReadlineInstanceof(lowerer, expr); + if (readlineInterface) return readlineInterface; } if (lowerer.isStdlibGlobal(expr.right, "SharedArrayBuffer")) { const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); @@ -457,10 +470,26 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc ); } const constructor = tryLowerExpression(lowerer, expr.right); - if (constructor?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (constructor?.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); return checkedClassInstanceOf(lowerer, value, constructor, loc); } + // `this instanceof Glob` in a JavaScript constructor function: the + // function's prototype against the value's prototype chain. + if ( + constructor?.type.kind === "func" && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + lowerer.dynConvertible(constructor.type) + ) { + const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN); + return { + kind: "libCall", + fn: "dyn.functionInstanceOf", + args: [value, { kind: "dynFrom", value: constructor, type: DYN, loc }], + type: BOOL, + loc, + }; + } lowerer.unsupported( "SC1090", expr.right, @@ -569,6 +598,14 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc loc, }; } + // `u instanceof EventEmitter` (or a stream class): an untyped object + // built by a constructor function inherits the untyped constructor's + // prototype (builtin-class-dyn.ts). Compiled emitters and streams have + // no untyped form, so the prototype chain answers for every value. + const builtinKind = builtinClassKind(target.def.name); + if (left.type.kind === "dyn" && builtinKind !== null) { + return builtinClassInstanceOf(left, builtinKind, loc); + } if (left.type.kind === "dyn") { lowerer.unsupported( "SC1090", @@ -1038,6 +1075,80 @@ function lowerPrivateIn( ); } } + // A checked value in JavaScript (`#x in this` inside a method, whose + // receiver is the actual call-site value): the brand is present exactly + // when the value is a class instance at or below the declarer. A typed + // reference carries the class it was boxed under: the declarer or a + // descendant answers true; an ancestor answers per instance. + if ( + recv.type.kind === "dyn" && + recv.kind === "varRef" && + isJsSourceFile(ts.getSourceFileOfNode(expr)) + ) { + // A function-local class has one brand per evaluation. + if (info.localClass) { + const owner = localPrivateBrandClass(lowerer, priv); + if (!owner) { + lowerer.unsupported( + "SC1090", + expr, + `'${pname} in …' inside a function-local class that is not a named declaration`, + ); + } + return checkedPrivateIn(lowerer, owner.info, owner.decl, pname, recv, loc); + } + const typedRefIs = (className: string): IrExpr => ({ + kind: "libCall", + fn: "dyn.typedRefIs", + args: [ + recv, + { kind: "strLit", value: typeKey({ kind: "object", className }), type: STRING, loc }, + ], + type: BOOL, + loc, + }); + const or = (left: IrExpr | null, right: IrExpr): IrExpr => + left === null ? right : { kind: "logical", op: "||", left, right, type: BOOL, loc }; + let test: IrExpr | null = null; + const below = (c: ClassInfo): void => { + test = or(test, typedRefIs(c.def.name)); + for (const sub of c.subclasses) below(sub); + }; + below(info); + for (let base = info.base; base; base = base.base) { + const baseType: IrType = { kind: "object", className: base.def.name }; + test = or(test, { + kind: "logical", + op: "&&", + left: typedRefIs(base.def.name), + right: { + kind: "instanceOf", + value: { kind: "dynCheck", value: recv, type: baseType, loc }, + className: declName, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }); + } + // Only objects carry private names: a primitive operand throws Node's + // TypeError. + return { + kind: "ternary", + cond: primitiveOperand(recv, loc), + then: { + kind: "libCall", + fn: "error.nodeThrow", + args: [numLit(1, loc), strLit("", loc), privateInMessage(pname, recv, loc)], + type: BOOL, + loc, + }, + else_: test!, + type: BOOL, + loc, + }; + } if (recv.type.kind !== "object") { lowerer.unsupported( "SC1090", @@ -1064,6 +1175,24 @@ function lowerPrivateIn( } /** The string keys every record inherits from Object.prototype. */ +/** Keys built-in errors carry outside their class fields: Node's system + * error properties, and AggregateError's, SuppressedError's and + * AssertionError's own members. */ +const BUILTIN_ERROR_SLOT_KEYS: ReadonlySet = new Set([ + "errno", + "syscall", + "path", + "dest", + "info", + "errors", + "error", + "suppressed", + "actual", + "expected", + "operator", + "generatedMessage", +]); + const OBJECT_PROTOTYPE_KEYS: ReadonlySet = new Set([ "constructor", "__defineGetter__", @@ -1256,7 +1385,7 @@ export function lowerInExpression( recv.type.kind === "classval" ? lowerer.classes.get(recv.type.className) : undefined; if ( (classInfo && (classInfo.callableBase || hasRuntimeStatics(classInfo))) || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "func" && lowerer.dynConvertible(recv.type)) ) { @@ -1280,9 +1409,10 @@ export function lowerInExpression( // predicate's `err instanceof Error && "code" in err` shape): `code` // answers from the runtime error's code slot (stamped by fs/system/ // event/spawn errors, absent on plain constructions — exactly Node's - // own-property answer), and the always-declared members fold true. - // Everything else fences: the runtime object carries no other dynamic - // properties to ask (`stack` would be a lie — Node has one, we don't). + // own-property answer), and the always-present members (message, name, + // stack, Object.prototype's keys, members the static class declares) + // fold true. Other keys test the declaring subclasses and the error's + // own runtime properties; keys of built-in errors' extra slots fence. if (recv.type.kind === "object") { // %DOMException first: `'cause' in e` answers the options form's // own-property record (the runtime slot), and code/name/message are @@ -1304,34 +1434,77 @@ export function lowerInExpression( if (key === "cause") { return { kind: "libCall", fn: "error.hasCause", args: [recv], type: BOOL, loc }; } + // The code slot, or an own `code` with another value + // (`err.code = undefined` is present). if (key === "code") { - const codeType = lowerer.envValueType(); - if (codeType.kind !== "union") - throw new InternalCompilerError("lowerer bug: error code type is not a union"); - const undefTag = lowerer.armTag(codeType.unionId, UNDEFINED_T); - const read: IrExpr = { + return { kind: "libCall", fn: "error.hasCode", args: [recv], type: BOOL, loc }; + } + // The receiver evaluates for its effects when the answer is static. + const always = (): IrExpr => + recv.kind === "varRef" || recv.kind === "caughtNarrow" + ? { kind: "boolLit", value: true, type: BOOL, loc } + : { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: recv, loc }], + result: { kind: "boolLit", value: true, type: BOOL, loc }, + type: BOOL, + loc, + }; + if (key === "message" || key === "name") return always(); + // Every Error has its own `stack`, and Error.prototype's `toString` + // and `constructor` are among Object.prototype's keys. + if (key === "stack" || OBJECT_PROTOTYPE_KEYS.has(key)) return always(); + // A member the static class declares is always present. + const declares = (c: ClassInfo): boolean => + c.fields.has(key) || + lowerer.findMethodOn(c, key) !== null || + lowerer.findMethodOn(c, `get:${key}`) !== null || + lowerer.findMethodOn(c, `set:${key}`) !== null; + const staticInfo = lowerer.classes.get(recv.type.className); + if (staticInfo && declares(staticInfo)) return always(); + // Any other key: a descendant class that declares it (instanceof), or + // an own property set at runtime (a throw site's extra fields, + // Object.assign, writes through a dynamic view). Keys built-in errors + // keep outside their class fields depend on the runtime error and + // stay fenced. + if (!BUILTIN_ERROR_SLOT_KEYS.has(key)) { + const stable = recv.kind === "varRef" || recv.kind === "caughtNarrow"; + const slot = stable ? null : lowerer.declareHiddenLocal("%inRecv", recv.type); + const ref = (): IrExpr => (slot ? varRef(slot.id, recv.type, loc) : recv); + const tests: IrExpr[] = []; + const staticName = recv.type.className; + for (const c of lowerer.classes.values()) { + const name = c.def.name; + if (name === staticName || !lowerer.isSubclassOf(name, staticName) || !declares(c)) + continue; + // A declaring ancestor below the static class already covers it. + if (c.base && c.base.def.name !== staticName && declares(c.base)) continue; + tests.push({ kind: "instanceOf", value: ref(), className: name, type: BOOL, loc }); + } + tests.push({ kind: "libCall", - fn: "error.code", - args: [recv], - type: codeType, + fn: "error.hasKey", + args: [ref(), { kind: "strLit", value: key, type: STRING, loc }], + type: BOOL, loc, - }; + }); + const answer = tests.reduce((acc, test) => ({ + kind: "logical", + op: "||", + left: acc, + right: test, + type: BOOL, + loc, + })); + if (!slot) return answer; return { - kind: "unionIsTag", - unionId: codeType.unionId, - tag: undefTag, - negated: true, - value: read, + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: slot.id, init: recv, loc }], + result: answer, type: BOOL, loc, }; } - if ( - (key === "message" || key === "name") && - (recv.kind === "varRef" || recv.kind === "caughtNarrow") - ) { - return { kind: "boolLit", value: true, type: BOOL, loc }; - } lowerer.unsupported( "SC1090", expr, @@ -1349,13 +1522,14 @@ export function lowerInExpression( // arm answers membership STATICALLY (a declared non-optional field is // always present, an undeclared name never is), so the whole test // collapses to runtime TAG tests — tsc's own narrowing then types the - // branches (the discriminating-`in` idiom). Optional (undefined-armed) - // fields and tuple/index-signature arms answer per VALUE, not per arm - // — those unions keep the fence below. + // branches (the discriminating-`in` idiom). An arm declaring the key + // as an optional (undefined-armed) slot answers per VALUE instead: + // its tag test joins that payload's presence test (see + // perValueUnionIn). Tuple/index-signature arms keep the fence below. if (recv.type.kind === "union") { const unionId = recv.type.unionId; const arms = lowerer.unions.get(unionId)?.arms ?? []; - const answers: { tag: number; has: boolean }[] = []; + const answers: { tag: number; has: boolean | "slot"; arm: IrType }[] = []; let staticAnswers = arms.length > 0; for (const arm of arms) { const tag = lowerer.armTag(unionId, arm); @@ -1372,14 +1546,17 @@ export function lowerInExpression( f.type.kind === "union" && lowerer.armTag(f.type.unionId, UNDEFINED_T) >= 0 ) { - staticAnswers = false; // an optional slot: presence is per-value - break; + answers.push({ tag, has: "slot", arm }); // presence is per-value + continue; } // Accessor properties are own properties to `in` (Node answers // true without invoking the getter) — either slot present makes // the name a member. const acc = shape.fields.some((x) => x.name === `%get:${key}` || x.name === `%set:${key}`); - answers.push({ tag, has: f !== undefined || acc || inherited }); + answers.push({ tag, has: f !== undefined || acc || inherited, arm }); + } + if (staticAnswers && answers.some((a) => a.has === "slot")) { + return perValueUnionIn(lowerer, recv, unionId, key, answers, loc); } if (staticAnswers) { const pureRecv = @@ -1531,6 +1708,75 @@ export function lowerInExpression( ); } +/** `"k" in u` over a union of fixed record shapes where some arm declares + * `k` as an optional slot. Arms without the name answer false and arms + * with an always-present member answer true, both by tag; an optional + * slot answers its payload's own-property presence (an explicit + * undefined is present, an omitted or deleted property is not), read + * through the checked arm extraction the tag test has just proven. The + * receiver evaluates once: a non-repeatable one is stabilized in a + * hidden local first. */ +function perValueUnionIn( + lowerer: Lowerer, + recv: IrExpr, + unionId: string, + key: string, + answers: readonly { tag: number; has: boolean | "slot"; arm: IrType }[], + loc: SrcLoc, +): IrExpr { + const recvType = recv.type; + const stable = isSafeToRepeat(recv) ? null : lowerer.declareHiddenLocal("%inRecv", recvType); + const ref = (): IrExpr => (stable ? varRef(stable.id, recvType, loc) : recv); + const isTag = (tag: number): IrExpr => ({ + kind: "unionIsTag", + unionId, + tag, + negated: false, + value: ref(), + type: BOOL, + loc, + }); + let out: IrExpr | null = null; + for (const a of answers) { + if (a.has === false) continue; + let test = isTag(a.tag); + if (a.has === "slot" && a.arm.kind === "record") { + test = { + kind: "logical", + op: "&&", + left: test, + right: { + kind: "recordHas", + obj: { + kind: "unionNarrow", + unionId, + tag: a.tag, + value: ref(), + type: a.arm, + loc, + }, + shapeId: a.arm.shapeId, + field: key, + type: BOOL, + loc, + }, + type: BOOL, + loc, + }; + } + out = out ? { kind: "logical", op: "||", left: out, right: test, type: BOOL, loc } : test; + } + const result: IrExpr = out ?? { kind: "boolLit", value: false, type: BOOL, loc }; + if (!stable) return result; + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: stable.id, init: recv, loc }], + result, + type: BOOL, + loc, + }; +} + /** The runtime-key `in` (see lowerInExpression): `k in r` where k is a * runtime string and r an index-signature record — an interned * `%rec.haskey.(k, r)` walks the declared names (a string-equality diff --git a/packages/compiler/src/frontend/lowering/expressions/typeof.ts b/packages/compiler/src/frontend/lowering/expressions/typeof.ts index 30d24e22a..46005e228 100644 --- a/packages/compiler/src/frontend/lowering/expressions/typeof.ts +++ b/packages/compiler/src/frontend/lowering/expressions/typeof.ts @@ -13,6 +13,7 @@ import { } from "../../../ir/ir.js"; import { lowerWebAssemblyTypeof } from "../builtins/webassembly.js"; import { isNavigatorGlobal } from "../builtins/navigator.js"; +import { processHasIpc } from "../builtins/process.js"; import { lowerPerfHooksTypeof } from "../builtins/performance.js"; import { isNodeModuleValue } from "../lower-node-module.js"; import { builtinClassRefOf } from "../builtins/class-values.js"; @@ -27,19 +28,54 @@ import { typeofAnswer, } from "../lower-exprs.js"; import { tryLowerExpression } from "./try-lower-expression.js"; -import { isJsSourceFile } from "../../program.js"; +import { isJsSourceFile, isNodeEsmFile } from "../../program.js"; import { isSafeToDiscard, isSafeToRepeat } from "./evaluation-safety.js"; import { varRef } from "../../../ir/build.js"; import { isExecExceptionType, refuseExecExceptionCode } from "../builtins/errors.js"; import { InternalCompilerError } from "../../../errors.js"; +import { TEMPORAL_GLOBAL } from "../../builtin-modules.js"; +import { isTemporalGlobalRoot } from "../lower-namespaces.js"; export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc: SrcLoc): IrExpr { const webAssembly = lowerWebAssemblyTypeof(lowerer, expr.expression); if (webAssembly) return webAssembly; if (lowerer.isStdlibGlobal(expr.expression, "process")) return { kind: "strLit", value: "object", type: STRING, loc }; + { + let operand = expr.expression; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + const ipcMember = + ts.isPropertyAccessExpression(operand) && !operand.questionDotToken + ? lowerer.stdlibGlobalMember(operand, "process") + : null; + if (ipcMember === "send" || ipcMember === "disconnect") { + return { + kind: "ternary", + cond: processHasIpc(lowerer, loc), + then: { kind: "strLit", value: "function", type: STRING, loc }, + else_: { kind: "strLit", value: "undefined", type: STRING, loc }, + type: STRING, + loc, + }; + } + } if (lowerer.isStdlibGlobal(expr.expression, "fetch")) return { kind: "strLit", value: "function", type: STRING, loc }; + // `__dirname` / `__filename` in a JavaScript ES module, where Node never + // defines them: typeof answers "undefined" instead of throwing. + { + let operand = expr.expression; + while (ts.isParenthesizedExpression(operand)) operand = operand.expression; + if ( + ts.isIdentifier(operand) && + (operand.text === "__dirname" || operand.text === "__filename") && + lowerer.isStdlibGlobal(operand, operand.text) + ) { + const sf = ts.getSourceFileOfNode(operand); + if (isNodeEsmFile(sf, lowerer.program) && !/\.tsx?$/.test(sf.fileName)) + return { kind: "strLit", value: "undefined", type: STRING, loc }; + } + } if (isNavigatorGlobal(lowerer, expr.expression)) return { kind: "strLit", value: "object", type: STRING, loc }; const performance = lowerPerfHooksTypeof(lowerer, expr.expression); @@ -124,6 +160,15 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc // declarations type some (AbortSignal) without a construct signature, // so neither the operand nor the type can answer; the read has no // effects. + // `typeof globalThis.`: the global object has no such + // property in Node 24. + if ( + ts.isPropertyAccessExpression(tested) && + tested.name.text === TEMPORAL_GLOBAL && + stdlibGlobalNameOf(lowerer, tested.expression) === "globalThis" + ) { + return { kind: "strLit", value: "undefined", type: STRING, loc }; + } if (constructorGlobalTypeof(lowerer, tested) !== null) return { kind: "strLit", value: "function", type: STRING, loc }; // `typeof globalThis.AbortSignal.any` (ky's feature probe): a static @@ -148,13 +193,18 @@ export function lowerTypeofExpr(lowerer: Lowerer, expr: ts.TypeOfExpression, loc if (!sym) { return { kind: "strLit", value: "undefined", type: STRING, loc }; } + // Node 24 defines no temporal global: typeof answers "undefined" + // without reading it, like any undeclared name. + if (isTemporalGlobalRoot(lowerer, tested, sym)) { + return { kind: "strLit", value: "undefined", type: STRING, loc }; + } // `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()) + const imported = isJsSourceFile(ts.getSourceFileOfNode(tested)) ? lowerer.builtinImportOf(tested) : null; if (imported && builtinModuleConstOf(lowerer, imported.module, imported.member) === undefined) { diff --git a/packages/compiler/src/frontend/lowering/expressions/union-fields.ts b/packages/compiler/src/frontend/lowering/expressions/union-fields.ts index f2dd0d0f0..6454bc1fd 100644 --- a/packages/compiler/src/frontend/lowering/expressions/union-fields.ts +++ b/packages/compiler/src/frontend/lowering/expressions/union-fields.ts @@ -117,7 +117,7 @@ export function lowerUnionFieldRead( access: ts.PropertyAccessExpression, receiver: IrExpr, ): IrExpr | null { - if (receiver.type.kind !== "union" || isJsSourceFile(access.getSourceFile())) return null; + if (receiver.type.kind !== "union" || isJsSourceFile(ts.getSourceFileOfNode(access))) return null; const unionId = receiver.type.unionId; const arms = lowerer.unions.get(unionId)?.arms; if (!arms || arms.length < 2) return null; diff --git a/packages/compiler/src/frontend/lowering/expressions/updates.ts b/packages/compiler/src/frontend/lowering/expressions/updates.ts index 61943eff4..104057906 100644 --- a/packages/compiler/src/frontend/lowering/expressions/updates.ts +++ b/packages/compiler/src/frontend/lowering/expressions/updates.ts @@ -1,5 +1,7 @@ /* Increment, decrement and prefix unary operators, and compound field updates. */ import * as ts from "../../ts7/adapter.js"; +import { isStrictCode } from "../strict-mode.js"; +import { isPrivateWriteReceiver } from "../private-brands.js"; import type { Lowerer } from "../lowerer.js"; import { type FieldTarget, @@ -40,6 +42,7 @@ import type { CompoundOp } from "../surfaces.js"; import { tryLowerExpression } from "./try-lower-expression.js"; import { hasRuntimeStatics } from "../class-runtime-statics.js"; import { declareContextLocal } from "../function-context.js"; +import { keyedFieldTarget, keyedFieldWrite, saveKeyedFieldKey } from "./keyed-field-targets.js"; /** Read-modify-write updates read the member first: an unassigned * deferred-init receiver throws Node's member-read TypeError. */ @@ -107,7 +110,7 @@ export function lowerPrefixUnary(lowerer: Lowerer, expr: ts.PrefixUnaryExpressio } // Match the checked numeric boundary used by JS binary arithmetic. const operand = - raw.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()) + raw.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)) ? { kind: "dynCheck" as const, value: raw, type: F64, loc } : lowerOptionalNumber(lowerer, raw, loc, expr.operand); if (operand.type.kind !== "f64") operatorUnsupported(lowerer, expr, "-", [raw.type]); @@ -260,7 +263,7 @@ export function lowerIncDec( const receiver = save(raw, "%incrementArray"); const rawIndex = lowerer.lowerExpr(access.argumentExpression); const index = save( - rawIndex.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile()) + rawIndex.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr)) ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [rawIndex], type: F64, loc } : rawIndex, "%incrementIndex", @@ -305,10 +308,13 @@ export function lowerIncDec( // class-backed protocol view): the expanded form `o.v = o.v + 1`, its // read checked into number like any other read of the member. const checkedNumber = - !isJsSourceFile(expr.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && raw.type.kind === "dyn" && lowerer.mapTypeOf(lowerer.typeOf(access))?.kind === "f64"; - if ((isJsSourceFile(expr.getSourceFile()) || checkedNumber) && raw.type.kind === "dyn") { + if ( + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || checkedNumber) && + raw.type.kind === "dyn" + ) { const receiver = save(raw, "%incrementReceiver"); const key = save( ts.isElementAccessExpression(access) @@ -335,10 +341,11 @@ export function lowerIncDec( } const target = ts.isPropertyAccessExpression(access) ? lowerer.fieldTarget(access) - : symbolFieldTarget(lowerer, access); + : (symbolFieldTarget(lowerer, access) ?? keyedFieldTarget(lowerer, access, true)); if (target && lowerer.dynConvertible(target.fieldType)) { checkDeferredMemberReceiver(lowerer, target, access); target.obj = save(target.obj, "%incrementReceiver"); + saveKeyedFieldKey(target, save); return finish(lowerer.fieldGetExpr(target, loc, access), (value) => lowerer.fieldSetStmt( target, @@ -348,7 +355,7 @@ export function lowerIncDec( ), ); } - if (isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(raw.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(raw.type)) { const receiver = save(lowerer.coerceInto(access.expression, raw, DYN), "%incrementReceiver"); const key = save( ts.isElementAccessExpression(access) @@ -524,6 +531,19 @@ export function fieldSetStmt( loc: SrcLoc, blame: ts.Node, ): IrStmt { + // A write through a brand-checked receiver (checkedPrivateReceiver): the + // value evaluates first, then the brand check throws, as in Node. + if (isPrivateWriteReceiver(target.obj) && value.kind !== "varRef") { + const stored = lowerer.declareHiddenLocal("%privateWriteValue", value.type); + return { + kind: "block", + body: [ + { kind: "varDecl", localId: stored.id, init: value, loc }, + fieldSetStmt(lowerer, target, varRef(stored.id, stored.type, loc), loc, blame), + ], + loc, + }; + } // A member write through an unassigned deferred-init field: the value // still evaluates first, then the write throws Node's TypeError. const deferredSlot = @@ -602,6 +622,7 @@ export function fieldSetStmt( }; } } + if (target.container === "recordKeyed") return keyedFieldWrite(lowerer, target, value, loc); if (target.container === "dynamic") return { kind: "exprStmt", @@ -737,6 +758,7 @@ export function fieldSetStmt( className: target.className, field: target.field, value, + ...(isStrictCode(blame) ? {} : { sloppy: true as const }), loc, } : { @@ -799,7 +821,7 @@ export function lowerFieldCompoundValue( rhsNode ? lowerer.lowerExpr(rhsNode) : { kind: "numLit", value: 1, type: F64, loc }, "%compoundRhs", ); - if (isJsSourceFile(access.getSourceFile()) && lowerer.dynConvertible(rhs.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(access)) && lowerer.dynConvertible(rhs.type)) { const numeric = ["-", "*", "/", "%", "**"].includes(op); const right = lowerer.coerceToExpected(rhs, DYN); const value = save( @@ -856,12 +878,13 @@ export function lowerFieldCompoundValue( const targetOf = (): FieldTarget | null => ts.isPropertyAccessExpression(access) ? lowerer.fieldTarget(access) - : symbolFieldTarget(lowerer, access); + : (symbolFieldTarget(lowerer, access) ?? keyedFieldTarget(lowerer, access, true)); const target = targetOf(); if (!target) lowerer.unsupported("SC1090", access, "compound assignment to unsupported field targets"); checkDeferredMemberReceiver(lowerer, target, access); target.obj = save(target.obj, "%compoundReceiver"); + saveKeyedFieldKey(target, save); // Accessors observe getter, RHS side effects, then setter, all through // the saved receiver. const fieldRead = lowerer.fieldGetExpr(target, locOf(access), access); @@ -892,7 +915,7 @@ export function lowerFieldCompoundValue( "%compoundRhs", ); const numericRhs: IrExpr = - rhs.type.kind === "dyn" && isJsSourceFile(access.getSourceFile()) + rhs.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(access)) ? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc } : lowerOptionalNumber(lowerer, rhs, loc); let value: IrExpr; @@ -907,7 +930,7 @@ export function lowerFieldCompoundValue( } else if (bigintValue) { value = bigintValue; } else if ( - (target.fieldType.kind === "dyn" && isJsSourceFile(access.getSourceFile())) || + (target.fieldType.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(access))) || // An untyped right operand: the expanded `o.f = o.f op rhs`; the field // write below checks the result into the declared field type. (rhs.type.kind === "dyn" && diff --git a/packages/compiler/src/frontend/lowering/function-context.ts b/packages/compiler/src/frontend/lowering/function-context.ts index e31e194c5..92042b7d7 100644 --- a/packages/compiler/src/frontend/lowering/function-context.ts +++ b/packages/compiler/src/frontend/lowering/function-context.ts @@ -48,6 +48,16 @@ export interface FnCtx { argumentsLocal?: IrLocal | null; /** Arrows inherit the nearest ordinary function's arguments binding. */ inheritsArguments?: boolean; + /** A function lowered on demand on top of an unrelated caller's frames + * (a generic instance): lexical `this` resolution stops here, so the + * body sees what it sees when lowered alone. */ + thisBoundary?: true; + /** Tuple bindings of this function that are only forwarded + * (typed-arguments.ts onlyForwarded): binding local id → the native + * slots forwarding reads. Such a binding is never materialized. Local ids + * are unique within one function only, and nested functions never read + * these bindings (a read there needs the tuple). */ + pureTuples: Map; /** Declared return type — lets `return` detect record-shape mismatches * (SC2002) before the validator would ICE on them. */ returnType: IrType; @@ -81,6 +91,7 @@ export function newFnCtx( captures: lifted ? [] : null, captureSources: [], captureBySymbol: new Map(), + pureTuples: new Map(), selfSymbol, selfType, returnType, @@ -154,7 +165,16 @@ export function captureContextBinding( throw new InternalCompilerError("lowerer bug: no active function context"); const direct = bindingInContext(current, symbol); if (direct !== null) return { local: direct, origin: direct, error: null }; - for (let depth = stack.length - 2; depth >= 0; depth--) { + let floor = 0; + if (symbol === undefined) { + for (let depth = stack.length - 1; depth >= 0; depth--) { + if (stack[depth]!.thisBoundary) { + floor = depth; + break; + } + } + } + for (let depth = stack.length - 2; depth >= floor; depth--) { const origin = bindingInContext(stack[depth]!, symbol); if (origin === null) continue; diff --git a/packages/compiler/src/frontend/lowering/function-returns.ts b/packages/compiler/src/frontend/lowering/function-returns.ts index c0e70013c..9500d6d2d 100644 --- a/packages/compiler/src/frontend/lowering/function-returns.ts +++ b/packages/compiler/src/frontend/lowering/function-returns.ts @@ -98,8 +98,8 @@ export function appendImplicitUndefinedReturn( /** Whether the source explicitly documents a return contract. */ export function hasExplicitJsDocReturn(decl: ts.SignatureDeclaration): boolean { - const sourceFile = decl.getSourceFile(); - return /@returns?\b/.test(sourceFile.text.slice(decl.pos, decl.getStart(sourceFile))); + const sourceFile = ts.getSourceFileOfNode(decl); + return /@returns?\b/.test(sourceFile.text.slice(decl.pos, ts.tokenStartOfNode(decl))); } export function promiseCarriesDyn(lowerer: Lowerer, type: IrType): boolean { @@ -130,14 +130,14 @@ export function declaredReturnType( decl: ts.SignatureDeclaration, blame: ts.Node, ): IrType { - if (isJsSourceFile(decl.getSourceFile()) && ts.isGetAccessorDeclaration(decl)) return DYN; + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && ts.isGetAccessorDeclaration(decl)) return DYN; const sig = lowerer.checker.getSignatureFromDeclaration(decl); if (!sig) lowerer.unsupported("SC1090", decl, "this function form"); const retTsType = lowerer.checker.getReturnTypeOfSignature(sig); // An inferred null/undefined return in JS can read a mutable checked-value // field. Keep the actual value instead of restricting it to the initializer. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (retTsType.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 ) return DYN; @@ -154,7 +154,7 @@ export function declaredReturnType( ) mappedReturn = mapAsyncGeneratorAnnotation(retTsType, lowerer.typeCtx); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword) && !decl.asteriskToken && mappedReturn?.kind !== "promise" @@ -162,7 +162,11 @@ export function declaredReturnType( const fallback = dynFallbackType(lowerer, decl, retTsType); return fallback?.kind === "promise" ? fallback : { kind: "promise", inner: DYN }; } - if (mappedReturn && isJsSourceFile(decl.getSourceFile()) && functionCanReturnUndefined(decl)) + if ( + mappedReturn && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && + functionCanReturnUndefined(decl) + ) mappedReturn = lowerer.withUndefinedArmOf(mappedReturn) ?? mappedReturn; // A generic-class method body that can return an indexed read that // misses returns undefined there, whatever the declared result says. @@ -171,33 +175,33 @@ export function declaredReturnType( mappedReturn.kind === "promise" ? { kind: "promise", inner: lowerer.runtimeOptionalType(mappedReturn.inner) } : lowerer.runtimeOptionalType(mappedReturn); - if (isJsSourceFile(decl.getSourceFile()) && mappedReturn?.kind === "void") { + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && mappedReturn?.kind === "void") { let returnsValue = false; const scan = (node: ts.Node): void => { if (node !== decl && ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression) returnsValue = true; - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(decl); if (returnsValue) return DYN; } - if (isJsSourceFile(decl.getSourceFile()) && mappedReturn?.kind === "array") return DYN; + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && mappedReturn?.kind === "array") return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "record" ) return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "date" ) return DYN; if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "union" && @@ -207,7 +211,7 @@ export function declaredReturnType( // JS inference assigns Number to arithmetic over any, although the // runtime operands can both be BigInts. Preserve that native result. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasExplicitJsDocReturn(decl) && mappedReturn?.kind === "f64" @@ -237,13 +241,13 @@ export function declaredReturnType( ) ) numericAnyReturn = true; - node.forEachChild(check); + ts.forEachChild(node, check); }; check(decl); if (numericAnyReturn) return DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !hasExplicitJsDocReturn(decl) && mappedReturn !== null && promiseCarriesDyn(lowerer, mappedReturn) && @@ -264,7 +268,7 @@ export function declaredReturnType( // value rides its own box, calls go through the boxed thunk (JS // arity), and typed slots re-check with dynCheck as usual. if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.type === undefined && lowerer.mapTypeOf(retTsType)?.kind === "func" ) { @@ -304,7 +308,7 @@ export function declaredReturnType( (arm.kind === "object" && !hasExplicitJsDocReturn(decl)), ); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && decl.type === undefined && recordReturn !== null && recordReturn !== undefined && @@ -468,5 +472,5 @@ export function blockBodyOf(decl: ts.FunctionLikeDeclaration): ts.Block | null { const body = decl.body; if (body === undefined) return null; if (ts.isBlock(body)) return body; - return decl.forEachChild((c) => (ts.isBlock(c) ? c : undefined)) ?? null; + return ts.forEachChild(decl, (c) => (ts.isBlock(c) ? c : undefined)) ?? null; } diff --git a/packages/compiler/src/frontend/lowering/general-streams.ts b/packages/compiler/src/frontend/lowering/general-streams.ts new file mode 100644 index 000000000..d31439465 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/general-streams.ts @@ -0,0 +1,136 @@ +/* The general stream representation (TypeMapperCtx.generalNodeStreams). + * + * NodeJS.ReadableStream and NodeJS.WritableStream (and @types/node's + * stream.Readable / stream.Writable, which also type child stdio) name the + * interface every Node stream implements. By default they map to the + * child/process stdio handles: the specialized representation of the + * values that commonly fill them (child.stdout, process.stdout). A program + * that moves another stream — fs.createReadStream(), a zlib Transform, a + * Readable subclass — into one of those slots needs the general + * representation instead: the runtime stream classes, with the stdio + * handles converted into them where they are read or flow. + * + * The decision is per program. Reachable emit lowers without it; a flow + * that needs it marks the lowerer (needsGeneralNodeStreams) and lowerToIr + * lowers the whole program again with it. Programs without such a flow + * never take the second pass, so their IR is unchanged. */ +import type { IrExpr, IrType } from "../../ir/ir.js"; +import { everyTypeChild } from "../../ir/traverse.js"; +import * as ts from "../ts7/adapter.js"; +import { isGeneralNodeStreamInterface } from "../type-mapper.js"; +import type { Lowerer } from "./lowerer.js"; +import { streamSidesOf } from "./lower-stream.js"; + +const STDIO_STREAM_KINDS: ReadonlySet = new Set([ + "childStream", + "childWriter", + "procStream", +]); + +/** Whether `type` (through union arms, record fields and the structural + * type children) contains a part satisfying `test`. */ +function containsPart(lowerer: Lowerer, type: IrType, test: (part: IrType) => boolean): boolean { + const seen = new Set(); + const walk = (part: IrType): boolean => { + if (test(part)) return true; + if (part.kind === "union") { + if (seen.has(`u:${part.unionId}`)) return false; + seen.add(`u:${part.unionId}`); + return (lowerer.unions.get(part.unionId)?.arms ?? []).some(walk); + } + if (part.kind === "record") { + if (seen.has(`r:${part.shapeId}`)) return false; + seen.add(`r:${part.shapeId}`); + return (lowerer.shapes.get(part.shapeId)?.fields ?? []).some((f) => walk(f.type)); + } + let found = false; + everyTypeChild(part, (child) => { + found = walk(child); + return !found; + }); + return found; + }; + return walk(type); +} + +function isGeneralStream(lowerer: Lowerer, part: IrType): boolean { + return ( + part.kind === "object" && streamSidesOf(lowerer, lowerer.classes.get(part.className)) !== null + ); +} + +/** A mismatched flow (requireExactShape's input) that the general stream + * representation resolves: a general stream into a stdio-represented + * slot, a child pipe handle into an 'unknown' slot, or an 'unknown' value + * into a child-pipe slot (the general classes take a checked cast). */ +export function needsGeneralNodeStreams( + lowerer: Lowerer, + actual: IrType, + expected: IrType, +): boolean { + const stdio = (part: IrType): boolean => STDIO_STREAM_KINDS.has(part.kind); + const pipeHandle = (part: IrType): boolean => + part.kind === "childStream" || part.kind === "childWriter"; + if (actual.kind === "dyn") return containsPart(lowerer, expected, pipeHandle); + if (containsPart(lowerer, expected, stdio)) + return containsPart(lowerer, actual, (p) => isGeneralStream(lowerer, p)); + return expected.kind === "dyn" && containsPart(lowerer, actual, pipeHandle); +} + +/** Under the general representation, process.stdout / process.stderr (the + * procStream fd scalar) flowing into a runtime Writable slot: an + * fd-backed Writable whose writes take the same synchronous path as + * process.stdout.write. Null when the flow is not that one. */ +export function stdioIntoGeneralStream( + lowerer: Lowerer, + expr: IrExpr, + expected: IrType, +): IrExpr | null { + if (!lowerer.generalNodeStreams || expr.type.kind !== "procStream") return null; + const writable: IrType = { kind: "object", className: "%Writable" }; + // Checked-dynamic stream slots (dyn-streams.ts) hold the Writable boxed. + const accepts = + (expected.kind === "object" && expected.className === "%Writable") || + (expected.kind === "union" && lowerer.armTag(expected.unionId, writable) >= 0) || + (expected.kind === "dyn" && lowerer.dynNodeStreams); + if (!accepts) return null; + return { + kind: "libCall", + fn: "stream.stdioWritable", + args: [expr], + type: writable, + loc: expr.loc, + }; +} + +/** A stdio-handle receiver whose member has no lowering on the handle + * (`out.on("error", ...)`, `child.stdout.pipe(...)`, `output.end()`): when + * its declared type is one the general representation maps to a runtime + * stream class, the program lowers again with it, where the full stream + * surface applies. The caller still reports its fence for this pass. */ +export function requestGeneralStreamSurface(lowerer: Lowerer, receiver: ts.Expression): void { + if (lowerer.generalNodeStreams) return; + const type = lowerer.checker.getNonNullableType(lowerer.typeOf(receiver)); + if (isGeneralNodeStreamInterface(type, lowerer.typeCtx)) + lowerer.requiresGeneralNodeStreams = true; +} + +/** Under the general representation, an untyped value reaching a runtime + * stream class slot (the checked unwrap) or an untyped pipe/pipeline + * stage: the program lowers again with checked-dynamic stream slots + * (dyn-streams.ts), where NodeJS.ReadableStream / NodeJS.WritableStream + * hold JavaScript stream objects too. */ +export function noteUntypedStreamFlow(lowerer: Lowerer, expected: IrType | null): void { + if (lowerer.dynNodeStreams) return; + if (!lowerer.generalNodeStreams) { + // An untyped pipe or pipeline stage needs both: the next pass lowers + // with the general representation and checked-dynamic slots at once. + if (expected === null) { + lowerer.requiresGeneralNodeStreams = true; + lowerer.requiresDynNodeStreams = true; + } + return; + } + if (expected === null || containsPart(lowerer, expected, (p) => isGeneralStream(lowerer, p))) + lowerer.requiresDynNodeStreams = true; +} diff --git a/packages/compiler/src/frontend/lowering/generic-functions.ts b/packages/compiler/src/frontend/lowering/generic-functions.ts index ca4664f20..ac5949656 100644 --- a/packages/compiler/src/frontend/lowering/generic-functions.ts +++ b/packages/compiler/src/frontend/lowering/generic-functions.ts @@ -1,3 +1,4 @@ +import { staticNameBindings } from "./static-function-names.js"; import * as ts from "../ts7/adapter.js"; import { bodyReadsArguments } from "../arguments-usage.js"; import type { Lowerer } from "./lowerer.js"; @@ -100,6 +101,9 @@ export interface GenericFnInfo { * names where the slot binds the call site's object type. Computed on * first use. */ implicitPatterns?: (ImplicitPattern | null)[]; + /** Parameter index → symbol for the function parameters whose `.name` + * the body reads (static-function-names.ts); computed on first use. */ + nameReadParams?: Map; } export interface GenericInstance { @@ -140,6 +144,9 @@ export interface GenericInstance { implicitInferReturn?: true; /** An erased instance (every type parameter bound to `unknown`). */ erased?: true; + /** Name-keyed instances: function parameter → its call site's static + * name (null when only known at run time), folded at `.name` reads. */ + staticFnNames?: Map; } /** Defaults bind during inference; collection only resolves symbols in @@ -218,7 +225,7 @@ export function collectGenericSignature(lowerer: Lowerer, decl: ts.FunctionDecla lowerer.genericFnsBySymbol.set(symbol, { decl, baseName: nameText, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(decl) + nameText), + qualifiedName: lowerer.qualify(ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + nameText), typeParams, instances: new Map(), }); @@ -298,7 +305,13 @@ export function genericCallInstance( const retTs = lowerer.checker.getReturnTypeOfSignature(rsig); const local = localClassResultInstance(lowerer, expr, info, rsig, params, retTs); if (local) return local; - const mappedReturn = lowerer.mapTypeOf(retTs); + // A result instantiated at `any` (a type argument inferred from an + // untyped value, such as an untyped forwarding call) is checked-dynamic. + const mappedReturn = + lowerer.mapTypeOf(retTs) ?? + ((retTs.flags & ts.TypeFlags.Any) !== 0 && !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) + ? DYN + : null); if (!mappedReturn) { fenceGenericSignatureResult(lowerer, expr, retTs); lowerer.badType(expr, retTs); @@ -313,16 +326,24 @@ export function genericCallInstance( // different fields). Non-literal bindings (a key union, plain string) // share one runtime-keyed instance per IR signature, exactly the // widened discipline. + const named = staticNameBindings(lowerer, expr, info); if (keyofConstrainedTypeParams(info).size > 0) { const tsBindings = new Map(); const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings); return internGenericInstance(lowerer, expr, info, params, returnType, () => bindings, { - extraKey: keyofLiteralKey(info, tsBindings), + extraKey: `${keyofLiteralKey(info, tsBindings)}${named?.key ?? ""}`, tsBindings, + ...(named ? { staticFnNames: named.names } : {}), }); } - return internGenericInstance(lowerer, expr, info, params, returnType, (tsBindings) => - lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings), + return internGenericInstance( + lowerer, + expr, + info, + params, + returnType, + (tsBindings) => lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings), + named ? { extraKey: named.key, staticFnNames: named.names } : undefined, ); } @@ -372,7 +393,7 @@ function overloadCallInstance( const overloadTps = rdecl.typeParameters?.nodes ?? []; expr.typeArguments?.nodes.forEach((ta, i) => { const name = overloadTps[i]?.name.text; - const tp = info.typeParams.find((p) => p.name === name); + const tp = info.typeParams.find((p) => ts.symbolName(p) === name); if (!tp || bindings.has(tp)) return; const taT = lowerer.checker.getTypeFromTypeNode(ta); const mapped = lowerer.mapTypeOf(taT); @@ -586,7 +607,11 @@ function internGenericInstance( params: ParamShape[], returnType: IrType, makeBindings: (tsBindings: Map) => Map, - opts?: { extraKey?: string; tsBindings?: Map }, + opts?: { + extraKey?: string; + tsBindings?: Map; + staticFnNames?: Map; + }, ): GenericInstance { // keyof-constrained instantiations append their literal keys // (extraKey): the IR signature alone under-discriminates there — two @@ -615,7 +640,7 @@ function internGenericInstance( return JSON.stringify(tsBound.value); } const bound = bindings.get(tp); - return bound ? lowerer.fmt(bound) : tp.name; + return bound ? lowerer.fmt(bound) : ts.symbolName(tp); }) .join(", "); // Deep polymorphic recursion renders unbounded types — keep messages sane. @@ -629,6 +654,7 @@ function internGenericInstance( bindings, tsBindings, typeArgsText, + ...(opts?.staticFnNames ? { staticFnNames: opts.staticFnNames } : {}), }; info.instances.set(key, inst); lowerer.instantiationQueue.push({ info, inst }); @@ -778,7 +804,12 @@ function unifySignatureBindings( tsBindings.set(sym, lowerer.typeParamTsResolver(inst) ?? inst); } if (!bindings.has(sym)) { - const mapped = lowerer.mapTypeOf(inst); + const mapped = + lowerer.mapTypeOf(inst) ?? + ((inst.flags & ts.TypeFlags.Any) !== 0 && + !isJsSourceFile(ts.getSourceFileOfNode(info.decl)) + ? DYN + : null); // A visitor's inferred T can be void. It still binds the // return convention of `(node) => T`; discarding it leaves // the callback's return type spuriously uninstantiated. @@ -909,7 +940,7 @@ function unifySignatureBindings( } if (declared.flags & ts.TypeFlags.Object) { for (const dp of lowerer.checker.getPropertiesOfType(declared)) { - const ip = lowerer.checker.getPropertyOfType(inst, dp.name); + const ip = lowerer.checker.getPropertyOfType(inst, ts.symbolName(dp)); if (ip) unify( lowerer.checker.getTypeOfSymbol(dp), @@ -952,6 +983,8 @@ export function lowerGenericInstance( const prevSuppress = lowerer.suppressStats; const prevClass = lowerer.currentClass; const prevImplicit = lowerer.implicitParamTypes; + const prevStaticFnNames = lowerer.staticFnNames; + lowerer.staticFnNames = inst.staticFnNames ?? null; // A generic METHOD of a generic-class INSTANTIATION lowers under BOTH // binding sets: the receiver instantiation's class type parameters // underneath, the method instantiation's own on top (disjoint symbol @@ -1001,6 +1034,8 @@ export function lowerGenericInstance( : lowerer.bodyReturnType(isAsync, inst.returnType); const fnCtx = newFnCtx(false, null, null, bodyReturn); fnCtx.isAsync = isAsync; + // Instances lower on demand, on top of whichever caller reached them. + fnCtx.thisBoundary = true; // Implicit-any instances whose declared return did not map lower in // return-INFERENCE mode: `return` statements record here bare, and the // post-pass (resolveInferredReturn) settles the type and wraps them. @@ -1112,6 +1147,7 @@ export function lowerGenericInstance( lowerer.typeParamBindings = prevBindings; lowerer.typeParamTsBindings = prevTsBindings; lowerer.implicitParamTypes = prevImplicit; + lowerer.staticFnNames = prevStaticFnNames; lowerer.instantiationContext = prevContext; lowerer.localClassInstantiations.pop(); lowerer.suppressStats = prevSuppress; @@ -1264,7 +1300,7 @@ function genericValueInstance( const rendered = info.typeParams .map((tp) => { const bound = bindings.get(tp); - return bound ? lowerer.fmt(bound) : tp.name; + return bound ? lowerer.fmt(bound) : ts.symbolName(tp); }) .join(", "); lowerer.typeParamBindings = bindings; @@ -1363,7 +1399,7 @@ function paramWrittenInBody( hit = true; return; } - m.forEachChild(scan); + ts.forEachChild(m, scan); }; scan(n); return hit; @@ -1398,7 +1434,7 @@ function paramWrittenInBody( written = true; return; } - n.forEachChild(walk); + ts.forEachChild(n, walk); }; walk(body); return written; @@ -1573,7 +1609,7 @@ export function implicitAnyParamSymbolsOf( }); // Destructured object parameters monomorphize too (implicitPatternsOf); // their slots stay null here. - if (!hasImplicitParams && isJsSourceFile(decl.getSourceFile())) + if (!hasImplicitParams && isJsSourceFile(ts.getSourceFileOfNode(decl))) hasImplicitParams = implicitPatternsOf(lowerer, decl).some((pattern) => pattern !== null); return hasImplicitParams ? out : null; } @@ -1723,7 +1759,7 @@ function implicitDeclaredReturn(lowerer: Lowerer, info: GenericFnInfo): IrType | // declaredReturnType widens non-generic JavaScript functions. if ( mapped !== null && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && functionCanReturnUndefined(info.decl) ) return lowerer.withUndefinedArmOf(mapped) ?? mapped; @@ -1771,7 +1807,7 @@ function bindableImplicitIr(t: IrType | null): t is IrType { function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrType | null { while (ts.isParenthesizedExpression(arg)) arg = arg.expression; if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) ) { const saved = lowerer.diagSink; @@ -1794,7 +1830,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp } if ( ts.isObjectLiteralExpression(arg) && - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (arg.properties.nodes.length === 0 || arg.properties.nodes.some( (property) => @@ -1809,7 +1845,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp if (ts.isIdentifier(arg)) return lowerer.peekLocal(arg)?.type ?? lowerer.globalOf(arg)?.type ?? null; if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isPropertyAccessExpression(arg) || ts.isElementAccessExpression(arg)) ) { // Mutable JS members can retain an initializer-only checker type (for @@ -1819,7 +1855,7 @@ function storedImplicitArgumentType(lowerer: Lowerer, arg: ts.Expression): IrTyp if (represented) return represented.type; } if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && (ts.isCallExpression(arg) || ts.isConditionalExpression(arg) || (ts.isBinaryExpression(arg) && @@ -1936,7 +1972,7 @@ export function implicitCallInstance( (neverTaintedJsType(lowerer, arg, t) ? null : lowerer.mapTypeOf(t)) ?? dynFallbackType(lowerer, arg, t); if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && ts.isArrayLiteralExpression(arg) && mapped?.kind === "array" && (mapped.elem.kind === "classval" || @@ -1961,7 +1997,7 @@ export function implicitCallInstance( // preserve represented arms through the checked boundary instead // of specializing a call to a union that would discard them. if ( - isJsSourceFile(arg.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(arg)) && mapped?.kind === "func" && !lowerer.dynConvertible(mapped) ) { @@ -2159,7 +2195,7 @@ export function implicitLocalFnNodeOf( if (cached !== undefined) return cached ? (cached.decl as ts.FunctionExpression | ts.ArrowFunction) : null; const probe = (): ts.FunctionExpression | ts.ArrowFunction | null => { - if (!implicitMonoFile(decl.getSourceFile())) return null; + if (!implicitMonoFile(ts.getSourceFileOfNode(decl))) return null; if (!ts.isIdentifier(decl.name) || decl.initializer === undefined) return null; let init: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(init)) init = init.expression; @@ -2190,7 +2226,7 @@ export function implicitLocalFnNodeOf( const d = s ? s.valueDeclaration : undefined; if ( d && - d.getSourceFile() === decl.getSourceFile() && + ts.getSourceFileOfNode(d) === ts.getSourceFileOfNode(decl) && !(d.pos >= init.pos && d.end <= init.end) ) { // Declared outside the initializer, in this file: a capture @@ -2209,7 +2245,7 @@ export function implicitLocalFnNodeOf( } } } - n.forEachChild(scan); + ts.forEachChild(n, scan); }; scan(init.body); return captures ? null : init; @@ -2247,8 +2283,8 @@ export function implicitLocalFnInfoOf( decl: fnNode, baseName: name, qualifiedName: lowerer.qualify( - decl.getSourceFile(), - nsPathPrefix(stmt ?? decl, decl) + `${name}%l${decl.getStart()}`, + ts.getSourceFileOfNode(decl), + nsPathPrefix(stmt ?? decl, decl) + `${name}%l${ts.tokenStartOfNode(decl)}`, ), typeParams: [], instances: new Map(), @@ -2342,7 +2378,10 @@ export function objLitGenericFnInfoOf( const info: GenericFnInfo = { decl: fnNode, baseName: name, - qualifiedName: lowerer.qualify(fnNode.getSourceFile(), `%ol${fnNode.getStart()}.${name}`), + qualifiedName: lowerer.qualify( + ts.getSourceFileOfNode(fnNode), + `%ol${ts.tokenStartOfNode(fnNode)}.${name}`, + ), typeParams, instances: new Map(), objectLiteral: true, @@ -2474,7 +2513,10 @@ export function bindingGenericFnInfoOf( const info: GenericFnInfo = { decl: fnNode, baseName: name, - qualifiedName: lowerer.qualify(decl.getSourceFile(), nsPathPrefix(stmt ?? decl, decl) + name), + qualifiedName: lowerer.qualify( + ts.getSourceFileOfNode(decl), + nsPathPrefix(stmt ?? decl, decl) + name, + ), typeParams, instances: new Map(), }; diff --git a/packages/compiler/src/frontend/lowering/import-meta.ts b/packages/compiler/src/frontend/lowering/import-meta.ts index baf550e6d..796e64b69 100644 --- a/packages/compiler/src/frontend/lowering/import-meta.ts +++ b/packages/compiler/src/frontend/lowering/import-meta.ts @@ -20,7 +20,7 @@ export function isImportMeta(expr: ts.Expression): boolean { /** File-backed metadata is fixed by the compiled module graph. Reads share * one implementation across dot access, folded keys and selected bindings. */ export function importMetaField(lowerer: Lowerer, node: ts.Node, name: string): IrExpr { - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); const fileName = lowerer.targetPlatform === "wasi" ? (wasiGuestPath(sf.fileName) ?? sf.fileName.replace(/\\/g, "/")) diff --git a/packages/compiler/src/frontend/lowering/intern-tables.ts b/packages/compiler/src/frontend/lowering/intern-tables.ts index 32bb71858..fc69237db 100644 --- a/packages/compiler/src/frontend/lowering/intern-tables.ts +++ b/packages/compiler/src/frontend/lowering/intern-tables.ts @@ -142,7 +142,7 @@ interface TableTypes { /** The key and value types of an InternTable checker type. */ function tableTypes(lowerer: Lowerer, type: ts.Type, blame: ts.Node): TableTypes { const parts = type.isUnionType() ? ts.constituentTypes(type) : [type]; - const table = parts.find((p) => p.getSymbol()?.name === "InternTable"); + const table = parts.find((p) => ts.symbolNameOf(p.getSymbol()) === "InternTable"); const args = table ? lowerer.checker.getTypeArguments(table as ts.TypeReference) : []; if (args.length !== 2) lowerer.noLowering("InternTable without type arguments", blame); const key = lowerer.mapTypeOf(args[0]!); diff --git a/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts b/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts index d39e5cceb..b9bbf6216 100644 --- a/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts +++ b/packages/compiler/src/frontend/lowering/js-dynamic-objects.ts @@ -81,10 +81,10 @@ export function isValueReturningConstructorClass(decl: ts.ClassDeclaration): boo if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; if (ts.isReturnStatement(node) && node.expression !== undefined && !insideArrow(node, ctor!)) returnsValue = true; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - ctor.body.forEachChild(visit); - for (const parameter of ctor.parameters.nodes) parameter.forEachChild(visit); + ts.forEachChild(ctor.body, visit); + for (const parameter of ctor.parameters.nodes) ts.forEachChild(parameter, visit); return returnsValue && !observesThis; } @@ -215,7 +215,7 @@ export function lowerValueReturningConstructorNew( "spread arguments when constructing a class whose constructor returns a value", ); } - const key = `ctorValue:${decl.getSourceFile().fileName}:${decl.getStart()}`; + const key = `ctorValue:${ts.getSourceFileOfNode(decl).fileName}:${ts.tokenStartOfNode(decl)}`; let name = lowerer.valueHelpers.get(key); if (name === undefined) { name = `%ctorValue.${lowerer.valueHelpers.size}`; @@ -235,7 +235,7 @@ export function lowerValueReturningConstructorNew( { kind: "runtimeFence", code: "SC1090", - message: `constructors that return no object (the bare instance of '${decl.name?.text ?? "class"}' has no representation here) are not supported yet [SC1090 at ${decl.getSourceFile().fileName}:${ts.getLineAndCharacterOfPosition(decl.getSourceFile(), ctor.getStart()).line + 1}]`, + message: `constructors that return no object (the bare instance of '${decl.name?.text ?? "class"}' has no representation here) are not supported yet [SC1090 at ${ts.getSourceFileOfNode(decl).fileName}:${ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(decl), ts.tokenStartOfNode(ctor)).line + 1}]`, loc: locOf(ctor), }, ]; @@ -261,8 +261,8 @@ export function lowerValueReturningConstructorNew( .map((arg) => ({ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc: locOf(arg) })); const result = lowerer.declareHiddenLocal("%constructed", DYN); const value: IrExpr = { kind: "varRef", localId: result.id, type: DYN, loc }; - const sf = expr.getSourceFile(); - const line = ts.getLineAndCharacterOfPosition(sf, expr.getStart()).line + 1; + const sf = ts.getSourceFileOfNode(expr); + const line = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(expr)).line + 1; const objectLike: IrExpr = { kind: "logical", op: "||", diff --git a/packages/compiler/src/frontend/lowering/json-require.ts b/packages/compiler/src/frontend/lowering/json-require.ts index 7c688dcda..927745253 100644 --- a/packages/compiler/src/frontend/lowering/json-require.ts +++ b/packages/compiler/src/frontend/lowering/json-require.ts @@ -36,7 +36,7 @@ export function jsonRequireTarget( if (call === undefined || !ts.isCallExpression(call)) return null; const spec = requireSpecOf(call); if (spec === null || !ts.isIdentifier(call.expression)) return null; - const sf = call.getSourceFile(); + const sf = ts.getSourceFileOfNode(call); if (!isCjsJsFile(sf, lowerer.program)) return null; if (createRequireCalleeFileOf(lowerer, call.expression) !== null) return null; const target = resolveImport(lowerer.program, sf, spec, "require"); diff --git a/packages/compiler/src/frontend/lowering/key-order-witness.ts b/packages/compiler/src/frontend/lowering/key-order-witness.ts index ba8001792..437a29563 100644 --- a/packages/compiler/src/frontend/lowering/key-order-witness.ts +++ b/packages/compiler/src/frontend/lowering/key-order-witness.ts @@ -224,12 +224,30 @@ export function applyKeyOrderWitnesses(lowerer: Lowerer, fns: readonly IrFunctio const loops: number[] = []; let loopIds = 0; const loopKey = (): string => loops.join("/"); + /** Bindings initialized by another binding's reference (`const view = + * value`, the snapshot a union-receiver write takes), so writes through + * either count as writes through the one object. */ + const aliases = new Map(); + const bindingOf = (obj: IrExpr): string | null => { + let o = obj; + while (o.kind === "unionNarrow" || o.kind === "downcast") o = o.value; + if (o.kind !== "varRef") return null; + let id = o.localId; + const seen = new Set(); + for (;;) { + const alias = aliases.get(id); + if (!alias || alias.fn !== fnName || inits.get(id)?.count !== 1 || seen.has(id)) return id; + seen.add(id); + id = alias.target; + } + }; const noteInit = (id: string, init: IrExpr): void => { const previous = inits.get(id); if (previous !== undefined) { previous.count++; return; } + if (init.kind === "varRef") aliases.set(id, { target: init.localId, fn: fnName }); const fresh = init.kind === "recordLit" || init.kind === "recordClone" || @@ -315,7 +333,7 @@ export function applyKeyOrderWitnesses(lowerer: Lowerer, fns: readonly IrFunctio field: st.field, kind: st.value.kind === "fieldAbsent" ? "delete" : "write", fn: fnName, - binding: st.obj.kind === "varRef" ? st.obj.localId : null, + binding: bindingOf(st.obj), loop: loopKey(), pos, }); diff --git a/packages/compiler/src/frontend/lowering/lower-assert.ts b/packages/compiler/src/frontend/lowering/lower-assert.ts index cb31ecc79..460f4d53a 100644 --- a/packages/compiler/src/frontend/lowering/lower-assert.ts +++ b/packages/compiler/src/frontend/lowering/lower-assert.ts @@ -887,10 +887,10 @@ export function lowerAssertDirectCall( * call's SOURCE TEXT (Node re-reads the file at runtime — the frontend has * the same bytes at compile time). */ function lowerAssertOk(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr { - requireStatementPosition(lowerer, expr, expr.expression.getText()); + requireStatementPosition(lowerer, expr, ts.getTextOfNode(expr.expression)); if (expr.arguments.nodes.length < 1 || expr.arguments.nodes.length > 2) { lowerer.noLowering( - `${expr.expression.getText()} with ${expr.arguments.nodes.length} arguments`, + `${ts.getTextOfNode(expr.expression)} with ${expr.arguments.nodes.length} arguments`, expr, "the supported forms are assert(value) and assert(value, message)", ); @@ -912,7 +912,7 @@ function lowerAssertOk(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): // Node reads the CALLSITE LINE and slices the expression out of it // (internal/errors/error_source.js) — a call spanning lines truncates // at the first newline, so the compile-time text does too. - const source = expr.getText().split("\n", 1)[0]!; + const source = ts.getTextOfNode(expr).split("\n", 1)[0]!; const text = `The expression evaluated to a falsy value:\n\n ${source}\n`; msg = { kind: "strLit", value: text, type: STRING, loc }; generated = true; diff --git a/packages/compiler/src/frontend/lowering/lower-async-iteration.ts b/packages/compiler/src/frontend/lowering/lower-async-iteration.ts index f095c5b9c..0e48f2040 100644 --- a/packages/compiler/src/frontend/lowering/lower-async-iteration.ts +++ b/packages/compiler/src/frontend/lowering/lower-async-iteration.ts @@ -137,7 +137,9 @@ function literalBooleanOption( function nodeReadablePlan(lowerer: Lowerer, expression: ts.Expression): NodeReadablePlan | null { const direct = - lowerer.stageSourceType(expression) ?? lowerer.mapTypeOf(lowerer.typeOf(expression)); + lowerer.receiverTypeOverrides.get(expression) ?? + lowerer.stageSourceType(expression) ?? + lowerer.mapTypeOf(lowerer.typeOf(expression)); if (direct?.kind === "object") { const sides = streamSidesOf(lowerer, lowerer.classes.get(direct.className)); if (sides === "r" || sides === "rw") { diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 316ebadf9..12f7409b9 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -1,7 +1,11 @@ +import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js"; import { guardNullProto, nullProtoRecordShape } from "./null-prototype.js"; +import { lowerDynStreamMemberCall } from "./dyn-streams.js"; +import { lowerBuiltinClassCall } from "./builtins/builtin-class-dyn.js"; import { refuseModuleMemberThis } from "./module-member-this.js"; import { lowerUnionEquality, tagEqualityMayMissAlias } from "./strict-equality.js"; import { lowerHashStreamCall } from "./builtins/hash-stream.js"; +import { lowerWebAssemblyValueOf } from "./builtins/webassembly.js"; import { lowerResponseStaticJson } from "./lower-stdlib.js"; import { lowerArrayCallbackCall } from "./containers/array-callback-call.js"; /** Dispatch source calls, builtin members and receiver-specific operations. @@ -35,6 +39,7 @@ import { import { cjsExportCallIgnoresThis, cjsInFileExportGlobalOf } from "./cjs-exports-object.js"; import { lowerGenMethodCall } from "./lower-generators.js"; import { lowerClassSymbolMethodCall } from "./symbol-methods.js"; +import { lowerReadlineDisposeCall } from "./builtins/readline.js"; import { errorToStringCall } from "./error-methods.js"; import { lowerClassPrototypeAssign, lowerClassPrototypeDescriptors } from "./class-prototypes.js"; import { @@ -148,6 +153,7 @@ import { import { fencePerfEntryKeyWalk, isPerfEntryType, + lowerHistogramMethodCall, lowerPerfHooksCall, perfEntryTag, } from "./builtins/performance.js"; @@ -226,6 +232,7 @@ import { } from "./optional-arguments.js"; import { fenceSymbolFieldCopy } from "./symbol-fields.js"; import { lowerClassDataDescriptor, lowerClassDescriptorRead } from "./class-descriptors.js"; +import { lowerClassFieldDefine, lowerClassIntegrity } from "./class-attributes.js"; import { classStaticDataFor } from "./class-static-data.js"; import { lowerUrlFactory } from "./lower-url.js"; import { nullishValueUnitOf } from "./binding-analysis.js"; @@ -246,11 +253,12 @@ import { genericCallInstance, } from "./generic-functions.js"; +import { codeGenerationCalleeOf, lowerCodeGenerationCall } from "./code-generation.js"; + const ZLIB_STREAM_METHODS: ReadonlySet = new Set(["flush", "params", "close", "reset"]); export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const loc = locOf(expr); - // Object's checker return type is `any`. Claim known object inputs // before generic checked-dynamic dispatch tries to load the constructor. if ( @@ -262,10 +270,21 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if (object) return object; } + // `EventEmitter.call(this)` in a constructor function: the untyped + // EventEmitter constructor initializes the receiver. + const builtinClassCall = lowerBuiltinClassCall(lowerer, expr); + if (builtinClassCall) return builtinClassCall; + // require() of a JSON document: the shared parsed object. const json = lowerJsonRequireCall(lowerer, expr); if (json) return json; + // eval(...) and Function(...): Node's EvalError under + // --disallow-code-generation-from-strings (code-generation.ts). + { + const codeGeneration = codeGenerationCalleeOf(lowerer, expr.expression); + if (codeGeneration !== null) return lowerCodeGenerationCall(lowerer, expr, codeGeneration); + } // 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 @@ -659,7 +678,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const isStorage = parts.length > 0 && parts.every((part) => { - const name = part.getSymbol()?.name; + const name = ts.symbolNameOf(part.getSymbol()); return name === "ArrayBuffer" || name === "SharedArrayBuffer"; }); if (isStorage) { @@ -833,7 +852,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { (source.type.kind === "dyn" || source.type.kind === "func" || (source.type.kind === "union" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(source.type)) || ((source.type.kind === "map" || source.type.kind === "set") && lowerer.dynConvertible(source.type)) || @@ -849,7 +868,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerer.lowerExprExpecting(ts.isSpreadElement(arg) ? arg.expression : arg, DYN), ); const spreads = expr.arguments.nodes.flatMap((arg, index) => - ts.isSpreadElement(arg) ? [{ arg: index, what: arg.expression.getText() }] : [], + ts.isSpreadElement(arg) ? [{ arg: index, what: ts.getTextOfNode(arg.expression) }] : [], ); return { kind: "seqExpr", @@ -860,7 +879,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, ...(spreads.length > 0 ? { spreads } : {}), type: DYN, @@ -1165,7 +1184,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // argument list. Typed .ts spreads keep completeArgs' rest packing. const jsSpreadArgs = expr.arguments.nodes.some((a) => ts.isSpreadElement(a)) && - isJsSourceFile(expr.getSourceFile()); + isJsSourceFile(ts.getSourceFileOfNode(expr)); if (lowerer.isTopLevelFnSymbol(expr.expression) && !lowerer.peekLocal(expr.expression)) { // `import g = N.f; g()` — the alias's own source-order guards // (a no-op for every non-import= binding). @@ -1375,12 +1394,12 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if ( g && (g.type.kind === "func" || g.type.kind === "dyn") && - !cjsExportCallIgnoresThis(lowerer, expr.getSourceFile(), expr.expression.name.text) + !cjsExportCallIgnoresThis(lowerer, ts.getSourceFileOfNode(expr), expr.expression.name.text) ) { lowerer.unsupported( "SC1090", expr, - `calling '${expr.expression.getText()}' through the module's exports object when the function may read 'this' (Node binds it to the exports object, which compiles to a static table — call a local function instead)`, + `calling '${ts.getTextOfNode(expr.expression)}' through the module's exports object when the function may read 'this' (Node binds it to the exports object, which compiles to a static table — call a local function instead)`, ); } if (g && (g.type.kind === "func" || g.type.kind === "dyn")) { @@ -1395,7 +1414,12 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { } if (ts.isPropertyAccessExpression(expr.expression)) { const intrinsic = + // Checked-dynamic stream slots dispatch per value (dyn-streams.ts). + lowerDynStreamMemberCall(lowerer, expr, expr.expression) ?? fenceNodeModuleMutationCall(lowerer, expr, expr.expression) ?? + // `f.apply(thisArg, args)` forwarding a fixed argument tuple (typed + // `arguments`, a tuple rest parameter): a direct call. + lowerForwardingApply(lowerer, expr, expr.expression) ?? // Builtin namespace imports first (`fs.readFileSync(...)` where fs // is `import * as fs from "node:fs"`): the same tables and fences // as named builtin imports — before anything below tries to lower @@ -1407,6 +1431,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // .bind(performance) function value over the runtime's // process-start-anchored monotonic clock. lowerPerfHooksCall(lowerer, expr, expr.expression) ?? + lowerHistogramMethodCall(lowerer, expr, expr.expression) ?? // The composed crypto pattern (randomBytes(n).toString(enc)) // — its receiver is a Buffer-typed CALL no other lowering claims. lowerer.lowerCryptoComposedCall(expr, expr.expression) ?? @@ -1569,7 +1594,11 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // monomorphized against the defining literal's declaration. lowerObjLitGenericMethodCall(lowerer, expr, expr.expression); if (intrinsic) return intrinsic; - if (isJsSourceFile(expr.getSourceFile())) { + { + const boxed = lowerDynReceiverMethodCall(lowerer, expr, expr.expression, true); + if (boxed) return boxed; + } + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const staticField = lowerStaticFieldRead(lowerer, expr.expression); if (staticField?.type.kind === "dyn" && !expr.arguments.nodes.some(ts.isSpreadElement)) { return { @@ -1637,7 +1666,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { lowerer.unsupported( "SC1090", expr, - `calls through function-with-properties values ('${expr.expression.expression.getText()}' is callable AND carries members — the chalk shape; no hybrid representation exists yet: export the base and the property as separate functions)`, + `calls through function-with-properties values ('${ts.getTextOfNode(expr.expression.expression)}' is callable AND carries members — the chalk shape; no hybrid representation exists yet: export the base and the property as separate functions)`, ); } } @@ -1662,7 +1691,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { ); } } - lowerer.unsupported("SC1090", expr, `method calls like '${expr.expression.getText()}'`); + lowerer.unsupported("SC1090", expr, `method calls like '${ts.getTextOfNode(expr.expression)}'`); } // `s[Symbol.iterator]()` on a string: the string iterator as a value. @@ -1683,6 +1712,8 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if (timersDispose) return timersDispose; const symbolMethod = lowerClassSymbolMethodCall(lowerer, expr); if (symbolMethod) return symbolMethod; + const readlineDispose = lowerReadlineDisposeCall(lowerer, expr); + if (readlineDispose) return readlineDispose; lowerer.fenceUnsupportedFetchConstructorMember(expr.expression); lowerer.fenceStaticResponseMember(expr.expression, "call"); lowerer.fenceStaticHeadersMember(expr.expression, "call"); @@ -1699,7 +1730,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { ) { const access = expr.expression; const value = tryLowerExpression(lowerer, access.expression); - if (value?.type.kind === "array" && !isJsSourceFile(expr.getSourceFile())) { + if (value?.type.kind === "array" && !isJsSourceFile(ts.getSourceFileOfNode(expr))) { const callback = lowerArrayCallbackCall(lowerer, expr, access, value); if (callback) return callback; } @@ -1734,7 +1765,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: expr.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -1853,7 +1884,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression) - ? expr.expression.getText() + ? ts.getTextOfNode(expr.expression) : ts.isIdentifier(expr.expression) ? expr.expression.text : "value"; @@ -1889,6 +1920,19 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // or marshals the callee and applies through a runtime-built argument // list (lowerSpreadArgsCall). Shapes outside its lanes fall through // to the historical fences. + // Forwarded fixed tuples (`fn(...args)` over a tuple rest parameter or a + // typed `arguments`) have a compile-time arity: complete them into the + // callee's positional slots directly, without the runtime-arity lane. + if ( + callee.type.rest !== true && + expr.arguments.nodes.some(ts.isSpreadElement) && + expr.arguments.nodes.every( + (a) => !ts.isSpreadElement(a) || isForwardedTupleSpread(lowerer, a.expression), + ) + ) { + const args = completeFuncValueArgs(lowerer, expr, callee.type, loc); + return { kind: "callValue", callee, args, type: callee.type.ret, loc }; + } if (expr.arguments.nodes.some((a) => ts.isSpreadElement(a))) { const spreadServed = lowerSpreadArgsCall(lowerer, expr, callee, loc); if (spreadServed) return spreadServed; @@ -1902,7 +1946,7 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { // per use like every any-origin value. if ( (expr.arguments.nodes.length > callee.type.params.length || callee.type.rest === true) && - (isJsSourceFile(expr.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) || (callee.type.rest === true && callee.type.restAbi === undefined)) && !expr.arguments.nodes.some((a) => ts.isSpreadElement(a)) && canBoxFuncIntoDyn( @@ -1934,7 +1978,7 @@ function lowerCheckedCallableValue( callee.type.kind !== "func" || !( (callee.type.rest === true && callee.type.restAbi === undefined) || - (isJsSourceFile(call.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(call)) && call.arguments.nodes.length > callee.type.params.length) ) || !lowerer.dynConvertible(callee.type) @@ -2499,7 +2543,7 @@ function lowerArrayPrototypeBorrowCall( access: ts.PropertyAccessExpression, ): IrExpr | null { if ( - !isJsSourceFile(call.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(call)) || call.questionDotToken || access.questionDotToken || access.name.text !== "call" || @@ -2711,7 +2755,7 @@ function lowerObjectOwnCall( result = guardNullProto( receiverRef, type.shapeId, - `${methodAccess.getText()} is not a function`, + `${ts.getTextOfNode(methodAccess)} is not a function`, result, loc, ); @@ -3274,6 +3318,13 @@ const ITERATOR_HELPER_METHODS = new Set([ "find", ]); +/** A call expression, through parentheses: its value has no other holder. */ +function isFreshCallResult(node: ts.Expression): boolean { + let e = node; + while (ts.isParenthesizedExpression(e)) e = e.expression; + return ts.isCallExpression(e); +} + /** METHOD calls on dyn receivers (`pkg.name.replace(...)`, `rawName.split`, * `ws.packages.filter(...)` — JSON.parse-derived values): validate the * receiver's dyn kind, extract, and ride the STATIC method machinery — the @@ -3291,6 +3342,7 @@ function lowerDynReceiverMethodCall( lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, + boxTypedTemporary = false, ): IrExpr | null { if (lowerer.chainBlocked(call, access)) return null; if (stdlibGlobalNameOf(lowerer, access.expression) !== null) { @@ -3307,7 +3359,13 @@ function lowerDynReceiverMethodCall( // array — scalar narrowings bridge through maybeNarrow's dynCheck and // take the ordinary typed paths, but there is no static home for an // any-elemented array). Typed receivers keep their own lowerings. - const recvTs = lowerer.typeOf(access.expression); + // An optional step the chain already guarded (`s?.m(x)`) has a receiver + // proven non-nullish. + const rawRecvTs = lowerer.typeOf(access.expression); + const recvTs = + access.questionDotToken !== undefined && lowerer.chainHandled.has(access) + ? lowerer.checker.getNonNullableType(rawRecvTs) + : rawRecvTs; if (lowerer.classImplementedProtocol(recvTs)) { const viewed = pickViewGenericMethodCall(lowerer, call, access, recvTs); if (viewed) return viewed; @@ -3321,7 +3379,7 @@ function lowerDynReceiverMethodCall( const recvSymbol = recvTs.getSymbol(); if ( recvSymbol && - CHECKED_ITERATOR_TYPES.has(recvSymbol.name) && + CHECKED_ITERATOR_TYPES.has(ts.symbolName(recvSymbol)) && lowerer.isStdlibSymbol(recvSymbol) && ITERATOR_HELPER_METHODS.has(access.name.text) && lowerer.isStdlibMember(access) @@ -3389,7 +3447,7 @@ function lowerDynReceiverMethodCall( kind: "dynCall", callee: member, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -3411,7 +3469,7 @@ function lowerDynReceiverMethodCall( return null; if ( probed?.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.unions .get(probed.type.unionId) ?.arms.every( @@ -3461,7 +3519,7 @@ function lowerDynReceiverMethodCall( if ( checkerUntyped && recv.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && isStringMethodName(access.name.text) && lowerer.unions .get(recv.type.unionId) @@ -3475,6 +3533,22 @@ function lowerDynReceiverMethodCall( // 'unknown' slot does. if (checkerUntyped && DYN_HANDLE_KINDS.has(recv.type.kind)) recv = lowerer.coerceToExpected(recv, DYN); + // The last-resort retry (boxTypedTemporary — every other lowering + // already declined): a checker-`any` receiver whose value is a FRESH + // typed result of an earlier step in the chain (`out.split("\n")` + // extracted the string and answered a native string[]) has no typed + // home under its `any` spelling, so it boxes into the checked-dynamic + // tree and the call dispatches on the runtime kind — `.map`, `.pop`, + // `.filter` on that array run JS-exact. Only call results qualify: + // the box copies, and a temporary has no other holder to observe it. + if ( + boxTypedTemporary && + checkerUntyped && + recv.type.kind !== "dyn" && + isFreshCallResult(access.expression) && + lowerer.dynConvertible(recv.type) + ) + recv = lowerer.coerceToExpected(recv, DYN); if (recv.type.kind !== "dyn") return null; if ( access.name.text === "normalize" && @@ -3531,7 +3605,7 @@ function lowerDynReceiverMethodCall( kind: "dynInvoke", recv: value, method: "normalize", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: hasArgument ? [argument] : [], type: DYN, loc, @@ -3567,7 +3641,7 @@ function lowerDynReceiverMethodCall( // dynKeyGet cannot stand in for JavaScript's prototype lookup. Only // an authored declaration can justify the own-member path. const authored = prop.declarations.some( - (decl) => !lowerer.isStdlibFile(decl.getSourceFile()), + (decl) => !lowerer.isStdlibFile(ts.getSourceFileOfNode(decl)), ); return ( authored && @@ -3609,7 +3683,7 @@ function lowerDynReceiverMethodCall( lowerer, call, access.name.text, - access.getText(), + ts.getTextOfNode(access), recv, ); if (primitive) return primitive; @@ -3624,7 +3698,7 @@ function lowerDynReceiverMethodCall( args: [ recv, argument ? lowerer.lowerExprExpecting(argument, DYN) : dynUndefinedExpr(locOf(call)), - { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, ], type: STRING, loc: locOf(call), @@ -3634,10 +3708,15 @@ function lowerDynReceiverMethodCall( // answers each kind's own: primitives and plain objects themselves, a // Date its time value, an own or inherited valueOf member its result. if (access.name.text === "valueOf" && call.arguments.nodes.length === 0) { + const webAssembly = lowerWebAssemblyValueOf(lowerer, access.expression, recv, locOf(call)); + if (webAssembly !== null) return webAssembly; return { kind: "libCall", fn: "dyn.valueOf", - args: [recv, { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }], + args: [ + recv, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, + ], type: DYN, loc: locOf(call), }; @@ -3725,15 +3804,15 @@ function lowerDynReceiverMethodCall( // statement cannot leave the expression). const stringRefusal = (read: IrExpr): IrExpr => { if (!stringOnlyName) return read; - const sf = call.getSourceFile(); - const line = ts.getLineAndCharacterOfPosition(sf, call.getStart(sf)).line + 1; + const sf = ts.getSourceFileOfNode(call); + const line = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(call)).line + 1; return { kind: "ternary", cond: { kind: "dynTest", test: "string", value: receiver, type: BOOL, loc }, then: nodeThrowExpr( 0, "SC1090", - `method calls like '${access.getText()}' on a string are not supported yet [SC1090 at ${sf.fileName}:${line}]`, + `method calls like '${ts.getTextOfNode(access)}' on a string are not supported yet [SC1090 at ${sf.fileName}:${line}]`, DYN, loc, ), @@ -3769,7 +3848,7 @@ function lowerDynReceiverMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -4059,7 +4138,7 @@ export function lowerDynDispatchMethodCall( ): IrExpr | null { const method = access.name.text; if ( - (!DYN_DISPATCH_METHODS.has(method) && !isJsSourceFile(call.getSourceFile())) || + (!DYN_DISPATCH_METHODS.has(method) && !isJsSourceFile(ts.getSourceFileOfNode(call))) || lowerer.chainBlocked(call, access) ) return null; @@ -4168,7 +4247,7 @@ export function lowerDynDispatchMethodCall( kind: "dynInvoke", recv: { kind: "varRef", localId: saved.id, type: DYN, loc: locOf(access.expression) }, method, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [dynUndefinedExpr(locOf(call.arguments.nodes[0]!))], type: DYN, loc: locOf(call), @@ -4190,7 +4269,7 @@ export function lowerDynDispatchMethodCall( kind: "dynInvoke", recv, method, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc: locOf(call), @@ -4341,6 +4420,14 @@ function lowerCaughtPredicateCall( const paramSymbol = lowerer.checker.getSymbolAtLocation(param.name); if (!paramSymbol) return null; const ret = decl.body.statements.nodes.length === 1 ? decl.body.statements.nodes[0] : undefined; + // A longer guard whose parameter is `unknown` (or `any`) takes the caught + // value as an ordinary checked-dynamic argument: the call path converts + // the binding, and the narrowed binding reads through the same value. + if ( + (!ret || !ts.isReturnStatement(ret) || !ret.expression) && + lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(paramSymbol))?.kind === "dyn" + ) + return null; if (!ret || !ts.isReturnStatement(ret) || !ret.expression) { lowerer.unsupported( "SC1090", @@ -4402,7 +4489,7 @@ function lowerNumberToStringCall( call.arguments.nodes[0] ? lowerer.lowerExprExpecting(call.arguments.nodes[0], DYN) : dynUndefinedExpr(locOf(call)), - { kind: "strLit", value: access.getText(), type: STRING, loc: locOf(call) }, + { kind: "strLit", value: ts.getTextOfNode(access), type: STRING, loc: locOf(call) }, ], type: STRING, loc: locOf(call), @@ -4525,7 +4612,7 @@ function lowerDefaultToStringCall( // A null-prototype record (Object.groupBy) has no toString: Node // throws " is not a function". const nullProtoT = nullProtoRecordShape(lowerer, recvT); - const missing = nullProtoT ? `${access.getText()} is not a function` : null; + const missing = nullProtoT ? `${ts.getTextOfNode(access)} is not a function` : null; if (!missing && pureObjectToStringReceiver(access.expression)) return constant; const recv = lowerer.lowerExpr(access.expression); const key = `objToStr:${typeKey(recv.type)}${missing ? `:${missing}` : ""}`; @@ -4676,7 +4763,7 @@ function lowerPrimitiveProtoCall( (name === "toFixed" || name === "toExponential") && call.arguments.nodes.length <= 1 && !call.arguments.nodes.some(ts.isSpreadElement) && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { const operand = lowerer.lowerExpr(recv); if ( @@ -4689,7 +4776,7 @@ function lowerPrimitiveProtoCall( lowerer, call, name, - `${recv.getText()}.${name}`, + `${ts.getTextOfNode(recv)}.${name}`, lowerer.coerceToExpected(operand, DYN), ); if (dispatched) return dispatched; @@ -4702,7 +4789,7 @@ function lowerPrimitiveProtoCall( kind: "dynInvoke", recv: lowerer.coerceToExpected(operand, DYN), method: name, - calleeName: `${recv.getText()}.${name}`, + calleeName: `${ts.getTextOfNode(recv)}.${name}`, args: call.arguments.nodes.map((argument) => lowerer.lowerExprExpecting(argument, DYN)), type: DYN, loc: locOf(call), @@ -4712,7 +4799,7 @@ function lowerPrimitiveProtoCall( name === "toPrecision" && call.arguments.nodes.length <= 1 && !call.arguments.nodes.some(ts.isSpreadElement) && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && recvKind === "union" ) { const operand = lowerer.lowerExpr(recv); @@ -4721,7 +4808,7 @@ function lowerPrimitiveProtoCall( lowerer, call, name, - `${recv.getText()}.${name}`, + `${ts.getTextOfNode(recv)}.${name}`, lowerer.coerceToExpected(operand, DYN), ); if (dispatched) return dispatched; @@ -4941,9 +5028,10 @@ function isSoleAbstractMethod(decl: ts.FunctionDeclaration | ts.MethodDeclaratio if (!ts.getModifiers(decl)?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword)) return false; const owner = decl.parent; if (!ts.isClassDeclaration(owner) && !ts.isClassExpression(owner)) return false; - const name = decl.name.getText(); + const name = ts.getTextOfNode(decl.name); return !owner.members.nodes.some( - (member) => member !== decl && ts.isMethodDeclaration(member) && member.name.getText() === name, + (member) => + member !== decl && ts.isMethodDeclaration(member) && ts.getTextOfNode(member.name) === name, ); } @@ -4961,7 +5049,7 @@ function reconcileOverloadReturn( call.type.kind === "object" && rdecl && ts.isMethodDeclaration(rdecl) && - (!isJsSourceFile(rdecl.getSourceFile()) || returnsOnlyThis(rdecl)) + (!isJsSourceFile(ts.getSourceFileOfNode(rdecl)) || returnsOnlyThis(rdecl)) ) { call = lowerer.maybeNarrow(call, expr); } @@ -5123,7 +5211,7 @@ export function lowerTaggedTemplate(lowerer: Lowerer, expr: ts.TaggedTemplateExp let callee = lowerer.lowerExpr(expr.tag); if ( callee.type.kind === "func" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && lowerer.dynConvertible(callee.type) ) { callee = { kind: "dynFrom", value: callee, type: DYN, loc }; @@ -5141,7 +5229,7 @@ export function lowerTaggedTemplate(lowerer: Lowerer, expr: ts.TaggedTemplateExp ]; const calleeName = ts.isPropertyAccessExpression(expr.tag) || ts.isElementAccessExpression(expr.tag) - ? expr.tag.getText() + ? ts.getTextOfNode(expr.tag) : ts.isIdentifier(expr.tag) ? expr.tag.text : "value"; @@ -5211,7 +5299,7 @@ function loweredTemplateStrings( loc, }) : null; - const key = `templateStringsGlobal:${loc.file}:${expr.template.getStart()}:${raw ? "checked" : "typed"}`; + const key = `templateStringsGlobal:${loc.file}:${ts.tokenStartOfNode(expr.template)}:${raw ? "checked" : "typed"}`; let getter = lowerer.valueHelpers.get(key); if (!getter) { const site = lowerer.globalsList.length; @@ -5926,7 +6014,7 @@ export function lowerPromiseMethodCall( kind: "dynInvoke", recv: lowerer.coerceToExpected(receiver, DYN), method: "finally", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: [lowerer.coerceToExpected(cb, DYN)], type: DYN, loc, @@ -6027,7 +6115,7 @@ export function lowerPromiseMethodCall( if ( member === "catch" && (inner.kind === "dyn" || - (isJsSourceFile(call.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(call)) && (inner.kind === "void" || lowerer.dynConvertible(inner)))) ) { let cb = lowerer.lowerExpr(call.arguments.nodes[0]!); @@ -6771,6 +6859,99 @@ function recordHasOwnHelper(lowerer: Lowerer, shapeId: string, loc: SrcLoc): str return helper; } +/** Whether `Object.assign(instance, record)` can copy every field of the + * record shape into a field the class declares with the same type: a plain + * record (no tuple, index signature or accessor slots) and a compiled + * class whose chain has no builtin or runtime-provided storage and no + * accessor for the written names. */ +function classAssignPlan(lowerer: Lowerer, className: string, srcShapeId: string): boolean { + const info = lowerer.classes.get(className); + const shape = lowerer.shapes.get(srcShapeId); + if (!info || !shape || shape.tuple || shape.indexValue || shapeHasAccessorSlots(shape)) + return false; + for (let c: ClassInfo | null = info; c; c = c.base) { + if (c.builtinError || c.builtinEmitter || c.builtinStream !== undefined || c.def.runtime) + return false; + } + return shape.fields.every((f) => { + const declared = info.fields.get(f.name); + return ( + declared !== undefined && + (typeEquals(declared, f.type) || lowerer.widthLiftPlan(f.type, declared) !== null) && + !lowerer.findMethodOn(info, f.name) && + !lowerer.findMethodOn(info, `get:${f.name}`) && + !lowerer.findMethodOn(info, `set:${f.name}`) + ); + }); +} + +/** Interned `%obj.assignClass.(o, s)` — Object.assign's per-field + * write of a record into a class instance (classAssignPlan's gate): + * optional source fields write only when present, and the instance + * returns. */ +function classAssignHelper( + lowerer: Lowerer, + className: string, + srcShapeId: string, + loc: SrcLoc, +): string | null { + if (!classAssignPlan(lowerer, className, srcShapeId)) return null; + const key = `obj.assignClass:${className}:${srcShapeId}`; + const existing = lowerer.arrHofHelpers.get(key); + if (existing) return existing; + const helper = `%obj.assignClass.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(key, helper); + const sShape = lowerer.shapes.get(srcShapeId)!; + const oT: IrType = { kind: "object", className }; + const sT: IrType = { kind: "record", shapeId: srcShapeId }; + const oRef: IrExpr = { kind: "varRef", localId: "o.0", type: oT, loc }; + const sRef: IrExpr = { kind: "varRef", localId: "s.0", type: sT, loc }; + const body: IrStmt[] = []; + const info = lowerer.classes.get(className)!; + for (const f of sShape.fields) { + const declared = info.fields.get(f.name)!; + const read: IrExpr = { + kind: "recordGet", + obj: sRef, + shapeId: srcShapeId, + field: f.name, + type: f.type, + loc, + }; + const lift = typeEquals(declared, f.type) ? null : lowerer.widthLiftPlan(f.type, declared); + const set: IrStmt = { + kind: "fieldSet", + obj: oRef, + className, + field: f.name, + value: lift === null ? read : lowerer.applyWidthLift(lift, read, declared, loc), + loc, + }; + const present = lowerer.recordFieldPresent(sRef, srcShapeId, f.name, loc); + body.push( + present.kind === "recordHas" + ? { kind: "if", cond: present, then: [set], else_: null, loc } + : set, + ); + } + body.push({ kind: "return", value: oRef, loc }); + lowerer.liftedFns.push({ + name: helper, + params: [ + { localId: "o.0", name: "o", type: oT }, + { localId: "s.0", name: "s", type: sT }, + ], + returnType: oT, + locals: [ + { id: "o.0", name: "o", type: oT, mutable: true }, + { id: "s.0", name: "s", type: sT, mutable: true }, + ], + body, + loc, + }); + return helper; +} + /** Interned `%obj.assign.(t, s)` — Object.assign's per-field copy * over signature-free records (every source field lands on a same-named, * same-typed target field — the caller's gate): optional source fields @@ -6789,6 +6970,7 @@ function recordAssignHelper( const helper = `%obj.assign.${lowerer.arrHofHelpers.size}`; lowerer.arrHofHelpers.set(key, helper); const sShape = lowerer.shapes.get(srcShapeId)!; + const tShape = lowerer.shapes.get(targetShapeId)!; const tT: IrType = { kind: "record", shapeId: targetShapeId }; const sT: IrType = { kind: "record", shapeId: srcShapeId }; const tRef: IrExpr = { kind: "varRef", localId: "t.0", type: tT, loc }; @@ -6803,12 +6985,13 @@ function recordAssignHelper( type: f.type, loc, }; + const targetType = tShape.fields.find((x) => x.name === f.name)!.type; const set: IrStmt = { kind: "recordSet", obj: tRef, shapeId: targetShapeId, field: f.name, - value: get, + value: typeEquals(targetType, f.type) ? get : lowerer.coerceToExpected(get, targetType), loc, }; const present = lowerer.recordFieldPresent(sRef, srcShapeId, f.name, loc); @@ -7326,7 +7509,7 @@ function lowerObjectStaticCall( const prototype = lowerClassPrototypeAssign(lowerer, call); if (prototype) return prototype; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && call.arguments.nodes.length > 0 && !call.arguments.nodes.some(ts.isSpreadElement) ) { @@ -7356,6 +7539,8 @@ function lowerObjectStaticCall( }; } } + const onError = lowerObjectAssignOntoError(lowerer, call); + if (onError) return onError; const hybrid = lowerObjectAssignHybrid(lowerer, call); if (hybrid) return hybrid; // `Object.assign({}, lit)` — an EMPTY fresh-literal target and one @@ -7412,6 +7597,28 @@ function lowerObjectStaticCall( return { kind: "libCall", fn: "dyn.assign", args: [target, source], type: DYN, loc }; } } + // A CLASS-INSTANCE target and a plain record source whose fields are + // all fields the class declares with the same types + // (`Object.assign(this, { retries: 3 })`): the per-field write into + // the instance, returning it — the instance mutates in place. + if (tProbe?.type.kind === "object" && sProbe?.type.kind === "record") { + const helper = classAssignPlan(lowerer, tProbe.type.className, sProbe.type.shapeId); + if (helper) { + const loc = locOf(call); + const target = lowerer.lowerExpr(call.arguments.nodes[0]!); + const source = lowerer.lowerExpr(call.arguments.nodes[1]!); + if (target.type.kind === "object" && source.type.kind === "record") { + const name = classAssignHelper( + lowerer, + target.type.className, + source.type.shapeId, + loc, + ); + if (name) + return { kind: "call", callee: name, args: [target, source], type: target.type, loc }; + } + } + } if (tProbe?.type.kind === "record" && sProbe?.type.kind === "record") { const tShape = lowerer.shapes.get(tProbe.type.shapeId); const sShape = lowerer.shapes.get(sProbe.type.shapeId); @@ -7426,7 +7633,12 @@ function lowerObjectStaticCall( !shapeHasAccessorSlots(sShape) && sShape.fields.every((sf) => { const tf = tShape.fields.find((x) => x.name === sf.name); - return tf !== undefined && typeEquals(tf.type, sf.type); + // A source value converts into a wider target field (`{ bail: + // true }` into `bail?: boolean`). + return ( + tf !== undefined && + (typeEquals(tf.type, sf.type) || lowerer.coercibleValue(sf.type, tf.type)) + ); }); if (ok) { const loc = locOf(call); @@ -7444,6 +7656,37 @@ function lowerObjectStaticCall( } } } + // `throw Object.assign(record, { extra })`: an error-shaped record given + // a member its type lacks, thrown at once. A fixed record cannot grow + // the member, so the throw carries a checked-dynamic copy of the target + // with the source's members added. The copy is unobservable: control + // leaves with the throw, so nothing on that path reads the target again. + { + let parent: ts.Node | undefined = call.parent; + while (parent && ts.isParenthesizedExpression(parent)) parent = parent.parent; + const argNodes = call.arguments.nodes; + if ( + parent && + ts.isThrowStatement(parent) && + argNodes.length === 2 && + !argNodes.some((a) => ts.isSpreadElement(a)) + ) { + const tProbe = tryLowerExpression(lowerer, argNodes[0]!); + const sProbe = tryLowerExpression(lowerer, argNodes[1]!); + if ( + tProbe?.type.kind === "record" && + lowerer.dynConvertible(tProbe.type) && + sProbe && + (sProbe.type.kind === "dyn" || lowerer.dynConvertible(sProbe.type)) + ) { + const loc = locOf(call); + const target = lowerer.coerceToExpected(lowerer.lowerExpr(argNodes[0]!), DYN); + const source = lowerer.coerceToExpected(lowerer.lowerExpr(argNodes[1]!), DYN); + if (target.type.kind === "dyn" && source.type.kind === "dyn") + return { kind: "libCall", fn: "dyn.assign", args: [target, source], type: DYN, loc }; + } + } + } // `Object.assign(target, ...sources)` over a CHECKED-DYNAMIC target // — the n-ary/spread form (`Object.assign({}, ...plugins.map(p => // p.options), coreOptions)`, support.js's option-table merge). The @@ -7502,6 +7745,25 @@ function lowerObjectStaticCall( const srcNode = spread ? argNode.expression : argNode; const rawSource = lowerer.lowerExpr(srcNode); fenceSymbolFieldCopy(lowerer, srcNode, rawSource.type, spread); + // A class instance copied into a fresh object + // (`Object.assign({}, instance)`): the result is a plain + // object holding the instance's own fields, never an + // instance, so a result the checker types as the class has + // no representation (the checked cast would throw). + if ( + rawSource.type.kind === "object" && + freshLiteralTarget && + !ts.isExpressionStatement(call.parent) + ) { + const resultT = lowerer.mapTypeOf(lowerer.typeOf(call)); + if (resultT !== null && resultT.kind === "object") { + lowerer.unsupported( + "SC1090", + srcNode, + `Object.assign copying a '${lowerer.fmt(rawSource.type)}' instance into a fresh object typed as the class (the copy is a plain object holding the instance's own fields, not an instance — build the object literal from the fields you need)`, + ); + } + } const src = lowerer.coerceToExpected(rawSource, DYN); if (src.type.kind !== "dyn") { ok = false; @@ -7519,7 +7781,12 @@ function lowerObjectStaticCall( args: [ pRef(), src, - { kind: "strLit", value: srcNode.getText(), type: STRING, loc: argLoc }, + { + kind: "strLit", + value: ts.getTextOfNode(srcNode), + type: STRING, + loc: argLoc, + }, ], type: VOID, loc: argLoc, @@ -7576,12 +7843,16 @@ function lowerObjectStaticCall( (exactClassOfReceiver(lowerer, targetNode.expression) !== null || storedClassValueType(lowerer, targetNode.expression)?.kind === "classval"); const declaredTarget = - target?.type.kind === "dyn" && !prototypeTarget && !isJsSourceFile(targetNode.getSourceFile()) + target?.type.kind === "dyn" && + !prototypeTarget && + !isJsSourceFile(ts.getSourceFileOfNode(targetNode)) ? lowerer.mapTypeOf(lowerer.typeOf(targetNode)) : null; if (target && declaredTarget && isDynTypedRefType(declaredTarget)) target = lowerer.coerceToExpected(target, declaredTarget); if (target && member === "defineProperty") { + const field = lowerClassFieldDefine(lowerer, call, target); + if (field) return field; const native = lowerClassDataDescriptor(lowerer, call, member, target); if (native) return native; } @@ -7724,6 +7995,19 @@ function lowerObjectStaticCall( loc, }; } + if (member === "preventExtensions" || member === "seal") { + const integrity = lowerClassIntegrity(lowerer, call, member, value); + if (integrity) return integrity; + } else if (isDynTypedRefType(value.type)) { + // The instance's checked view carries its integrity state. + return { + kind: "libCall", + fn: member === "isSealed" ? "dyn.isSealed" : "dyn.isExtensible", + args: [lowerer.coerceToExpected(value, DYN)], + type: BOOL, + loc: locOf(call), + }; + } if (value.type.kind === "dyn") { const operation = { preventExtensions: "dyn.preventExtensions", @@ -7760,7 +8044,7 @@ function lowerObjectStaticCall( while (ts.isParenthesizedExpression(inner) || ts.isAsExpression(inner)) inner = inner.expression; const value = - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && (ts.isObjectLiteralExpression(inner) || ts.isArrayLiteralExpression(inner)) ? lowerer.lowerExprExpecting(argNode, DYN) : lowerer.lowerExpr(argNode); @@ -7794,6 +8078,8 @@ function lowerObjectStaticCall( ) ) return value; + const integrity = lowerClassIntegrity(lowerer, call, "freeze", value); + if (integrity) return integrity; lowerer.noLowering( "Object.freeze of a possibly-aliased value", call, @@ -7979,7 +8265,7 @@ function lowerObjectStaticCall( probed.type.kind === "bool"); const isJsUnion = probed?.type.kind === "union" && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.dynConvertible(probed.type); if ( isDyn || @@ -8001,7 +8287,7 @@ function lowerObjectStaticCall( // write to the arrays well typed. JavaScript may store anything into // them, so it keeps the checked-dynamic arrays. const resultT = - member !== "values" && !isJsSourceFile(call.getSourceFile()) + member !== "values" && !isJsSourceFile(ts.getSourceFileOfNode(call)) ? lowerer.mapTypeOf(lowerer.typeOf(call)) : null; if (member === "keys" && resultT?.kind === "array" && resultT.elem.kind === "string") @@ -8024,7 +8310,11 @@ function lowerObjectStaticCall( // program only iterates or indexes the result, or passes it to a // read-only array method, a native vector of the checked values skips // building that array and, for for-of, the iterator protocol. - if (member === "values" && !isJsSourceFile(call.getSourceFile()) && consumesValueList(call)) + if ( + member === "values" && + !isJsSourceFile(ts.getSourceFileOfNode(call)) && + consumesValueList(call) + ) return { kind: "libCall", fn: "dyn.objValueList", @@ -8062,7 +8352,7 @@ function lowerObjectStaticCall( if ( (member === "values" || member === "entries") && (enumerationType?.kind === "dyn" || - (enumerationType === null && isJsSourceFile(call.getSourceFile()))) + (enumerationType === null && isJsSourceFile(ts.getSourceFileOfNode(call)))) ) { let receiver = lowerer.coerceToExpected(lowerer.lowerExpr(argNode), DYN); if (receiver.type.kind !== "dyn") lowerer.badType(argNode, lowerer.typeOf(argNode)); @@ -8593,7 +8883,7 @@ export function lowerRecordFieldCall( const target = lowerer.fieldTarget(access); const loc = locOf(call); const receiverLocal = - target && isJsSourceFile(call.getSourceFile()) + target && isJsSourceFile(ts.getSourceFileOfNode(call)) ? lowerer.declareHiddenLocal("%callReceiver", target.obj.type) : null; const init: IrStmt | null = @@ -8613,7 +8903,7 @@ export function lowerRecordFieldCall( // values (util.DebugLogger). if ( callee.type.kind === "dyn" && - (isJsSourceFile(call.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(call)) || lowerer.checker.getCallSignatures(lowerer.typeOf(access)).length > 0) ) { const receiver = @@ -8631,13 +8921,16 @@ export function lowerRecordFieldCall( kind: "dynCall", callee, ...(receiver ? { receiver } : {}), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, }; return init ? { kind: "seqExpr", stmts: [init], result, type: result.type, loc } : result; } + // An optional callback field the checker narrowed at this call + // (`o.cb ? o.cb(x) : …`, `if (o.cb) o.cb(x)`): extract the func arm. + if (callee.type.kind === "union") callee = lowerer.maybeNarrow(callee, access); if (callee.type.kind !== "func") lowerer.badType(access, lowerer.typeOf(access)); // JavaScript passes the record as `this`. A record with no dynamic form // cannot be that receiver; the library's own function fields (the @@ -8699,7 +8992,7 @@ function lowerUrlStaticCall( if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken !== undefined) return null; if (!ts.isIdentifier(callee.expression)) return null; const sym = lowerer.resolveValueSymbol(callee.expression); - if (!sym || !lowerer.isStdlibSymbol(sym) || sym.name !== "URL") return null; + if (!sym || !lowerer.isStdlibSymbol(sym) || ts.symbolName(sym) !== "URL") return null; if (callee.name.text === "canParse" || callee.name.text === "parse") { return lowerUrlFactory(lowerer, call, callee.name.text); } @@ -9057,7 +9350,7 @@ function lowerObjLitGenericMethodCall( kind: "dynInvoke", recv: { kind: "dynFrom", value, type: DYN, loc }, method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9066,7 +9359,7 @@ function lowerObjLitGenericMethodCall( lowerer.unsupported( "SC1090", call, - `Function.prototype.${name} on a function whose signature cannot cross the checked-value boundary (call '${access.expression.getText()}(...)' directly)`, + `Function.prototype.${name} on a function whose signature cannot cross the checked-value boundary (call '${ts.getTextOfNode(access.expression)}(...)' directly)`, ); } // STANDARD-LIBRARY generic members (Promise.then, Object. @@ -9094,7 +9387,7 @@ function lowerObjLitGenericMethodCall( kind: "dynInvoke", recv: receiver, method: name, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9136,6 +9429,7 @@ export function lowerObjectMethodCall( ): IrExpr | null { if (lowerer.chainBlocked(access, call)) return null; let mappedReceiver = + lowerer.receiverTypeOverrides.get(access.expression) ?? structuralReadType(lowerer, access.expression) ?? lowerer.mapTypeOf(lowerer.typeOf(access.expression)); // EventEmitter's static side (the class, a value of it, or the events @@ -9154,7 +9448,7 @@ export function lowerObjectMethodCall( (lowerer.implicitParamTypes !== null || lowerer.classImplementedProtocol(lowerer.typeOf(access.expression)) || ts.isNewExpression(access.expression) || - isJsSourceFile(call.getSourceFile())) + isJsSourceFile(ts.getSourceFileOfNode(call))) ? ts.isNewExpression(access.expression) ? lowerer.lowerExpr(access.expression) : tryLowerExpression(lowerer, access.expression) @@ -9173,7 +9467,7 @@ export function lowerObjectMethodCall( if (dispatched) return dispatched; const arms = lowerer.unions.get(mappedReceiver.unionId)?.arms; if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && arms?.every( (arm) => arm.kind === "object" && !lowerer.classes.get(arm.className)?.def.runtime, ) && @@ -9327,7 +9621,7 @@ export function lowerObjectMethodCall( callee, lowerer.armTag(callee.type.unionId, callableType), callableType, - () => `${access.getText()} is not a function`, + () => `${ts.getTextOfNode(access)} is not a function`, ); const helper = inPlace ? null @@ -9341,7 +9635,7 @@ export function lowerObjectMethodCall( if ( ((callee.type.rest === true && callee.type.restAbi === undefined) || call.arguments.nodes.length > callee.type.params.length) && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && lowerer.dynConvertible(callee.type) ) { const boxed: IrExpr = { kind: "dynFrom", value: callee, type: DYN, loc }; @@ -9352,7 +9646,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee: boxed, ...receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -9370,7 +9664,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee, ...receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -9379,7 +9673,7 @@ export function lowerObjectMethodCall( } if ( !fieldType && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && !info.def.runtime && !info.builtinError && !info.builtinEmitter && @@ -9409,7 +9703,7 @@ export function lowerObjectMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9458,7 +9752,7 @@ export function lowerObjectMethodCall( call.arguments.nodes, fallback, loc, - access.getText(), + ts.getTextOfNode(access), ); return { kind: "seqExpr", @@ -9484,6 +9778,30 @@ export function lowerObjectMethodCall( `calls of the abstract method '${method}' with no concrete implementation below the receiver's static class`, ); } + // The static receiver's nearest declaration is an override adapted to + // the inherited ABI, and nothing below overrides it: call its body + // directly under its declared signature (exact parameters and result). + // The slot function stays reachable; it lifts the body. + const adapted = found.declarer.returnAdapters?.get(method); + // A TypeScript override's own entry answers its declared result. A + // JavaScript body's result is inferred, so its callers keep the slot. + const declarerDecl = found.declarer.decl; + if ( + adapted && + !(declarerDecl && isJsSourceFile(ts.getSourceFileOfNode(declarerDecl))) && + !lowerer.overrideBelow(info, method) + ) { + lowerer.noteEdge(`%${found.declarer.def.name}.${method}`); + const receiver = lowerReceiver(); + const args = lowerer.completeArgs(call.arguments.nodes, adapted.params, locOf(call), call); + return { + kind: "call", + callee: `%${found.declarer.def.name}.${method}$own`, + args: [lowerer.upcastTo(receiver, found.declarer.def.name), ...args], + type: adapted.ret, + loc: locOf(call), + }; + } if (lowerer.overrideBelow(info, method)) lowerer.noteVirtualEdge(info, method); else lowerer.noteEdge(`%${found.declarer.def.name}.${method}`); const receiver = lowerReceiver(); @@ -9560,7 +9878,7 @@ function lowerUnionObjectMethodCall( kind: "dynCall", callee, receiver, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -9722,7 +10040,7 @@ function lowerUnionObjectDynFieldCall( kind: "dynCall", callee, receiver: lowerer.coerceToExpected(receiverRef, DYN), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args, type: DYN, loc, @@ -10109,8 +10427,9 @@ function pickViewGenericMethodCall( recvTs: ts.Type, ): IrExpr | null { const alias = recvTs.getAliasSymbol(); - if (alias === undefined || (alias.name !== "Pick" && alias.name !== "Omit")) return null; - if (!alias.declarations.every((d) => d.getSourceFile().isDeclarationFile)) return null; + if (alias === undefined || (ts.symbolName(alias) !== "Pick" && ts.symbolName(alias) !== "Omit")) + return null; + if (!alias.declarations.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) return null; const source = recvTs.getAliasTypeArguments()[0]; const symbol = source?.getSymbol(); const info = symbol ? lowerer.classBySymbol.get(symbol) : undefined; @@ -10131,7 +10450,7 @@ function pickViewGenericMethodCall( lowerer.unsupported( "SC1090", access, - `calling the generic method '${access.name.text}' through a '${alias.name}' view of '${info.def.name}' that holds a '${lowerer.fmt(lowered.type)}'`, + `calling the generic method '${access.name.text}' through a '${ts.symbolName(alias)}' view of '${info.def.name}' that holds a '${lowerer.fmt(lowered.type)}'`, ); } const receiver: IrExpr = @@ -10140,3 +10459,60 @@ function pickViewGenericMethodCall( : { kind: "dynCheck", value: lowerer.coerceToExpected(lowered, DYN), type: instance, loc }; return lowerClassGenericMethodCall(lowerer, call, access, info, found, receiver); } + +/** `Object.assign(err, ...sources)` in TypeScript onto an error-hierarchy + * target (`Object.assign(new Error(message), details)`): the sources' + * own members land on the error — the runtime copy over its + * checked-dynamic node, whose name, message, stack, cause and code feed + * back into the typed slots — and the call answers the error itself. + * Null for every other target. */ +function lowerObjectAssignOntoError(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null { + const argNodes = call.arguments.nodes; + if ( + isJsSourceFile(ts.getSourceFileOfNode(call)) || + argNodes.length < 2 || + argNodes.some((a) => ts.isSpreadElement(a)) + ) + return null; + const probe = tryLowerExpression(lowerer, argNodes[0]!); + if (probe?.type.kind !== "object" || !lowerer.isErrorRootedClass(probe.type.className)) + return null; + const loc = locOf(call); + const target = lowerer.lowerExpr(argNodes[0]!); + const slot = lowerer.declareHiddenLocal("%assignErr", target.type); + const sources: IrExpr[] = []; + for (const node of argNodes.slice(1)) { + const raw = lowerer.lowerExpr(node); + fenceSymbolFieldCopy(lowerer, node, raw.type); + const source = lowerer.coerceToExpected(raw, DYN); + if (source.type.kind !== "dyn") { + lowerer.unsupported( + "SC1090", + node, + `Object.assign onto an Error from a '${lowerer.fmt(raw.type)}' source (sources enter the error's dynamic properties)`, + ); + } + sources.push(source); + } + const ref = (): IrExpr => ({ kind: "varRef", localId: slot.id, type: target.type, loc }); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: slot.id, init: target, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "error.assign", + args: [ref(), { kind: "dynArrLit", elems: sources, type: DYN, loc }], + type: VOID, + loc, + }, + loc, + }, + ], + result: ref(), + type: target.type, + loc, + }; +} diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 8a5170a7a..49e7ad9fc 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -1,4 +1,5 @@ import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js"; +import { adaptPromiseExecutor } from "./promise-executor-adapter.js"; import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js"; import { constructedFromValue, @@ -16,6 +17,8 @@ import { InternalCompilerError } from "../../errors.js"; * accessor access, upcasts, `new` expressions, and the builtin Error * hierarchy registration. */ import { fsStreamClassOf, lowerFsStreamNew } from "./builtins/fs-streams.js"; +import { codeGenerationCalleeOf, lowerCodeGenerationCall } from "./code-generation.js"; +import { TEMPORAL_GLOBAL } from "../builtin-modules.js"; import * as ts from "../ts7/adapter.js"; import { lowerZlibStreamNew, zlibClassName, zlibStreamModeOf } from "./builtins/compression.js"; import { everyStmtList } from "../../ir/traverse.js"; @@ -28,6 +31,7 @@ import type { FnCtx, Lowerer } from "./lowerer.js"; import { inlineNamespaceRequireOf, lowerWithInlineRequireInit } from "./cjs-inline-require.js"; import { BOOL, + canDynCheckTo, DATE_T, DYN, F64, @@ -70,6 +74,7 @@ import { } from "./instantiation-path.js"; import { appendImplicitUndefinedReturn, hasExplicitJsDocReturn } from "./function-returns.js"; import { bodyReadsArguments } from "../arguments-usage.js"; +import { errorNameAfterField, errorViewFields } from "./error-name-order.js"; import { checkedJsDocumentedParameter, documentedParameter, @@ -88,7 +93,6 @@ import { isCjsJsFile, isJsSourceFile, isModuleExportsAccess, - isNodeTypesPath, locOf, } from "../program.js"; import { PoisonError, dynFallbackType, jsFuncNameOf, newFnCtx, own } from "./lowerer.js"; @@ -113,6 +117,7 @@ import { import { STREAM_API_MEMBERS, STREAM_PROP_MEMBERS, + STREAM_COMPLETION_CALLBACK_INDEX, UNDERSCORE_METHODS, lowerStreamNew, lowerStreamSuperCall, @@ -145,6 +150,7 @@ import { ambientUndefReadType, ambientUndefVarRootOf, ambientUndefinedFnSymbolOf, + contextualUndefReadType, fenceEarlyAliasUse, fenceEarlyNsMemberRef, nsMemberIdentOf, @@ -177,9 +183,9 @@ export function returnsOnlyThis(member: ts.MethodDeclaration): boolean { while (value && ts.isParenthesizedExpression(value)) value = value.expression; if (!value || value.kind !== ts.SyntaxKind.ThisKeyword) valid = false; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return valid; } @@ -210,9 +216,9 @@ export function returnsOnlyThisValue(member: ts.MethodDeclaration): boolean { } if (!value || value.kind !== ts.SyntaxKind.ThisKeyword) valid = false; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return valid; } @@ -335,7 +341,7 @@ export function storedClassValueType( } if ( (ts.isArrowFunction(callee) || ts.isFunctionExpression(callee)) && - isJsSourceFile(callee.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callee)) && !callee.typeParameters?.nodes.length && !lowerer.preparingClassFactories.has(callee) ) { @@ -404,7 +410,7 @@ export function storedClassValueType( } if ( !factory || - !isJsSourceFile(factory.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(factory)) || factory.typeParameters?.nodes.length || !ts.isVariableDeclaration(factory.parent) || factory.parent.initializer !== factory || @@ -496,6 +502,12 @@ export interface ClassInfo { /** Own JS overrides whose signature cannot use the inherited ABI. The slot * retains that ABI, but its implementation throws before executing. */ methodEntryFences?: Map; + /** Own TypeScript overrides whose declared result differs from the + * inherited ABI's but converts into it (`Promise` over `any`, a + * union narrowed to its inherited arm): the slot function `%C.m` keeps the + * inherited ABI and converts the result of the body, which lowers as + * `%C.m$own` with the declared result (async when declared so). */ + returnAdapters?: Map; /** OWN GENERIC instance methods (own type parameters — `m(x: T)`), * monomorphized per call site like top-level generic functions: instance * `n` is the module function `%C.m%n` taking `this` as param 0. They @@ -796,7 +808,7 @@ function symbolSlotReturnType( ): IrType | null { if (symbolFields.size === 0) return null; if (fnLike.type !== undefined || !fnLike.body) return null; - if (!isJsSourceFile(fnLike.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(fnLike))) return null; if (fnLike.asteriskToken !== undefined) return null; if (fnLike.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) return null; const stmts = fnLike.body.statements.nodes; @@ -806,9 +818,9 @@ function symbolSlotReturnType( const visit = (n: ts.Node): void => { if (ts.isFunctionLike(n)) return; if (ts.isReturnStatement(n)) returns.push(n); - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - fnLike.body.forEachChild(visit); + ts.forEachChild(fnLike.body, visit); let out: IrType | null = null; for (const r of returns) { let e = r.expression; @@ -1043,12 +1055,14 @@ export function builtinStreamInfoOf( } let irName: string | null = null; for (const [name, rec] of RUNTIME_STREAM_CLASSES) { - if (rec.lib === symbol.name) irName = name; + if (rec.lib === ts.symbolName(symbol)) irName = name; } if (!irName) return null; + // Both the bundled declarations and @types/node declare the classes + // inside `declare module "stream"` (@types/node nests them in the module's + // `internal` namespace). const declared = symbol.declarations.some((d) => { if (!ts.isClassDeclaration(d)) return false; - if (isNodeTypesPath(d.getSourceFile().fileName)) return false; let node: ts.Node | undefined = d.parent; while (node) { if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) { @@ -1082,7 +1096,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E // JavaScript documentation cannot guarantee that a call or an indexed // read returns a present number. Numeric fields receiving those values // must retain undefined, so later arithmetic performs JS coercion (NaN). - if (type.kind === "f64" && isJsSourceFile(expression.getSourceFile())) { + if (type.kind === "f64" && isJsSourceFile(ts.getSourceFileOfNode(expression))) { const assignment = expression.parent; const left = ts.isBinaryExpression(assignment) ? assignment.left : null; const field = @@ -1114,7 +1128,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E if (ts.isCallExpression(rhs) || ts.isElementAccessExpression(rhs)) acceptsUnchecked = true; } - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(owner); if (acceptsUnchecked) return DYN; @@ -1151,7 +1165,7 @@ function jsFieldInitializerType(lowerer: Lowerer, type: IrType, expression: ts.E owner = owner.parent; if (owner && (ts.isClassDeclaration(owner) || ts.isClassExpression(owner)) && owner.name) { const className = owner.name.text; - for (const statement of owner.getSourceFile().statements.nodes) { + for (const statement of ts.getSourceFileOfNode(owner).statements.nodes) { if ( !ts.isExpressionStatement(statement) || !ts.isBinaryExpression(statement.expression) @@ -1254,7 +1268,7 @@ function returnsWidenedJsField( ret: IrType, ): boolean { if ( - !isJsSourceFile(member.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(member)) || member.type || member.asteriskToken || hasExplicitJsDocReturn(member) @@ -1317,7 +1331,7 @@ function returnsWidenedJsField( const scan = (node: ts.Node): void => { if (result || ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression && widened(node.expression)) result = true; - node.forEachChild(scan); + ts.forEachChild(node, scan); }; if (member.body) scan(member.body); return result; @@ -1396,7 +1410,7 @@ export function builtinEmitterInfoOf( } return null; } - if (symbol.name !== "EventEmitter") return null; + if (ts.symbolName(symbol) !== "EventEmitter") return null; const declared = symbol.declarations.some((d) => { if (!ts.isClassDeclaration(d) && !ts.isInterfaceDeclaration(d)) return false; let node: ts.Node | undefined = d.parent; @@ -1426,7 +1440,7 @@ export function builtinErrorInfoOf( if (alias !== null) return alias.kind === "error" ? alias.info : null; if (!lowerer.isStdlibSymbol(symbol)) return null; for (const [irName, rec] of RUNTIME_ERROR_CLASSES) { - if (rec.lib === symbol.name) return lowerer.classes.get(irName) ?? null; + if (rec.lib === ts.symbolName(symbol)) return lowerer.classes.get(irName) ?? null; } return null; } @@ -1571,9 +1585,9 @@ function collectClassShapeDeferring(lowerer: Lowerer, decl: ts.ClassDeclaration) if ( baseDeclaration && !lowerer.isStdlibSymbol(baseSymbol ?? undefined) && - !baseDeclaration.getSourceFile().isDeclarationFile && - (baseDeclaration.getSourceFile() !== decl.getSourceFile() || - baseDeclaration.getStart() < decl.getStart()) + !ts.getSourceFileOfNode(baseDeclaration).isDeclarationFile && + (ts.getSourceFileOfNode(baseDeclaration) !== ts.getSourceFileOfNode(decl) || + ts.tokenStartOfNode(baseDeclaration) < ts.tokenStartOfNode(decl)) ) lowerer.collectClassShape(baseDeclaration); lowerer.collectClassShapeInner(decl); @@ -1778,7 +1792,7 @@ export function collectClassShapeInner( // source (V8 has not shipped the proposal; the type-stripping // loaders leave `@dec` in place) — there is no runtime behavior // to be exact against. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { lowerer.unsupported( "SC1090", (classDecoratorNodes[0] ?? decoratorNodesOf(decoratedMembers[0]!)[0])!, @@ -1916,7 +1930,7 @@ export function collectClassShapeInner( const source = signatures.length === 1 ? signatures[0]!.declaration : undefined; if ( !source || - !isJsSourceFile(source.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(source)) || !(ts.isFunctionDeclaration(source) || ts.isFunctionExpression(source)) ) return false; @@ -2032,13 +2046,13 @@ export function collectClassShapeInner( const baseDecl = propBase.decl; if ( baseDecl != null && - baseDecl.getSourceFile() === decl.getSourceFile() && - baseDecl.getStart() > decl.getStart() + ts.getSourceFileOfNode(baseDecl) === ts.getSourceFileOfNode(decl) && + ts.tokenStartOfNode(baseDecl) > ts.tokenStartOfNode(decl) ) { lowerer.unsupported( "SC1090", t, - `extending '${t.expression.getText()}' above the statement that assigns it (the property is still undefined when this class evaluates — Node throws here; assign the base first)`, + `extending '${ts.getTextOfNode(t.expression)}' above the statement that assigns it (the property is still undefined when this class evaluates — Node throws here; assign the base first)`, ); } base = propBase; @@ -2056,7 +2070,7 @@ export function collectClassShapeInner( lowerer.unsupported( "SC1090", t, - `extending the reassigned property '${t.expression.getText()}' (the runtime base is whichever assignment ran last — bind the class exactly once)`, + `extending the reassigned property '${ts.getTextOfNode(t.expression)}' (the runtime base is whichever assignment ran last — bind the class exactly once)`, ); } } @@ -2212,11 +2226,7 @@ export function collectClassShapeInner( } if (lowerer.collectingExprClasses.has(decl)) { for (let parent = base; parent; parent = parent.base) { - if ( - parent.builtinEmitter || - parent.builtinStream !== undefined || - parent.builtinEventTarget !== undefined - ) { + if (parent.builtinEventTarget !== undefined) { lowerer.unsupported( "SC1090", decl, @@ -2248,6 +2258,7 @@ export function collectClassShapeInner( } >(); const methodEntryFences = new Map(); + const returnAdapters = new Map(); // Own accessor declarations ("get:x"/"set:x" → node), for the // partial-override analysis below (diagnostics need the node). const accessorNodes = new Map(); @@ -2278,7 +2289,7 @@ export function collectClassShapeInner( // Public JS accessors need live constructor descriptors, including // receiver-sensitive getters and setters inherited by subclasses. const checkedStatics = - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && ((base && hasRuntimeStatics(base)) || decl.members.nodes.some( (member) => @@ -2352,7 +2363,7 @@ export function collectClassShapeInner( for (const member of decl.members.nodes) { if ( (functionLocalClass(decl) || checkedStatics) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (ts.isClassStaticBlockDeclaration(member) || ts .getModifiers(member) @@ -2363,7 +2374,7 @@ export function collectClassShapeInner( } if ( functionLocalClass(decl) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && (ts.isMethodDeclaration(member) || ts.isAccessor(member)) && member.body && !ts.isPrivateIdentifier(member.name) @@ -2435,7 +2446,7 @@ export function collectClassShapeInner( if (type.kind === "void") lowerer.badType(member.name, lowerer.typeOf(member.name)); if ( type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) && (member.type !== undefined || (lowerer.typeOf(member.name).flags & ts.TypeFlags.Any) === 0) @@ -2469,7 +2480,7 @@ export function collectClassShapeInner( } staticMethods.set(member.name.text, { params: shapes, ret: ft.ret, member }); if ( - implicitMonoFile(decl.getSourceFile()) && + implicitMonoFile(ts.getSourceFileOfNode(decl)) && !functionLocalClass(decl) && inst === undefined && decl.typeParameters === undefined @@ -2526,7 +2537,7 @@ export function collectClassShapeInner( // read of an Error-rooted class is the checked-dynamic property // (fieldTarget's errorCause container), so only types that read // back unchanged from it are accepted. - if (!isJsSourceFile(member.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(member))) { if ( modifiers?.some( (m) => @@ -2689,6 +2700,24 @@ export function collectClassShapeInner( ); } } + // A completion callback declared with the broad `Function` type + // (`cb: Function`) takes the checked-dynamic callable the runtime + // passes implicitly typed callbacks; calls through it are + // dynamic calls of that callable. + const callbackIndex = STREAM_COMPLETION_CALLBACK_INDEX.get(name); + const callbackParam = + ts.isMethodDeclaration(member) && callbackIndex !== undefined + ? member.parameters.nodes[callbackIndex] + : undefined; + if ( + callbackParam !== undefined && + ts.isIdentifier(callbackParam.name) && + !callbackParam.dotDotDotToken && + !callbackParam.questionToken && + !callbackParam.initializer && + lowerer.checker.typeToString(lowerer.typeOf(callbackParam.name)) === "Function" + ) + lowerer.checkedCallbackParams.add(callbackParam); } } if (ts.isPropertyDeclaration(member)) { @@ -2727,10 +2756,12 @@ export function collectClassShapeInner( // unconditional `this[key] = ...` at the constructor's top // level), checked with the unguarded fields below. const declaredOnly = - !member.initializer && !isJsSourceFile(member.getSourceFile()) && !!member.type; + !member.initializer && !isJsSourceFile(ts.getSourceFileOfNode(member)) && !!member.type; if ( fieldName === null || - (!member.initializer && !isJsSourceFile(member.getSourceFile()) && !declaredOnly) + (!member.initializer && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && + !declaredOnly) ) { lowerer.unsupported( "SC1090", @@ -2748,7 +2779,9 @@ export function collectClassShapeInner( ? (lowerer.mapTypeOf(inferred) ?? dynFallbackType(lowerer, member, inferred)) : DYN; let type = - mapped?.kind === "record" && isJsSourceFile(member.getSourceFile()) && !member.type + mapped?.kind === "record" && + isJsSourceFile(ts.getSourceFileOfNode(member)) && + !member.type ? DYN : mapped ? jsOpenObjectType(member, mapped, lowerer.shapes, lowerer.unions) @@ -2757,11 +2790,11 @@ export function collectClassShapeInner( !type || type.kind === "void" || (type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(inferred)) ) lowerer.badType(member, inferred); - if (isJsSourceFile(member.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(member))) { type = inheritedJsFieldType(lowerer, decl, fieldName, type); if (base?.fields.get(fieldName)?.kind === "dyn") type = DYN; if (base && !fields.has(fieldName) && lowerer.dynConvertible(type)) type = DYN; @@ -2875,7 +2908,7 @@ export function collectClassShapeInner( // as undefined on construction, verified), and reads/writes ride // the ordinary undefined-armed union machinery. let type = lowerer.irTypeOf(member.name); - if (isJsSourceFile(member.getSourceFile()) && !member.initializer) { + if (isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.initializer) { const constructor = decl.members.nodes.find( (part): part is ts.ConstructorDeclaration => ts.isConstructorDeclaration(part) && !!part.body, @@ -2903,19 +2936,25 @@ export function collectClassShapeInner( // Unannotated JavaScript callback slots retain the original // callable and its receiver instead of adapting its signature. if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.type && - !/@type\b/.test(member.getSourceFile().text.slice(member.pos, member.getStart())) && + !/@type\b/.test( + ts.getSourceFileOfNode(member).text.slice(member.pos, ts.tokenStartOfNode(member)), + ) && (type.kind === "func" || type.kind === "record") && lowerer.dynConvertible(type) ) type = DYN; // JS permits reads before a constructor's first assignment even // when its inferred field type omits undefined. - if (isJsSourceFile(member.getSourceFile()) && !member.initializer && type.kind !== "dyn") { + if ( + isJsSourceFile(ts.getSourceFileOfNode(member)) && + !member.initializer && + type.kind !== "dyn" + ) { type = lowerer.withUndefinedArmOf(type) ?? DYN; } - if (isJsSourceFile(member.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(member))) { type = inheritedJsFieldType(lowerer, decl, member.name.text, type); if (base?.fields.get(member.name.text)?.kind === "dyn") type = DYN; if (base && !fields.has(member.name.text) && lowerer.dynConvertible(type)) type = DYN; @@ -2926,7 +2965,7 @@ export function collectClassShapeInner( // storage as implicit constructor-assigned JS fields. if ( type.kind === "dyn" && - !isJsSourceFile(member.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && !lowerer.classFieldStoresDyn(lowerer.typeOf(member.name)) ) { lowerer.unsupported( @@ -3198,7 +3237,7 @@ export function collectClassShapeInner( // name (the override stays on the vtable), a shadowed field, or // a generic-class instantiation's member. if ( - implicitMonoFile(decl.getSourceFile()) && + implicitMonoFile(ts.getSourceFileOfNode(decl)) && !functionLocalClass(decl) && ts.isIdentifier(member.name) && !lowerer.virtualJsMethods.has(member) && @@ -3239,14 +3278,14 @@ export function collectClassShapeInner( // JS inference may retain only a symbol sentinel while a method // also returns values from an untyped mutable field or local. if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.type && !hasExplicitJsDocReturn(member) && ft.ret.kind === "symbol" ) ft.ret = DYN; if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && lowerer.prototypeMethodAccesses.has(mName) && !lowerer.findMethodOn(base, mName)?.declarer.def.runtime && shapes.every((p) => p.mode === "required" || p.mode === "omittable") @@ -3300,7 +3339,7 @@ export function collectClassShapeInner( // against the STATIC receiver's shape, so `m(x?: number)` and // `m(x: number | undefined)` interchange soundly in overrides. const overridden = lowerer.findMethodOn(base, mName); - if (overridden && isJsSourceFile(member.getSourceFile())) { + if (overridden && isJsSourceFile(ts.getSourceFileOfNode(member))) { // An unannotated JS override can return a checked value into // the inherited native ABI, just as its untyped parameters do. // Body lowering inserts the conversion at each return. @@ -3348,7 +3387,7 @@ export function collectClassShapeInner( // readers keep the exact-signature fence. if ( overridden && - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && shapes.length < overridden.sig.params.length && !bodyReadsArguments(member) && shapes.every( @@ -3368,6 +3407,56 @@ export function collectClassShapeInner( .map((p): ParamShape => ({ type: p.type, mode: "omittable" })), ); } + // A TypeScript override of a JavaScript method (`class T extends + // Agent` over an npm-static package) keeps the base's checked-value + // ABI: each untyped base slot stays dyn, and the override's typed + // parameter narrows from it at entry with a checked conversion. An + // untyped base return takes the override's value boxed. Calls on + // a statically typed receiver see the same ABI. + if ( + overridden && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && + overridden.declarer.decl !== null && + isJsSourceFile(ts.getSourceFileOfNode(overridden.declarer.decl)) && + member.asteriskToken === undefined && + !overridden.sig.async && + !overridden.sig.gen && + shapes.length <= overridden.sig.params.length && + shapes.every((p, i) => { + const inherited = overridden.sig.params[i]!; + return ( + (p.mode === "required" || p.mode === "omittable") && + p.bodyType === undefined && + (inherited.mode === "required" || inherited.mode === "omittable") && + (typeEquals(p.type, inherited.type) || + (inherited.type.kind === "dyn" && lowerer.dynConvertible(p.type))) + ); + }) && + overridden.sig.params + .slice(shapes.length) + .every( + (p) => p.type.kind === "dyn" && (p.mode === "required" || p.mode === "omittable"), + ) + ) { + for (let i = 0; i < shapes.length; i++) { + const own = shapes[i]!; + if (overridden.sig.params[i]!.type.kind === "dyn" && own.type.kind !== "dyn") + shapes[i] = { type: DYN, mode: own.mode, entryNarrow: own.type }; + } + shapes.push( + ...overridden.sig.params + .slice(shapes.length) + .map((p): ParamShape => ({ type: p.type, mode: "omittable" })), + ); + if ( + overridden.sig.ret.kind === "dyn" && + ft.ret.kind !== "dyn" && + ft.ret.kind !== "void" && + lowerer.dynConvertible(ft.ret) + ) + ft.ret = DYN; + ft.params = shapes.map((shape) => shape.type); + } if ( overridden && member.asteriskToken === undefined && @@ -3379,11 +3468,71 @@ export function collectClassShapeInner( )) ) { const fencedJsOverride = - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && overridden.declarer.decl !== null && - isJsSourceFile(overridden.declarer.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(overridden.declarer.decl)) && overridden.sig.gen === undefined; - if (fencedJsOverride) { + // A TypeScript override that only changes the result, into a + // type that converts into the inherited one (`Promise` over + // an `any` base, `string | undefined` over `string`): the slot + // keeps the inherited ABI and converts the body's result at the + // boundary (lowerClassMethodMemberInner). + const ownAsync = + member.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true; + if ( + !fencedJsOverride && + !functionLocalClass(decl) && + overridden.sig.gen === undefined && + // An async base's slot answers its promise: a Promise type, or + // the checked-dynamic value of an untyped async method. + (overridden.sig.async !== true || + overridden.sig.ret.kind === "promise" || + overridden.sig.ret.kind === "dyn") && + !overridden.sig.abstract && + overridden.sig.params.length === shapes.length && + overridden.sig.params.every( + (p, i) => + p.mode === shapes[i]!.mode && + (p.mode === "required" || p.mode === "omittable") && + // Each inherited argument converts into the override's + // parameter: equal, a checked extraction from an `any` + // slot, or a widening (a contravariant parameter). + (typeEquals(p.type, shapes[i]!.type) || + (p.type.kind === "dyn" && + canDynCheckTo( + shapes[i]!.type, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + )) || + lowerer.coercibleValue(p.type, shapes[i]!.type)), + ) && + (ownAsync ? ft.ret.kind === "promise" : true) && + (typeEquals(ft.ret, overridden.sig.ret) || + (overridden.sig.ret.kind === "dyn" + ? ft.ret.kind !== "void" && lowerer.dynConvertible(ft.ret) + : lowerer.coercibleValue(ft.ret, overridden.sig.ret) || + // A promise whose payload widens into the inherited one + // (`Promise` over an untyped async `Promise`). + (ft.ret.kind === "promise" && + overridden.sig.ret.kind === "promise" && + lowerer.promisePayloadWidens(ft.ret, overridden.sig.ret)) || + // A JavaScript override's inferred `any` result checks + // into the inherited type (`toString() { return + // this.message }` over Error's). + (ft.ret.kind === "dyn" && + isJsSourceFile(ts.getSourceFileOfNode(member)) && + overridden.sig.ret.kind !== "void" && + canDynCheckTo( + overridden.sig.ret, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + )))) + ) { + returnAdapters.set(mName, { params: [...shapes], ret: ft.ret, async: ownAsync }); + shapes.splice(0, shapes.length, ...overridden.sig.params); + ft.ret = overridden.sig.ret; + if (overridden.sig.async === true) thunkAsyncSlot(overridden, mName); + } else if (fencedJsOverride) { // A dormant JS method must not poison the whole class. Keep // the inherited call/dispatch ABI and refuse at method entry; // lowering this body with the base signature would change JS. @@ -3438,9 +3587,36 @@ export function collectClassShapeInner( // fiber spawn wrapper; vtable slots hold raw implementations) — // an override chain touching an async method on either end would // put a spawn wrapper behind a virtual slot, so it fences. - if (overridden && (asyncMember || overridden.sig.async === true)) { + // An override chain touching async methods dispatches through + // plain slot functions that return the body's promise: the async + // bodies lower as `%C.m$own` behind them (thunkAsyncSlot). + if ( + overridden && + !returnAdapters.has(mName) && + (asyncMember || overridden.sig.async === true) && + !isJsSourceFile(ts.getSourceFileOfNode(member)) && + !functionLocalClass(decl) && + member.asteriskToken === undefined && + overridden.sig.gen === undefined && + !overridden.sig.abstract && + ft.ret.kind === "promise" && + typeEquals(ft.ret, overridden.sig.ret) && + overridden.sig.params.length === shapes.length && + overridden.sig.params.every( + (p, i) => typeEquals(p.type, shapes[i]!.type) && p.mode === shapes[i]!.mode, + ) + ) { + if (overridden.sig.async === true) thunkAsyncSlot(overridden, mName); + if (asyncMember) + returnAdapters.set(mName, { params: [...shapes], ret: ft.ret, async: true }); + } + if ( + overridden && + !returnAdapters.has(mName) && + (asyncMember || overridden.sig.async === true) + ) { const message = `overriding ${overridden.sig.async === true ? "the async method" : "a method with an async method"} '${mName}' (async methods dispatch statically — the vtable slot machinery has no fiber-spawn story)`; - if (isJsSourceFile(member.getSourceFile())) + if (isJsSourceFile(ts.getSourceFileOfNode(member))) methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message)); else lowerer.unsupported("SC1090", member.name, message); } @@ -3449,7 +3625,7 @@ export function collectClassShapeInner( (member.asteriskToken !== undefined || overridden.sig.gen !== undefined) ) { const message = `overriding the generator method '${mName}' (generator methods dispatch statically — a virtual slot cannot enter their spawn wrapper)`; - if (isJsSourceFile(member.getSourceFile())) + if (isJsSourceFile(ts.getSourceFileOfNode(member))) methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message)); else lowerer.unsupported("SC1090", member.name, message); } @@ -3469,7 +3645,7 @@ export function collectClassShapeInner( } else { methods.set( mName, - asyncMember + asyncMember && !returnAdapters.has(mName) ? { params: shapes, ret: ft.ret, async: true as const } : { params: shapes, ret: ft.ret }, ); @@ -3521,7 +3697,7 @@ export function collectClassShapeInner( const mName = `${isGet ? "get" : "set"}:${prop}`; if ( fields.has(prop) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && base?.fields.has(prop) && !fieldOrder.some((field) => field.name === prop) ) { @@ -3836,7 +4012,7 @@ export function collectClassShapeInner( // assignment ran — the zeroed-memory trap a TS declaration order // forbids via strictPropertyInitialization — so they keep a named // fence instead of reading undefined without a diagnostic. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { const hasAccessor = (name: string): boolean => methods.has(`get:${name}`) || methods.has(`set:${name}`) || @@ -3860,7 +4036,7 @@ export function collectClassShapeInner( // goes through the key. const lateBoundByKey = new Map(); for (const p of instType ? lowerer.checker.getPropertiesOfType(instType) : []) { - if (!p.name.startsWith("__@")) continue; + if (!ts.symbolName(p).startsWith("__@")) continue; const keySym = lateBoundKeySymOf(lowerer, p); if (keySym) lateBoundByKey.set(keySym, p); } @@ -3916,7 +4092,7 @@ export function collectClassShapeInner( if ( t && (t.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) type = DYN; if (type) type = jsOpenObjectType(assign, type, lowerer.shapes, lowerer.unions); @@ -4034,10 +4210,14 @@ export function collectClassShapeInner( // static layouts cannot represent. Named fence, at the first // assignment site. for (const p of instType ? lowerer.checker.getPropertiesOfType(instType) : []) { - if (errorRooted && p.name === "cause") continue; - if (fields.has(p.name) || methods.has(p.name)) continue; - if (hasAccessor(p.name)) continue; - if (base && (base.fields.has(p.name) || lowerer.findMethodOn(base, p.name))) continue; + if (errorRooted && ts.symbolName(p) === "cause") continue; + if (fields.has(ts.symbolName(p)) || methods.has(ts.symbolName(p))) continue; + if (hasAccessor(ts.symbolName(p))) continue; + if ( + base && + (base.fields.has(ts.symbolName(p)) || lowerer.findMethodOn(base, ts.symbolName(p))) + ) + continue; const site = p.declarations.find( (d) => ts.isPropertyAccessExpression(d) || @@ -4051,7 +4231,7 @@ export function collectClassShapeInner( // identities (symbol parameters, mutable bindings, computed // descriptions) or assignments outside the constructor's top // level. - if (p.name.startsWith("__@")) { + if (ts.symbolName(p).startsWith("__@")) { const keySym = lateBoundKeySymOf(lowerer, p); const key = keySym ? classSymbolKeyOfSymbol(lowerer, keySym) : null; if (key && symbolFields.has(key.identity)) continue; @@ -4071,24 +4251,26 @@ export function collectClassShapeInner( // Unmappable inferences and arm-less kinds keep the fence. // TypeScript classes keep the compile-time fence too: an annotated // program can spell `T | undefined` itself. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { const original = undefArmedFieldType(lowerer, p); const armed = - original === null ? null : inheritedJsFieldType(lowerer, decl, p.name, original); + original === null + ? null + : inheritedJsFieldType(lowerer, decl, ts.symbolName(p), original); if (armed !== null) { // Unknown JS properties use the instance bag: they do not // exist until assigned, and a subclass may declare the same // name as an ordinary layout field. if (armed.kind === "dyn") continue; - fields.set(p.name, armed); - fieldOrder.push({ name: p.name, type: armed, initializer: undefined }); + fields.set(ts.symbolName(p), armed); + fieldOrder.push({ name: ts.symbolName(p), type: armed, initializer: undefined }); continue; } } lowerer.unsupported( "SC1090", site, - `fields assigned outside the constructor's top level ('this.${p.name}' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor)`, + `fields assigned outside the constructor's top level ('this.${ts.symbolName(p)}' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor)`, ); } } @@ -4131,7 +4313,7 @@ export function collectClassShapeInner( if ( ctor && !mixinForwarding && - isJsSourceFile(ctor.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(ctor)) && !ctorParams.some((shape) => shape.mode === "rest" || shape.mode === "dynRest") && bodyReadsArguments(ctor) ) { @@ -4187,6 +4369,7 @@ export function collectClassShapeInner( fieldOrder, methods, ...(methodEntryFences.size > 0 ? { methodEntryFences } : {}), + ...(returnAdapters.size > 0 ? { returnAdapters } : {}), decl, ...(emitOverride !== undefined ? { emitOverride } : {}), ctor, @@ -4209,6 +4392,12 @@ export function collectClassShapeInner( ...(deferredInitFields.size > 0 ? { deferredInitFields } : {}), ...(escape.escapes ? { constructionEscapes: true as const } : {}), }; + let root: ClassInfo | null = info; + while (root?.base) root = root.base; + if (root !== info && root?.def.name === "%Error") { + const after = errorNameAfterField(info, errorViewFields(info.def.fields)); + if (after !== undefined) info.def.errorNameAfter = after; + } // GENERIC members get their declaring-class backlink now that the // info exists (instance lowering reads it for `this` typing and the // generic-class binding merge). @@ -4519,7 +4708,7 @@ export function lowerStaticFieldInits(lowerer: Lowerer, info: ClassInfo): IrStmt // Keep their refusal attached to the class declaration instead of // letting a poisoned initializer escape the module lowering pass. if (!(error instanceof PoisonError)) throw error; - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, info.decl, { kind: "statement" }) @@ -4646,8 +4835,16 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] | { pos: number; kind: "field"; f: ClassInfo["staticFields"][number] } | { pos: number; kind: "block"; b: ts.ClassStaticBlockDeclaration }; const items: Item[] = [ - ...info.staticFields.map((f): Item => ({ pos: f.initializer.getStart(), kind: "field", f })), - ...(info.staticBlocks ?? []).map((b): Item => ({ pos: b.getStart(), kind: "block", b })), + ...info.staticFields.map((f): Item => ({ + pos: ts.tokenStartOfNode(f.initializer), + kind: "field", + f, + })), + ...(info.staticBlocks ?? []).map((b): Item => ({ + pos: ts.tokenStartOfNode(b), + kind: "block", + b, + })), ].sort((a, b) => a.pos - b.pos); for (const item of items) { if (item.kind === "block") { @@ -4694,7 +4891,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] } catch (e) { if (!(e instanceof PoisonError)) throw e; lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file); - if (isJsSourceFile(f.initializer.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(f.initializer))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) @@ -4753,7 +4950,7 @@ export function analyzeClassDecoration(lowerer: Lowerer, info: ClassInfo): void ts.isVariableDeclaration(vdecl) && vdecl.initializer === undefined && (ts.getCombinedModifierFlags(vdecl) & ts.ModifierFlags.Ambient) !== 0 && - !vdecl.getSourceFile().isDeclarationFile; + !ts.getSourceFileOfNode(vdecl).isDeclarationFile; if (ambientVar || ambientUndefinedFnSymbolOf(lowerer, ambientCallee) !== null) { shapes.push({ kind: "ambientThrow", name: ambientCallee.text }); continue; @@ -5132,16 +5329,11 @@ export function classValueRef(lowerer: Lowerer, info: ClassInfo, blame: ts.Node) loc: locOf(blame), }; } - if ( - lowerer.inheritsBuiltinEmitterCtor(info) || - inheritsBuiltinStreamCtor(lowerer, info) || - eventTargetRootOf(info) !== null || - (() => { - for (let c = info.base; c; c = c.base) - if (c.builtinEmitter || c.builtinStream !== undefined) return true; - return false; - })() - ) { + // Classes extending EventEmitter or a stream class construct through + // their own (or synthesized) `%C.constructor`, whose super call runs the + // runtime initializer, so the class object's construct thunk serves them + // like any program class. EventTarget subclasses keep the refusal. + if (eventTargetRootOf(info) !== null) { lowerer.unsupported( "SC1090", blame, @@ -5381,7 +5573,7 @@ export function lowerStaticFieldRead( // JavaScript often assigns public statics after the class declaration. // Their storage stays mutable and checked, while reads retain the // checker's concrete type instead of losing the method receiver. - const type = isJsSourceFile(expr.getSourceFile()) + const type = isJsSourceFile(ts.getSourceFileOfNode(expr)) ? null : lowerer.mapTypeOf(lowerer.typeOf(expr)); return type ? lowerer.coerceToExpected(value, type) : value; @@ -5412,6 +5604,75 @@ function namedEvaluationName(expr: ts.ClassLikeDeclaration): string { return ""; } +/** Each evaluation of a function-local class creates fresh private names, + * while the compiled class shares one layout across evaluations. A + * JavaScript class's prototype methods are closures per evaluation, so the + * names agree when every private member is an instance field and every use + * is on `this` (privateNamesOnlyOnThis): native methods only run on their + * own evaluation's instances, and the closures, which receive any `this`, + * check the receiver's evaluation against the class's own binding + * (private-brands.ts), so the class must be a named declaration (undici's + * webidl FastIterableIterator). The checker rejects writes to that binding. */ +function localPrivateFieldsAgree(expr: ts.ClassLikeDeclaration): boolean { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr)) || !ts.isClassDeclaration(expr) || !expr.name) + return false; + if ( + expr.members.nodes.some( + (member) => + member.name !== undefined && + ts.isPrivateIdentifier(member.name) && + !ts.isPropertyDeclaration(member), + ) + ) + return false; + return privateNamesOnlyOnThis(expr); +} + +/** Every private name is used only on `this` inside the class's own instance + * members (`this.#x`, `#x in this`), outside nested classes and non-arrow + * functions, and the class has no static members that could hand instances + * across evaluations. */ +function privateNamesOnlyOnThis(expr: ts.ClassLikeDeclaration): boolean { + let ok = true; + for (const member of expr.members.nodes) { + if (ts.getModifiers(member)?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword)) return false; + ts.walkPreorder(member, (node) => { + if (!ok) return "stop"; + // A nested class or non-arrow function rebinds `this`: any private + // name inside it disqualifies. + if ( + node !== member && + (ts.isClassDeclaration(node) || + ts.isClassExpression(node) || + ts.isFunctionExpression(node) || + ts.isFunctionDeclaration(node)) + ) { + ts.walkPreorder(node, (inner) => { + if (ts.isPrivateIdentifier(inner)) ok = false; + return ok ? undefined : "stop"; + }); + return "skip"; + } + if (ts.isPrivateIdentifier(node)) { + const parent = node.parent; + const onThis = + parent === member || + (ts.isPropertyAccessExpression(parent) && + parent.name === node && + parent.expression.kind === ts.SyntaxKind.ThisKeyword) || + (ts.isBinaryExpression(parent) && + parent.left === node && + parent.operatorToken.kind === ts.SyntaxKind.InKeyword && + parent.right.kind === ts.SyntaxKind.ThisKeyword); + if (!onThis) ok = false; + } + return undefined; + }); + if (!ok) return false; + } + return true; +} + /** Collect a class expression once per enclosing specialization. Function-local * evaluations allocate fresh constructor objects; top-level expressions use * immortal templates. Static initialization remains limited to whole top-level @@ -5449,7 +5710,8 @@ export function lowerClassExpressionInfo( } if ( local && - expr.members.nodes.some((member) => member.name && ts.isPrivateIdentifier(member.name)) + expr.members.nodes.some((member) => member.name && ts.isPrivateIdentifier(member.name)) && + !localPrivateFieldsAgree(expr) ) { lowerer.unsupported("SC1090", expr, "function-local classes with private brands"); } @@ -5460,7 +5722,7 @@ export function lowerClassExpressionInfo( expr.members.nodes.some( (member) => decoratorNodesOf(member).length || - (!isJsSourceFile(expr.getSourceFile()) && + (!isJsSourceFile(ts.getSourceFileOfNode(expr)) && (ts.isClassStaticBlockDeclaration(member) || ts .getModifiers(member) @@ -5584,7 +5846,7 @@ export function lowerClassExpression(lowerer: Lowerer, expr: ts.ClassLikeDeclara decoratorNodesOf(expr).length > 0 || expr.members.nodes.some((m) => decoratorNodesOf(m).length > 0) ) { - if (!isJsSourceFile(expr.getSourceFile()) && expr.typeParameters === undefined) { + if (!isJsSourceFile(ts.getSourceFileOfNode(expr)) && expr.typeParameters === undefined) { const thrown = guaranteedDecorationThrow(lowerer, expr); if (thrown) { // The expression's static type never materializes — the read @@ -5870,12 +6132,12 @@ function enclosingTopLevelClassAssignment(expr: ts.ClassExpression): ts.BinaryEx * `Common.I`): a second assignment makes the binding dynamic, so * callers refuse to pin the first one's class. */ function countAssignmentsTo(assign: ts.BinaryExpression): number { - const sf = assign.getSourceFile(); + const sf = ts.getSourceFileOfNode(assign); // `exports.I` and `module.exports.I` are the SAME binding in Node // (exports aliases module.exports until a table replaces it) — fold // the member spellings together before comparing. const canon = (lhs: ts.Expression): string => { - const text = lhs.getText().replace(/\s+/g, ""); + const text = ts.getTextOfNode(lhs).replace(/\s+/g, ""); return text.startsWith("module.exports.") ? text.slice("module.".length) : text; }; const target = canon(assign.left); @@ -5933,10 +6195,10 @@ export function lowerStaticMethodCall( if (!ts.isIdentifier(access.expression)) { if ( !isModuleExportsAccess(access.expression) || - !isCjsJsFile(access.getSourceFile(), lowerer.program) + !isCjsJsFile(ts.getSourceFileOfNode(access), lowerer.program) ) { let receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); - if (receiverType?.kind !== "classval" && isJsSourceFile(call.getSourceFile())) { + if (receiverType?.kind !== "classval" && isJsSourceFile(ts.getSourceFileOfNode(call))) { const receiver = tryLowerExpression(lowerer, access.expression); if (receiver?.type.kind === "classval") receiverType = receiver.type; } @@ -5957,7 +6219,7 @@ export function lowerStaticMethodCall( loc: locOf(call), }; } - const whole = cjsClassExprWholeExportOf(access.getSourceFile()); + const whole = cjsClassExprWholeExportOf(ts.getSourceFileOfNode(access)); if (!whole) return null; return staticCallOn( lowerer, @@ -6254,9 +6516,9 @@ function hasLexicalSuper(root: ts.Node): boolean { ts.isClassExpression(node) ) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } @@ -6285,9 +6547,9 @@ function capturesEnclosingScope( if (decl && decl !== scope && inside(decl, scope) && !inside(decl, member)) captured = true; return; } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - member.forEachChild(visit); + ts.forEachChild(member, visit); return captured; } @@ -6410,7 +6672,10 @@ export function lowerClassValueProperty( // `module.exports.label` in a class-replaced CJS module: the // receiver is the exact exported class, and the read is // side-effect-free — as bindable as an identifier. - !(isModuleExportsAccess(expr.expression) && isCjsJsFile(expr.getSourceFile(), lowerer.program)) + !( + isModuleExportsAccess(expr.expression) && + isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) + ) ) { // Devirtualized reads DISCARD the receiver value, so only // side-effect-free receivers are claimed (the instanceOf fold @@ -6494,6 +6759,28 @@ function abstractMemberSignature( /** A nominal return subtype uses the same owned pointer as the inherited * return slot. The current class is still being collected, so consult its * already collected base chain before the complete class graph is available. */ +/** An inherited ASYNC method that an override now dispatches against: its + * slot becomes a plain function returning the body's promise, and the + * async body lowers behind it as `%C.m$own` (the adapter path with an + * identity conversion), so a virtual call never enters an async body + * without its spawn wrapper. */ +function thunkAsyncSlot( + overridden: { declarer: ClassInfo; sig: ClassMethodSignature }, + mName: string, +): void { + if (overridden.sig.async !== true) return; + const declarer = overridden.declarer; + declarer.returnAdapters ??= new Map(); + if (!declarer.returnAdapters.has(mName)) { + declarer.returnAdapters.set(mName, { + params: [...overridden.sig.params], + ret: overridden.sig.ret, + async: true, + }); + } + delete overridden.sig.async; +} + function overrideReturnAbi( lowerer: Lowerer, declared: IrType, @@ -6652,7 +6939,7 @@ export function collectInheritedJsFieldWrites( if (name !== null) addWrite(name, part.right); } } - part.forEachChild(scan); + ts.forEachChild(part, scan); }; for (const member of node.members.nodes) { if ( @@ -6693,7 +6980,7 @@ export function collectInheritedJsFieldWrites( if ( !owner || (!ts.isClassDeclaration(owner) && !ts.isClassExpression(owner)) || - !isJsSourceFile(owner.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(owner)) ) continue; let fields = lowerer.inheritedJsFieldWrites.get(owner); @@ -6731,7 +7018,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if ( node.heritageClauses?.nodes.length && accessors.length && - isJsSourceFile(node.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(node)) ) { const symbol = lowerer.typeOf(node).getSymbol(); const instance = symbol ? lowerer.checker.getDeclaredTypeOfSymbol(symbol) : null; @@ -6748,7 +7035,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if ( (ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration)) && - isJsSourceFile(declaration.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(declaration)) ) { lowerer.checkedVirtualJsAccessors.add(declaration); lowerer.checkedVirtualJsAccessors.add(member); @@ -6790,12 +7077,12 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou for (const declaration of property ? property.declarations : []) { if ( ts.isMethodDeclaration(declaration) && - isJsSourceFile(declaration.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(declaration)) && !checkedJsOverride(lowerer, declaration, member) ) { lowerer.virtualJsMethods.add(declaration); if ( - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && !member.typeParameters?.nodes && !member.asteriskToken && !member.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) && @@ -6841,7 +7128,7 @@ export function collectVirtualJsMethods(lowerer: Lowerer, files: readonly ts.Sou if (node !== member && ts.isFunctionLike(node)) return; if (ts.isReturnStatement(node) && node.expression) lowerer.checkedVirtualJsValueReturns.add(member); - node.forEachChild(scan); + ts.forEachChild(node, scan); }; scan(member); } @@ -6874,7 +7161,7 @@ function checkedJsOverride( ): boolean { return [base, member].every( (method) => - lowerer.checkedJsFile(method.getSourceFile()) && + lowerer.checkedJsFile(ts.getSourceFileOfNode(method)) && method.parameters.nodes.every(documentedParameter), ); } @@ -7022,7 +7309,7 @@ export function lowerClassGenericMethodCall( result.type.kind === "object" && !ts.isExpressionStatement(call.parent) && ts.isMethodDeclaration(info.decl) && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && returnsOnlyThisValue(info.decl) ) { return lowerer.maybeNarrow(result, call); @@ -7127,7 +7414,7 @@ export function lowerClassMembers(lowerer: Lowerer, info: ClassInfo): IrFunction out.push(lowerer.lowerClassCtor(info)); } catch (e) { if (!(e instanceof PoisonError)) throw e; - if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + if (info.decl && isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { const types: IrType[] = [ { kind: "object", className }, ...info.ctorParams.map((param) => param.type), @@ -7716,6 +8003,82 @@ function lowerClassMethodMemberInner( allowDiagSink: true, }); } + // An override converting its declared result into the inherited ABI: + // the body lowers as `%C.m$own` under its declared result, and the slot + // function forwards to it and converts what it returns. + const adapter = info.returnAdapters?.get(mName); + if (adapter) { + const ownSig: ClassMethodSignature = { ...sig, params: adapter.params, ret: adapter.ret }; + if (adapter.async) ownSig.async = true; + const own = lowerClassMethodBody(lowerer, info, fnLike, `%${className}.${mName}$own`, ownSig); + if (!own) return null; + lowerer.liftedFns.push(own); + const loc = locOf(fnLike); + const params: IrParam[] = [ + { localId: "this.0", name: "this", type: thisType }, + ...sig.params.map((p, i) => ({ localId: `p.${i}`, name: `p${i}`, type: p.type })), + ]; + const fnCtx = newFnCtx(false, null, null, sig.ret); + lowerer.fnStack.push(fnCtx); + try { + const call: IrExpr = { + kind: "call", + callee: own.name, + args: params.map((p, i): IrExpr => { + const ref: IrExpr = { kind: "varRef", localId: p.localId, type: p.type, loc }; + // `this` passes through; each inherited argument converts into + // the override's declared parameter (checked where it narrows). + return i === 0 ? ref : lowerer.coerceInto(fnLike.name, ref, adapter.params[i - 1]!.type); + }), + type: adapter.ret, + loc, + }; + // A void slot discards the override's result after running it. A + // toString slot hands on what every ToString consumer of the result + // makes of it (`String(err)`, templates): undefined reads + // "undefined", as in Node. Other slots check the result into the + // inherited type. + const result = (): IrExpr => + mName === "toString" && sig.ret.kind === "string" + ? call.type.kind === "dyn" + ? { kind: "libCall", fn: "dyn.stringConstructor", args: [call], type: STRING, loc } + : lowerer.ensureString(call, fnLike.name) + : lowerer.coerceInto(fnLike.name, call, sig.ret); + const body: IrStmt[] = + sig.ret.kind === "void" + ? [ + { kind: "exprStmt", expr: call, loc }, + { kind: "return", value: null, loc }, + ] + : [{ kind: "return", value: result(), loc }]; + return { + name: `%${className}.${mName}`, + params, + returnType: sig.ret, + locals: [ + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...lowerer.ctx.locals, + ], + body, + loc, + }; + } finally { + lowerer.fnStack.pop(); + } + } + return lowerClassMethodBody(lowerer, info, fnLike, `%${className}.${mName}`, sig); +} + +function lowerClassMethodBody( + lowerer: Lowerer, + info: ClassInfo, + fnLike: ts.MethodDeclaration | ts.AccessorDeclaration, + fnName: string, + sig: ClassMethodSignature, +): IrFunction | null { + const className = info.def.name; + const thisType: IrType = { kind: "object", className }; + if (!fnLike.body) return null; const prevClass = lowerer.currentClass; lowerer.currentClass = info; // ASYNC methods: the module function is an async IrFunction — its @@ -7757,7 +8120,7 @@ function lowerClassMethodMemberInner( const body = [...declared.prologue, ...lowerer.lowerStmts(fnLike.body.statements.nodes)]; appendImplicitUndefinedReturn(lowerer, body, bodyReturn, locOf(fnLike)); const fn: IrFunction = { - name: `%${className}.${mName}`, + name: fnName, params, returnType: bodyReturn, locals: lowerer.ctx.locals, @@ -7942,6 +8305,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL className: info.def.name, field: f.name, value: dynUndefinedExpr(loc), + define: true, loc, }); } @@ -7957,12 +8321,13 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL className: info.def.name, field: f.name, value, + define: true, loc: locOf(f.initializer), }); } catch (e) { if (!(e instanceof PoisonError)) throw e; lowerer.countStatementFailed(f.initializer, locOf(f.initializer).file); - if (isJsSourceFile(f.initializer.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(f.initializer))) { const fence = lowerer.diagSink === null ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) @@ -8461,7 +8826,7 @@ export function lowerSuperMethodCall( kind: "dynCall", callee, receiver: lowerer.coerceToExpected(thisRef, DYN), - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), type: DYN, loc, @@ -8614,6 +8979,60 @@ function inheritsBuiltinStreamCtor(lowerer: Lowerer, info: ClassInfo): boolean { return false; } +/** The run-time form of refuseInheritedBuiltinCtorArgs, for construction + * from checked-dynamic code (`new F(options)` with F an untyped value + * holding C), whose argument count is known only to the caller: a + * runtimeFence located at C, or null when C's constructor takes its + * arguments. */ +export function inheritedBuiltinCtorFence( + lowerer: Lowerer, + info: ClassInfo, + loc: SrcLoc, +): IrStmt | null { + let feature: string | null = null; + if (inheritsBuiltinStreamCtor(lowerer, info)) + feature = `new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor (declare a constructor that passes an inline options object to super(...))`; + else if (lowerer.inheritsBuiltinEmitterCtor(info)) + feature = "EventEmitter constructor options ('captureRejections')"; + if (feature === null) return null; + // Located at the class, whose constructor takes no arguments. + const at = info.decl !== null ? locOf(info.decl) : loc; + const diag = unsupportedDiag("SC1090", at, feature); + lowerer.runtimeFences.push(diag); + const line = + info.decl !== null + ? ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(info.decl), at.start).line + 1 + : null; + const where = line !== null ? ` [${diag.code} at ${at.file}:${line}]` : ""; + return { kind: "runtimeFence", code: diag.code, message: `${diag.message}${where}`, loc }; +} + +/** `new C(args)` where C's constructor is the inherited runtime stream or + * EventEmitter constructor (a ctor-less chain into the base): that + * constructor takes zero arguments here (the options bag fences, like the + * super() form), so arguments report at the first one. Applies to direct + * construction and to construction through a class value of C. Stream + * subclasses come first: their chain roots at the emitter too, but the + * message should name the stream story. */ +function refuseInheritedBuiltinCtorArgs( + lowerer: Lowerer, + info: ClassInfo, + expr: ts.NewExpression, +): void { + const first = expr.arguments?.nodes[0]; + if (first === undefined) return; + if (inheritsBuiltinStreamCtor(lowerer, info)) { + lowerer.noLowering( + `new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor`, + first, + "declare a constructor that passes an inline options object to super(...)", + ); + } + if (lowerer.inheritsBuiltinEmitterCtor(info)) { + lowerer.unsupported("SC1090", first, "EventEmitter constructor options ('captureRejections')"); + } +} + /** Preserve argument evaluation order and option presence through builtin * and inherited Error constructors. Checked-dynamic conversion retains * Error identity and distinguishes a missing cause from undefined. */ @@ -8738,24 +9157,7 @@ function lowerProgramClassNew( "construction through a function-local class's internal name", ); lowerer.noteEdge(`%${info.def.name}.constructor`); - // A ctor-less chain into an EventEmitter base inherits `new C()` — - // zero arguments (the options bag fences, like the super() form). - // Stream subclasses come first: their chain roots at the emitter - // too, but the message should name the stream story. - if (inheritsBuiltinStreamCtor(lowerer, info) && (expr.arguments?.nodes ?? []).length > 0) { - lowerer.noLowering( - `new ${info.def.name.replace(/^%/, "")} with arguments through an inherited stream constructor`, - expr.arguments!.nodes[0]!, - "declare a constructor that passes an inline options object to super(...)", - ); - } - if (lowerer.inheritsBuiltinEmitterCtor(info) && (expr.arguments?.nodes ?? []).length > 0) { - lowerer.unsupported( - "SC1090", - expr.arguments!.nodes[0]!, - "EventEmitter constructor options ('captureRejections')", - ); - } + refuseInheritedBuiltinCtorArgs(lowerer, info, expr); // A ctor-less chain into a builtin error base inherits both arguments; // each synthesized constructor forwards the same pair to its base. const args = lowerer.inheritsBuiltinErrorCtor(info) @@ -8802,6 +9204,20 @@ function isCollectionOrErrorClass(ref: BuiltinClassRef | null): boolean { export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const loc = locOf(expr); + // new Function(...): Node's EvalError under + // --disallow-code-generation-from-strings (code-generation.ts). + if (codeGenerationCalleeOf(lowerer, expr.expression) === "Function") + return lowerCodeGenerationCall(lowerer, expr, "Function"); + // `new .X(...)`: Node 24 has no such global, so the callee's + // root read throws the ReferenceError before any argument evaluates. + { + const root = ambientUndefVarRootOf(lowerer, expr.expression); + if (root !== null && root.text === TEMPORAL_GLOBAL) { + const t = + ambientUndefReadType(lowerer, expr) ?? contextualUndefReadType(lowerer, expr) ?? F64; + return nsUndefRead(lowerer, root.text, expr, t); + } + } // `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) { @@ -9033,7 +9449,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // lowering ahead of the assignment statement). if ( !expr.expression.questionDotToken && - ((isCjsJsFile(expr.getSourceFile(), lowerer.program) && + ((isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program) && (isModuleExportsAccess(expr.expression.expression) || (ts.isIdentifier(expr.expression.expression) && expr.expression.expression.text === "exports" && @@ -9049,7 +9465,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { memberSym, resolved, lowerer.resolveValueSymbol(expr.expression.name) ?? undefined, - lowerer.cjsModuleExportSymbol(expr.getSourceFile(), expr.expression.name.text), + lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(expr), expr.expression.name.text), ]; const exportSym = candidates.find((s) => s !== undefined); const g = candidates @@ -9264,7 +9680,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // libregexp engine the literals ride. The pattern compiles EAGERLY, // so bad input throws Node's catchable SyntaxError at construction. // Checked arguments retain native regexes and perform runtime coercion. - if (symbol && symbol.name === "RegExp" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "RegExp" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; if (args.length > 2) { lowerer.noLowering(`new RegExp with ${args.length} arguments`, expr); @@ -9332,32 +9748,71 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { loc, }; } - if (symbol && symbol.name === "URL" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "URL" && lowerer.isStdlibSymbol(symbol)) { return lowerUrlNew(lowerer, expr); } // `new URLSearchParams(init?)`: the WHATWG list (stdlib provenance — // see lowerSearchParamsNew for the lowered init shapes). - if (symbol && symbol.name === "URLSearchParams" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "URLSearchParams" && lowerer.isStdlibSymbol(symbol)) { return lowerSearchParamsNew(lowerer, expr, loc); } // Date's read-only value slice: store the constructor's TimeClip'd // epoch milliseconds as the scalar date kind. That is sufficient // for every getter and toISOString; identity and setters stay // fenced, so copying the scalar cannot create an observable lie. - if (symbol && symbol.name === "Date" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "Date" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; - if (args.some(ts.isSpreadElement) || args.length > 1) { + if (args.some(ts.isSpreadElement)) { lowerer.noLowering( - `new Date with ${args.length} arguments`, + `new Date with spread arguments`, expr, - "new Date(), new Date(milliseconds), and new Date(dateString) are supported; the local-time year/month field constructor has no lowering", + "new Date(), new Date(milliseconds), new Date(dateString), and new Date(year, monthIndex, ...) with listed arguments are supported", symbol, ); } - if (isJsSourceFile(expr.getSourceFile())) { + // new Date(year, monthIndex[, date[, hours[, minutes[, seconds[, ms]]]]]): + // local-time fields, converted to UTC in the host zone at run time. + // JavaScript callers pass their arguments to the runtime's ToNumber + // (in order, exactly the constructor's coercions); typed callers + // pass numbers, completed with the spec's defaults (date 1, time + // parts 0) for the seven-f64 ABI. + if (args.length > 1) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { + return { + kind: "libCall", + fn: "date.nativeNewFields", + args: [ + { + kind: "dynArrLit", + elems: args.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), + type: DYN, + loc, + }, + ], + type: DYN, + loc, + }; + } + if (args.length > 7) { + lowerer.noLowering(`new Date with ${args.length} arguments`, expr, undefined, symbol); + } + const defaults = [0, 0, 1, 0, 0, 0, 0]; + const fields: IrExpr[] = []; + for (let i = 0; i < 7; i++) { + const arg = args[i]; + fields.push( + arg !== undefined + ? lowerer.lowerExprExpecting(arg, F64) + : { kind: "numLit", value: defaults[i]!, type: F64, loc }, + ); + } + return { kind: "libCall", fn: "date.newLocal", args: fields, type: DATE_T, loc }; + } + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { return { kind: "libCall", - fn: "date.nativeNew", + // One argument: a string parses with the full date parser. + fn: args.length === 1 ? "date.nativeNewValue" : "date.nativeNew", args: [ { kind: "dynArrLit", @@ -9398,7 +9853,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // normalized at run time (Node's normalizeEncoding, which throws // ERR_UNKNOWN_ENCODING for an unknown name). Omitted means utf8, // Node's default. - if (symbol && symbol.name === "StringDecoder" && lowerer.isStdlibSymbol(symbol)) { + if (symbol && ts.symbolName(symbol) === "StringDecoder" && lowerer.isStdlibSymbol(symbol)) { const args = expr.arguments?.nodes ?? []; if (args.length > 1) { lowerer.noLowering("new StringDecoder with 2 arguments", expr, undefined, symbol); @@ -9432,12 +9887,13 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { loc, }; } + const codec = symbol ? ts.symbolName(symbol) : undefined; if ( symbol && - (symbol.name === "TextDecoder" || symbol.name === "TextEncoder") && + (codec === "TextDecoder" || codec === "TextEncoder") && lowerer.isStdlibSymbol(symbol) ) { - return lowerTextCodecNew(lowerer, expr, symbol.name); + return lowerTextCodecNew(lowerer, expr, codec); } // `new Uint8Array(...)` / `new Uint32Array(...)` / `new // Float32Array(...)` / `new Float64Array(...)`: the typed-array @@ -9471,11 +9927,11 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // types get their half named specifically instead of the component // fence (SC2009, which names Map slots at value positions elsewhere). // Both Array() and new Array() share the elements/count lowering. - if (symbol?.name === "Object" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Object" && lowerer.isStdlibSymbol(symbol)) { const object = lowerObjectConstructor(lowerer, expr.arguments?.nodes ?? [], loc); if (object) return object; } - if (symbol?.name === "Array" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Array" && lowerer.isStdlibSymbol(symbol)) { return lowerArrayConstructor(lowerer, expr, expr.arguments?.nodes ?? []); } // A const alias (`const M = Map`) constructs the same Map. @@ -9491,7 +9947,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { : null; let tsType = lowerer.typeOf(expr); let mapped = lowerer.mapTypeOf(tsType); - if (mapped === null && isJsSourceFile(expr.getSourceFile())) mapped = lowerer.irTypeOf(expr); + if (mapped === null && isJsSourceFile(ts.getSourceFileOfNode(expr))) + mapped = lowerer.irTypeOf(expr); const fieldType = assignedThisFieldType(lowerer, expr); if (mapped?.kind !== "map" && fieldType?.kind === "map") mapped = fieldType; // A fresh map built from pair literals can use its destination's @@ -9553,7 +10010,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // unconditionally; the format path never touches them. TypeScript // keeps the compile fence. if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) === 0 && targs.length > 0 && targs.every((t) => (t.flags & ts.TypeFlags.Any) !== 0) @@ -9589,7 +10046,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } // Weak containers share checked native key identities and lifetime hooks. if ( - (symbol?.name === "WeakMap" || symbol?.name === "WeakSet") && + ((symbol !== null && ts.symbolName(symbol) === "WeakMap") || + (symbol !== null && ts.symbolName(symbol) === "WeakSet")) && lowerer.isStdlibSymbol(symbol) ) { const args = expr.arguments?.nodes ?? []; @@ -9597,13 +10055,13 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { lowerer.unsupported( "SC1090", expr, - `${symbol.name} construction with surplus or spread arguments`, + `${ts.symbolName(symbol)} construction with surplus or spread arguments`, ); } return { kind: "libCall", - fn: symbol.name === "WeakMap" ? "weakMap.new" : "weakSet.new", + fn: ts.symbolName(symbol) === "WeakMap" ? "weakMap.new" : "weakSet.new", args: [args[0] ? lowerer.lowerExprExpecting(args[0], DYN) : dynUndefinedExpr(loc)], type: DYN, loc, @@ -9622,7 +10080,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { } if ( !mapped && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) <= 1 && !expr.arguments?.nodes.some(ts.isSpreadElement) ) { @@ -9645,7 +10103,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // the honest construction is a Set of those scalars. if ( !mapped && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && (expr.arguments?.nodes.length ?? 0) === 1 && ts.isArrayLiteralExpression(expr.arguments!.nodes[0]!) && !(expr.arguments!.nodes[0] as ts.ArrayLiteralExpression).elements.nodes.some( @@ -9688,7 +10146,11 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // `new AsyncLocalStorage()` (node:async_hooks): a fresh store id — // an f64 handle (type-mapper.ts), the Channel story. Construction options // ({ defaultValue, name }) have no lowering yet. - if (symbol?.name === "AsyncLocalStorage" && lowerer.isStdlibSymbol(symbol)) { + if ( + symbol !== null && + ts.symbolName(symbol) === "AsyncLocalStorage" && + lowerer.isStdlibSymbol(symbol) + ) { if ((expr.arguments?.nodes.length ?? 0) > 0) { lowerer.noLowering( "new AsyncLocalStorage(options)", @@ -9699,7 +10161,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { return { kind: "libCall", fn: "als.new", args: [], type: F64, loc }; } // `new Promise((resolve) => ...)`: the ambient Promise constructor. - if (symbol?.name === "Promise" && lowerer.isStdlibSymbol(symbol)) { + if (symbol !== null && ts.symbolName(symbol) === "Promise" && lowerer.isStdlibSymbol(symbol)) { const type = lowerer.irTypeOf(expr); if (type.kind !== "promise") lowerer.badType(expr, lowerer.typeOf(expr)); const args = expr.arguments?.nodes ?? []; @@ -9715,7 +10177,10 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { if (ts.isIdentifier(a0) && a0.text === "setImmediate") { const sym = lowerer.checker.getSymbolAtLocation(a0); const decls = sym ? sym.declarations : []; - if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if ( + decls.length > 0 && + decls.every((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) + ) { // The settled value is the undefined dyn value — the result // is promise whatever T the checker inferred for the // unusual executor (Promise in the suite's shape). @@ -9736,7 +10201,9 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { // catch-side instanceof and the uncaught printer working). First // settle wins, exactly JS: reject-after-resolve and double-reject // are no-ops, and an executor throw after any settle is swallowed. - const executor = lowerer.lowerExpr(args[0]!); + let executor = lowerer.lowerExpr(args[0]!); + if (executor.type.kind !== "func") lowerer.badType(args[0]!, lowerer.typeOf(args[0]!)); + executor = adaptPromiseExecutor(lowerer, executor, type.inner, loc) ?? executor; if (executor.type.kind !== "func") lowerer.badType(args[0]!, lowerer.typeOf(args[0]!)); if (executor.type.params.length > 1) { const rj = executor.type.params[1]!; @@ -10003,6 +10470,9 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { `EventEmitter and stream class values constructed with options (construct with 'new ${info.def.name.slice(1)}({ ... })' directly)`, ); } + // A program class whose constructor is the inherited runtime one + // takes no arguments through its class value either. + refuseInheritedBuiltinCtorArgs(lowerer, info, expr); defineClassValueConstruct(lowerer, info); const args = lowerer.completeArgs( expr.arguments?.nodes ?? [], @@ -10041,7 +10511,8 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { if ( def.arms.every( (arm) => - arm.kind === "classval" || (isJsSourceFile(expr.getSourceFile()) && isUnitType(arm)), + arm.kind === "classval" || + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && isUnitType(arm)), ) && lowerer.dynConvertible(calleeT) ) { @@ -10074,7 +10545,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr { const sym = raw && raw.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(raw) : raw; const hint = - sym && lowerer.isStdlibSymbol(sym) ? own(STDLIB_CTOR_HINTS, sym.name) : undefined; + sym && lowerer.isStdlibSymbol(sym) ? own(STDLIB_CTOR_HINTS, ts.symbolName(sym)) : undefined; if (hint !== undefined) { lowerer.unsupported("SC1090", expr, hint); } diff --git a/packages/compiler/src/frontend/lowering/lower-comptime.ts b/packages/compiler/src/frontend/lowering/lower-comptime.ts index 41c6704c5..e442b34a5 100644 --- a/packages/compiler/src/frontend/lowering/lower-comptime.ts +++ b/packages/compiler/src/frontend/lowering/lower-comptime.ts @@ -4,7 +4,7 @@ import { InternalCompilerError } from "../../errors.js"; * literals — records/arrays/unions included, subject to comptimeBakeable. */ import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; -import { type IrExpr, type IrType } from "../../ir/ir.js"; +import { isUnitType, type IrExpr, type IrType } from "../../ir/ir.js"; import { comptimeFailedDiag } from "../../diagnostics/diagnostic.js"; import { locOf } from "../program.js"; import { PoisonError } from "./lowerer.js"; @@ -76,7 +76,7 @@ export function lowerComptime(lowerer: Lowerer, expr: ts.CallExpression): IrExpr "SC1090", expr, `baking comptime results of type '${lowerer.fmt(target)}' into the binary ` + - `(only number, string, boolean, arrays, and records lower to literals)`, + `(number, string, boolean, and arrays, records and unions of those lower to literals; a record or union type reachable from itself does not)`, ); } // 5. The host executes the extracted callback under a finite budget. @@ -86,7 +86,7 @@ export function lowerComptime(lowerer: Lowerer, expr: ts.CallExpression): IrExpr try { if (lowerer.frontendServices === undefined) throw new Error("compile-time evaluation requires frontend services"); - result = lowerer.frontendServices.evaluateComptime(cb.getText(), COMPTIME_TIMEOUT_MS); + result = lowerer.frontendServices.evaluateComptime(ts.getTextOfNode(cb), COMPTIME_TIMEOUT_MS); } catch (e) { const detail = comptimeFailureDetail(e); lowerer.pushDiag(comptimeFailedDiag(detail, locOf(cb))); @@ -110,20 +110,104 @@ function comptimeFailureDetail(error: unknown): string { /** True when a comptime result of this type can be written as a literal * expression: number/string/boolean, arrays and record shapes of those, - * nested. Closures, class instances, unions, promises, dyn, and void - * cannot be baked. */ + * and unions of those with null or undefined (the mixed rows of a JSON + * table: `(string | number)[][]`), nested. Closures, class instances, + * promises, dyn, void, and recursive types (a record or union reachable + * from itself, directly or through arrays) cannot be baked. */ export function comptimeBakeable(lowerer: Lowerer, t: IrType): boolean { + return bakeableWithin(lowerer, t, new Set()); +} + +/** comptimeBakeable below the records and unions in `open` (keyed + * `record:` and `union:`), whose members are being + * checked: reaching one of them again means the type is recursive. */ +function bakeableWithin(lowerer: Lowerer, t: IrType, open: Set): boolean { switch (t.kind) { case "f64": case "string": case "bool": return true; case "array": - return lowerer.comptimeBakeable(t.elem); + return bakeableWithin(lowerer, t.elem, open); case "record": { const shape = lowerer.shapes.get(t.shapeId); - return !!shape && shape.fields.every((f) => lowerer.comptimeBakeable(f.type)); + const key = `record:${t.shapeId}`; + if (!shape || open.has(key)) return false; + open.add(key); + const bakeable = shape.fields.every((f) => bakeableWithin(lowerer, f.type, open)); + open.delete(key); + return bakeable; } + case "union": { + const arms = lowerer.unions.get(t.unionId)?.arms; + const key = `union:${t.unionId}`; + if (arms === undefined || open.has(key)) return false; + open.add(key); + const bakeable = arms.every( + (arm) => isUnitType(arm) || (arm.kind !== "union" && bakeableWithin(lowerer, arm, open)), + ); + open.delete(key); + return bakeable; + } + default: + return false; + } +} + +/** The tag of the arm of union `unionId` that `value` bakes into, or -1. + * A record value whose discriminant field holds one of the union's + * discriminant values takes that case's arm, since arms with overlapping + * shapes can both fit it. Any other value takes the first arm it fits, in + * the union's arm order: a JSON value fits exactly one scalar arm, and + * array or record arms are told apart by their contents. */ +function comptimeUnionArm(lowerer: Lowerer, unionId: string, value: unknown): number { + const def = lowerer.unions.get(unionId); + if (!def) return -1; + const discriminant = def.discriminant; + if ( + discriminant !== undefined && + typeof value === "object" && + value !== null && + !Array.isArray(value) + ) { + const key = (value as Record)[discriminant.field]; + const named = discriminant.cases.find((c) => c.values.some((v) => v === key)); + if (named !== undefined) + return comptimeFits(lowerer, value, def.arms[named.tag]!) ? named.tag : -1; + } + return def.arms.findIndex((arm) => comptimeFits(lowerer, value, arm)); +} + +/** Whether `value` bakes into the bakeable type `t` without a mismatch: + * the arm choice of a union literal. Records are exact, as in + * comptimeValueToIr; an absent field reads as undefined. */ +function comptimeFits(lowerer: Lowerer, value: unknown, t: IrType): boolean { + switch (t.kind) { + case "f64": + return typeof value === "number" && Number.isFinite(value); + case "string": + return typeof value === "string"; + case "bool": + return typeof value === "boolean"; + case "nullT": + return value === null; + case "undefinedT": + return value === undefined; + case "array": + return Array.isArray(value) && value.every((el) => comptimeFits(lowerer, el, t.elem)); + case "record": { + if (typeof value !== "object" || value === null || Array.isArray(value)) return false; + const shape = lowerer.shapes.get(t.shapeId); + if (!shape) return false; + const obj = value as Record; + const declared = new Set(shape.fields.map((f) => f.name)); + return ( + Object.keys(obj).every((key) => declared.has(key)) && + shape.fields.every((f) => comptimeFits(lowerer, obj[f.name], f.type)) + ); + } + case "union": + return comptimeUnionArm(lowerer, t.unionId, value) >= 0; default: return false; } @@ -142,7 +226,7 @@ export function rejectComptimeCaptures( lowerer: Lowerer, cb: ts.ArrowFunction | ts.FunctionExpression, ): void { - const sf = cb.getSourceFile(); + const sf = ts.getSourceFileOfNode(cb); const inTypePosition = (node: ts.Node): boolean => { for (let p: ts.Node | undefined = node.parent; p && p !== cb; p = p.parent) { if (ts.isTypeNode(p)) return true; @@ -165,7 +249,7 @@ export function rejectComptimeCaptures( const decls = symbol ? symbol.declarations : undefined; if (symbol && decls && decls.length > 0) { const ownLocal = decls.every( - (d) => d.getSourceFile() === sf && d.pos >= cb.pos && d.end <= cb.end, + (d) => ts.getSourceFileOfNode(d) === sf && d.pos >= cb.pos && d.end <= cb.end, ); if (!ownLocal) { if (node.text === "comptime" && lowerer.isStdlibSymbol(symbol)) { @@ -265,6 +349,23 @@ export function comptimeValueToIr( })); return { kind: "recordLit", fields, type: expected, loc }; } + case "union": { + const arms = lowerer.unions.get(expected.unionId)!.arms; // bakeable-checked + const tag = comptimeUnionArm(lowerer, expected.unionId, value); + if (tag < 0) fail(describeComptimeValue(value)); + const arm = arms[tag]!; + const payload: IrExpr = isUnitType(arm) + ? { kind: "unitLit", unit: arm.kind === "nullT" ? "null" : "undefined", type: arm, loc } + : lowerer.comptimeValueToIr(value, arm, path, blame); + return { + kind: "unionWrap", + unionId: expected.unionId, + tag, + value: payload, + type: expected, + loc, + }; + } default: // comptimeBakeable already fenced these off. throw new InternalCompilerError(`lowerer bug: unbakeable comptime target '${expected.kind}'`); diff --git a/packages/compiler/src/frontend/lowering/lower-console.ts b/packages/compiler/src/frontend/lowering/lower-console.ts index a7fac4f51..eec308342 100644 --- a/packages/compiler/src/frontend/lowering/lower-console.ts +++ b/packages/compiler/src/frontend/lowering/lower-console.ts @@ -209,7 +209,7 @@ export function lowerConsoleInstanceCall( if (access.questionDotToken) return null; const recvType = lowerer.typeOf(access.expression); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Console" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Console" || !lowerer.isStdlibSymbol(sym)) return null; const toStderr = OUTPUT_MEMBERS.get(access.name.text); if (toStderr === undefined) { lowerer.noLowering( diff --git a/packages/compiler/src/frontend/lowering/lower-dgram.ts b/packages/compiler/src/frontend/lowering/lower-dgram.ts index 54f9d158b..4cb238352 100644 --- a/packages/compiler/src/frontend/lowering/lower-dgram.ts +++ b/packages/compiler/src/frontend/lowering/lower-dgram.ts @@ -15,6 +15,7 @@ import { BOOL, canBoxFuncIntoDyn, canDynCheckTo, + typeKey, DGRAMSOCK_T, DYN, F64, @@ -131,6 +132,7 @@ export function lowerDgramDnsModuleCall( loc: SrcLoc, ): IrExpr | null { if (bi.module === "dns") return lowerDnsModuleCall(lowerer, expr, bi, loc); + if (bi.module === "dns/promises") return lowerDnsPromisesCall(lowerer, expr, bi, loc); if (bi.module !== "dgram") return null; const args = expr.arguments.nodes; if (bi.member === "createSocket") { @@ -193,7 +195,7 @@ export function lowerDgramDnsModuleCall( sawType = true; } else if (name === "reuseAddr") { reuseAddr = lowerer.lowerExprExpecting(prop.initializer, BOOL); - } else if (name === "signal" && isJsSourceFile(expr.getSourceFile())) { + } else if (name === "signal" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { // A provably-non-AbortSignal signal (the invalid-input probes: // strings, numbers, plain records) throws Node's // validateAbortSignal ladder; plausible signal values keep the @@ -278,6 +280,124 @@ function lowerDnsCallback(lowerer: Lowerer, node: ts.Expression, what: string): ); } +/** node:dns/promises calls (and dns.promises members): lookup(hostname[, + * options]) and lookupService(address, port) as promises over the callback + * forms' runtime (argument errors throw synchronously, as Node validates + * before it creates the promise), plus the shared getServers and result + * order functions. A typed result (Promise) checks the + * settled value through the checked-dynamic boundary. */ +function lowerDnsPromisesCall( + lowerer: Lowerer, + expr: ts.CallExpression, + bi: { module: string; member: string }, + loc: SrcLoc, +): IrExpr { + const args = expr.arguments.nodes; + const dyn = (node: ts.Expression): IrExpr => + lowerer.coerceToExpected(lowerer.lowerExpr(node), DYN); + if (bi.member === "lookup" || bi.member === "lookupService") { + const service = bi.member === "lookupService"; + if (service ? args.length !== 2 : args.length !== 1 && args.length !== 2) { + lowerer.noLowering( + `dnsPromises.${bi.member} with ${args.length} arguments`, + expr, + service + ? "the form is lookupService(address, port)" + : "the supported forms are lookup(hostname) and lookup(hostname, options)", + ); + } + const promise: IrExpr = { + kind: "libCall", + fn: service ? "dns.promisesLookupService" : "dns.promisesLookup", + args: [dyn(args[0]!), args.length === 2 ? dyn(args[1]!) : dynUndefinedExpr(loc)], + type: { kind: "promise", inner: DYN }, + loc, + }; + const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (declared === null || declared.kind !== "promise" || declared.inner.kind === "dyn") { + return promise; + } + return checkedDynPromise(lowerer, promise, declared, expr, loc); + } + if (["getServers", "getDefaultResultOrder", "setDefaultResultOrder"].includes(bi.member)) { + return lowerDnsModuleCall(lowerer, expr, { module: "dns", member: bi.member }, loc); + } + lowerer.noLowering( + `dnsPromises.${bi.member}`, + expr, + "lookup, lookupService, getServers, getDefaultResultOrder, and setDefaultResultOrder are the lowered dns/promises functions", + ts.isIdentifier(expr.expression) ? lowerer.resolveValueSymbol(expr.expression) : undefined, + ); +} + +/** A promise as the call site's typed promise: an interned async + * helper awaits it and checks the settled value into the declared inner + * type (a mismatch rejects with the checked-dynamic TypeError). */ +function checkedDynPromise( + lowerer: Lowerer, + promise: IrExpr, + declared: IrType & { kind: "promise" }, + node: ts.Node, + loc: SrcLoc, +): IrExpr { + const inner = declared.inner; + if ( + !canDynCheckTo( + inner, + (id) => lowerer.shapes.get(id), + (id) => lowerer.unions.get(id), + ) + ) { + lowerer.noLowering( + `a dns/promises result typed '${lowerer.fmt(declared)}'`, + node, + "the settled value must have a checked-dynamic conversion (type it as the documented LookupAddress shape or unknown)", + ); + } + const key = `dns.promise:${typeKey(declared)}`; + let helper = lowerer.valueHelpers.get(key); + if (!helper) { + helper = `%dns.promise.${lowerer.valueHelpers.size}`; + lowerer.valueHelpers.set(key, helper); + const promiseT: IrType = { kind: "promise", inner: DYN }; + lowerer.liftedFns.push({ + name: helper, + params: [{ localId: "promise", name: "promise", type: promiseT }], + returnType: inner, + locals: [ + { id: "promise", name: "promise", type: promiseT, mutable: false }, + { id: "settled", name: "settled", type: DYN, mutable: false }, + ], + body: [ + { + kind: "varDecl", + localId: "settled", + init: { + kind: "awaitExpr", + value: { kind: "varRef", localId: "promise", type: promiseT, loc }, + type: DYN, + loc, + }, + loc, + }, + { + kind: "return", + value: { + kind: "dynCheck", + value: { kind: "varRef", localId: "settled", type: DYN, loc }, + type: inner, + loc, + }, + loc, + }, + ], + loc, + async: true, + }); + } + return { kind: "call", callee: helper, args: [promise], type: declared, loc }; +} + /** dns module calls: lookup(hostname[, options], callback) with Node's * option forms, lookupService(address, port, callback), getServers() and * get/setDefaultResultOrder(). The runtime validates the arguments as @@ -290,8 +410,17 @@ function lowerDnsModuleCall( loc: SrcLoc, ): IrExpr { const args = expr.arguments.nodes; - const dyn = (node: ts.Expression): IrExpr => - lowerer.coerceToExpected(lowerer.lowerExpr(node), DYN); + const dyn = (node: ts.Expression): IrExpr => { + // `opts as dns.LookupOneOptions` over a checked-dynamic value (the + // overload-satisfying cast of a pass-through lookup): the cast changes + // no value, so the options reach the runtime as they are. + let inner = node; + while (ts.isAsExpression(inner) || ts.isParenthesizedExpression(inner)) + inner = inner.expression; + if (inner !== node && lowerer.mapTypeOf(lowerer.typeOf(inner))?.kind === "dyn") + return lowerer.lowerExpr(inner); + return lowerer.coerceToExpected(lowerer.lowerExpr(node), DYN); + }; // A callback whose second parameter is typed (string, LookupAddress[]) // cannot take the undefined Node passes after an error. const typedResult = (node: ts.Expression): IrExpr => { @@ -449,7 +578,7 @@ export function lowerDgramMethodCall( return { kind: "libCall", fn, args: [receiver, data, port, host], type: VOID, loc }; } if ( - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && args.length <= 5 && !args.some(ts.isSpreadElement) ) { diff --git a/packages/compiler/src/frontend/lowering/lower-enums.ts b/packages/compiler/src/frontend/lowering/lower-enums.ts index 159d72f32..e72dd2780 100644 --- a/packages/compiler/src/frontend/lowering/lower-enums.ts +++ b/packages/compiler/src/frontend/lowering/lower-enums.ts @@ -29,20 +29,21 @@ import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; import { locOf } from "../program.js"; -import { F64, type IrExpr, STRING } from "../../ir/ir.js"; +import { BOOL, F64, type IrExpr, STRING, UNDEFINED_T } from "../../ir/ir.js"; +import { nodeThrowExpr } from "../../ir/build.js"; /** The member's source-name text (identifiers and string-literal names — * the only name forms tsc allows on enum members). */ function memberNameText(member: ts.EnumMember): string { const n = member.name; - return ts.isIdentifier(n) || ts.isStringLiteralLike(n) ? n.text : n.getText(); + return ts.isIdentifier(n) || ts.isStringLiteralLike(n) ? n.text : ts.getTextOfNode(n); } /** Ambient: a `declare`d declaration, or one living in a .d.ts. */ function isAmbientEnumDecl(decl: ts.EnumDeclaration): boolean { return ( (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Ambient) !== 0 || - decl.getSourceFile().isDeclarationFile + ts.getSourceFileOfNode(decl).isDeclarationFile ); } @@ -125,7 +126,7 @@ function lowerEnumMemberRead( // runtime values, so SC2020-family member fences own those. A user's // `declare enum` follows the // `declare const __VERSION__` stance: ReferenceError at the access. - const sf = enumDecl.getSourceFile(); + const sf = ts.getSourceFileOfNode(enumDecl); if (lowerer.isStdlibFile(sf)) return null; const declared = lowerer.mapTypeOf(lowerer.typeOf(expr)); if (!declared || declared.kind === "void") return null; @@ -164,10 +165,10 @@ function lowerEnumReverseRead( key: ts.Expression, ): IrExpr | null { const loc = locOf(expr); - const enumName = recvSym.name; + const enumName = ts.symbolName(recvSym); // Ambient: no object exists — the member-read stance. if (decls.every((d) => isAmbientEnumDecl(d))) { - if (decls.some((d) => lowerer.isStdlibFile(d.getSourceFile()))) { + if (decls.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d)))) { return null; } return { @@ -182,6 +183,15 @@ function lowerEnumReverseRead( // existence tracking; nothing needs it — fence through the generic path. if (decls.some((d) => isAmbientEnumDecl(d))) return null; const idx = foldConstIndex(lowerer, key); + const keyType = lowerer.typeOf(key); + if ( + idx === null && + ((keyType.flags & ts.TypeFlags.StringLike) !== 0 || + (keyType.isUnionType() && + ts.constituentTypes(keyType).every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0))) + ) { + return lowerEnumKeyRead(lowerer, expr, decls, key); + } if (idx === null) { lowerer.unsupported( "SC1090", @@ -206,6 +216,109 @@ function lowerEnumReverseRead( return { kind: "strLit", value: name, type: STRING, loc }; } +/** Object.prototype members every enum object inherits: a runtime key + * naming one reads a function, which the member value types cannot hold. */ +const OBJECT_PROTOTYPE_KEYS = new Set([ + "__defineGetter__", + "__defineSetter__", + "__lookupGetter__", + "__lookupSetter__", + "__proto__", + "constructor", + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + "toLocaleString", + "toString", + "valueOf", +]); + +/** `E[key]` with a RUNTIME string key (`Color[name as keyof typeof + * Color]` — the name-to-value lookup): the key evaluates once and is + * compared against every member NAME, each hit answering that member's + * constant, exactly the forward entries tsc's emit stores. A key naming no + * member is Node's undefined, so the read carries an undefined arm. Keys + * Node would answer from elsewhere on the object throw a named runtime + * refusal instead of a wrong value: a numeric member's reverse entry (its + * value spelled as a string — Node answers the member NAME) and the + * inherited Object.prototype members. */ +function lowerEnumKeyRead( + lowerer: Lowerer, + expr: ts.ElementAccessExpression, + decls: readonly ts.EnumDeclaration[], + key: ts.Expression, +): IrExpr { + const loc = locOf(expr); + const enumName = decls[0]!.name.text; + const valueType = lowerer.mapTypeOf(lowerer.typeOf(expr)); + if (valueType === null) lowerer.badType(expr, lowerer.typeOf(expr)); + const resultType = lowerer.withUndefinedArm(valueType); + const keyValue = lowerer.coerceToExpected(lowerer.lowerExpr(key), STRING); + const keyLocal = lowerer.declareHiddenLocal("%enumKey", STRING); + const keyRef: IrExpr = { kind: "varRef", localId: keyLocal.id, type: STRING, loc }; + const matches = (text: string): IrExpr => ({ + kind: "strEq", + negated: false, + left: keyRef, + right: { kind: "strLit", value: text, type: STRING, loc }, + type: BOOL, + loc, + }); + const refusal = (what: string): IrExpr => + nodeThrowExpr( + 0, + "SC1090", + `${what} through a runtime key ('${enumName}[key]') is not supported yet`, + resultType, + loc, + ); + const arms: { cond: IrExpr; value: IrExpr }[] = []; + const names = new Set(); + const reverse = new Set(); + for (const d of decls) { + for (const member of d.members.nodes) { + const name = memberNameText(member); + const constant = lowerEnumMemberRead(lowerer, expr, member); + if (constant === null) + lowerer.unsupported("SC1090", expr, `'${enumName}[key]' over this enum`); + if (!names.has(name)) { + names.add(name); + arms.push({ cond: matches(name), value: lowerer.coerceToExpected(constant, resultType) }); + } + const value = lowerer.checker.getConstantValue(member); + if (typeof value === "number") reverse.add(String(value)); + } + } + for (const text of reverse) { + if (names.has(text)) continue; + arms.push({ cond: matches(text), value: refusal("enum reverse lookups") }); + } + for (const text of OBJECT_PROTOTYPE_KEYS) { + if (names.has(text) || reverse.has(text)) continue; + arms.push({ cond: matches(text), value: refusal("inherited enum object members") }); + } + let result: IrExpr = { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }; + result = lowerer.coerceToExpected(result, resultType); + for (let i = arms.length - 1; i >= 0; i--) { + const arm = arms[i]!; + result = { + kind: "ternary", + cond: arm.cond, + then: arm.value, + else_: result, + type: resultType, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: keyLocal.id, init: keyValue, loc }], + result, + type: resultType, + loc, + }; +} + /** A compile-time-constant NUMERIC index: a numeric literal, its negation, * or an enum member read whose constant is a number. */ function foldConstIndex(lowerer: Lowerer, e: ts.Expression): number | null { diff --git a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts index 06e9c4ab1..f8588fe48 100644 --- a/packages/compiler/src/frontend/lowering/lower-event-emitter.ts +++ b/packages/compiler/src/frontend/lowering/lower-event-emitter.ts @@ -72,6 +72,8 @@ import { STREAM_FORCED_EVENT_NAMES, streamForcedTuple, streamSidesOf } from "./l import { boolLit, strLit, varRef } from "../../ir/build.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; import { lowerAddAbortListener } from "./builtin-calls.js"; +import { requestGeneralStreamSurface } from "./general-streams.js"; +import { nodeStreamInterfaceSide } from "./dyn-streams.js"; /** True when the class descends from (or is) the runtime emitter. */ export function emitterRooted(lowerer: Lowerer, info: ClassInfo | undefined | null): boolean { @@ -412,7 +414,11 @@ function isErrorMonitorRef(lowerer: Lowerer, arg: ts.Expression): boolean { const isMonitorType = (t: ts.Type): boolean => { if (!(t.flags & ts.TypeFlags.UniqueESSymbol)) return false; const symbol = t.getSymbol(); - return symbol?.name === "errorMonitor" && lowerer.isStdlibSymbol(symbol); + return ( + symbol !== undefined && + ts.symbolName(symbol) === "errorMonitor" && + lowerer.isStdlibSymbol(symbol) + ); }; // The unique symbol TYPE names its declaration through any alias. if (isMonitorType(lowerer.typeOf(arg))) return true; @@ -1245,7 +1251,19 @@ export function lowerEmitterMethodCall( ); } const p = cbT.params[0]; - if (p !== undefined && !(p.kind === "bytes" && p.elem === "u8") && p.kind !== "string") { + // A `Buffer | string` chunk (NodeJS.ReadableStream's listener + // shape) takes whichever side the stream delivers. + const chunkUnion = + p?.kind === "union" && + (lowerer.unions.get(p.unionId)?.arms ?? []).every( + (arm) => (arm.kind === "bytes" && arm.elem === "u8") || arm.kind === "string", + ); + if ( + p !== undefined && + !(p.kind === "bytes" && p.elem === "u8") && + p.kind !== "string" && + !chunkUnion + ) { lowerer.noLowering( `a 'data' listener whose chunk parameter is '${lowerer.fmt(p)}'`, args[1]!, @@ -1683,7 +1701,7 @@ export function emitOverrideShapeReason( const p0T = lowerer.mapTypeOf(lowerer.typeOf(p0.name)); const jsUnannotated = p0.type === undefined && - isJsSourceFile(member.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(member)) && (p0T === null || p0T.kind === "dyn"); if (p0T?.kind !== "string" && !jsUnannotated) { return "the event parameter must be typed 'string' (symbol event names have no lowering)"; @@ -1717,9 +1735,9 @@ export function emitOverrideShapeReason( "the event parameter cannot be reassigned (the forward's event name must stay pinned)"; } } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - member.body.forEachChild(visit); + ts.forEachChild(member.body, visit); return reason; } @@ -2192,36 +2210,90 @@ function eventsOnceBoxer( return { kind: "closure", fnName: name, captures: [], type: fnType, loc }; } -/** events.once(emitter, name): a promise of the next `name` event's - * arguments as an array, rejected by an earlier 'error' event (unless the - * awaited event is 'error'). The emitter's listeners are typed by the - * program-wide event table, so the runtime registers one-shot typed - * listeners whose compiler-built boxers turn the event's tuple into the - * array. EventTarget and stream receivers, computed names and options are - * refused. */ +/** The `signal` of an events.once options object literal (a checked- + * dynamic AbortSignal or undefined), or null when the literal names none. + * Other options forms are refused. */ +function eventsOnceSignal(lowerer: Lowerer, node: ts.Expression): IrExpr | null { + if (!ts.isObjectLiteralExpression(node)) { + lowerer.noLowering( + "events.once options that are not an object literal", + node, + "pass the options as an object literal: { signal }", + ); + return null; + } + let signal: IrExpr | null = null; + for (const prop of node.properties.nodes) { + const name = prop.name && ts.isIdentifier(prop.name) ? prop.name.text : null; + if ( + name === "signal" && + signal === null && + (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) + ) { + signal = lowerer.lowerExprExpecting( + ts.isPropertyAssignment(prop) ? prop.initializer : (prop.name as ts.Identifier), + DYN, + ); + } else { + lowerer.noLowering( + "events.once options other than one signal", + prop, + "the lowered options object takes signal: AbortSignal", + ); + } + } + return signal; +} + +/** events.once(emitter, name[, { signal }]): a promise of the next `name` + * event's arguments as an array, rejected by an earlier 'error' event + * (unless the awaited event is 'error') or by the signal's abort with an + * AbortError. The emitter's listeners are typed by the program-wide event + * table (a stream's runtime-emitted events by their fixed tuples), so the + * runtime registers one-shot typed listeners whose compiler-built boxers + * turn the event's tuple into the array. EventTarget receivers, a stream's + * 'data' event, computed names and options other than a literal { signal } + * are refused. */ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: SrcLoc): IrExpr { const args = call.arguments.nodes; - if (args.length < 2 || args.some(ts.isSpreadElement)) { - lowerer.noLowering("events.once without an emitter and an event name", call); - } - if (args.length > 2) { - lowerer.noLowering( - "events.once with options", - args[2]!, - "the lowered form is events.once(emitter, name); AbortSignal options are not supported yet", - ); + if (args.length < 2 || args.length > 3 || args.some(ts.isSpreadElement)) { + lowerer.noLowering("events.once without an emitter, an event name and options", call); } // Node's Promise: the arguments array of checked-dynamic values. const arrayT = arrayOf(DYN); const resultT: IrType = { kind: "promise", inner: arrayT }; - const receiver = lowerer.lowerExpr(args[0]!); + let receiver = lowerer.lowerExpr(args[0]!); + // A checked-dynamic NodeJS stream slot (dyn-streams.ts): a native stream + // unboxes by identity; any other value throws the not-supported Error. + if ( + receiver.type.kind === "dyn" && + lowerer.dynNodeStreams && + nodeStreamInterfaceSide(lowerer, lowerer.typeOf(args[0]!)) !== null + ) { + receiver = { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + receiver, + strLit( + "events.once over stream objects other than native streams is not supported yet", + loc, + ), + ], + type: { kind: "object", className: "%Duplex" }, + loc, + }; + } const info = receiver.type.kind === "object" ? lowerer.classes.get(receiver.type.className) : undefined; - if (!emitterRooted(lowerer, info) || streamSidesOf(lowerer, info) !== null) { + if (!emitterRooted(lowerer, info)) { + // A NodeJS stream interface slot that holds a stdio handle here: the + // program lowers again with the runtime stream classes. + requestGeneralStreamSurface(lowerer, args[0]!); lowerer.noLowering( "events.once over this value", args[0]!, - "the emitter must be an EventEmitter instance; EventTarget and stream receivers are not supported yet", + "the emitter must be an EventEmitter or stream instance; EventTarget receivers are not supported yet", ); } const name = literalEventName(lowerer, args[1]!); @@ -2233,6 +2305,14 @@ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: ); } const event = name ?? ""; + const sides = streamSidesOf(lowerer, info); + if ((sides === "r" || sides === "rw") && (event === "data" || event === "keypress")) { + lowerer.noLowering( + `events.once of a stream's '${event}' event`, + args[1]!, + `the chunk has no checked-dynamic boxing here; register stream.once('${event}', listener) instead`, + ); + } const table = emitterEvents(lowerer); if (event !== "error" && flexibleJsEvent(event, table.get(event))) { lowerer.noLowering( @@ -2240,14 +2320,22 @@ export function lowerEventsOnce(lowerer: Lowerer, call: ts.CallExpression, loc: args[1]!, ); } - const tuple = tupleOf(lowerer, table, event, call); + const tuple = streamForcedTuple(lowerer, info, event) ?? tupleOf(lowerer, table, event, call); const errorTuple: IrType[] = [{ kind: "object", className: "%Error" }]; const boxer = eventsOnceBoxer(lowerer, call, tuple, arrayT, loc); const errorBoxer = eventsOnceBoxer(lowerer, call, errorTuple, arrayT, loc); + const signal = args.length > 2 ? eventsOnceSignal(lowerer, args[2]!) : null; + const common = [ + receiver, + eventNameExpr(event, loc), + boxer, + errorBoxer, + boolLit(event !== "error", loc), + ]; return { kind: "libCall", - fn: "events.once", - args: [receiver, eventNameExpr(event, loc), boxer, errorBoxer, boolLit(event !== "error", loc)], + fn: signal === null ? "events.once" : "events.onceSignal", + args: signal === null ? common : [...common, signal], type: resultT, loc, }; diff --git a/packages/compiler/src/frontend/lowering/lower-expando.ts b/packages/compiler/src/frontend/lowering/lower-expando.ts index 5ed97d4d7..6adce8f67 100644 --- a/packages/compiler/src/frontend/lowering/lower-expando.ts +++ b/packages/compiler/src/frontend/lowering/lower-expando.ts @@ -98,8 +98,8 @@ function expandoFnSymbolOf(lowerer: Lowerer, recv: ts.Expression): ts.Symbol | n const sym = lowerer.resolveValueSymbol(recv); if (!sym) return null; const decl = sym.valueDeclaration; - if (!decl || decl.getSourceFile().isDeclarationFile) return null; - if (isJsSourceFile(decl.getSourceFile())) return null; + if (!decl || ts.getSourceFileOfNode(decl).isDeclarationFile) return null; + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; if (ts.isFunctionDeclaration(decl)) { return ts.isSourceFile(decl.parent) ? sym : null; } @@ -177,7 +177,7 @@ export function collectExpandoMembers(lowerer: Lowerer, sf: ts.SourceFile): void } const existing = memberOf(members, w.key); if (existing) { - existing.firstWriteStart = Math.min(existing.firstWriteStart, node.getStart()); + existing.firstWriteStart = Math.min(existing.firstWriteStart, ts.tokenStartOfNode(node)); } else if ( !(typeof w.key === "string" && READONLY_FN_MEMBERS.has(w.key)) && !nsOwnedMember(lowerer, w.access) @@ -198,18 +198,22 @@ export function collectExpandoMembers(lowerer: Lowerer, sf: ts.SourceFile): void } if (type?.kind === "void" && isUnitOnlyTsType(tsType)) type = unitOnlyUnion(lowerer.unions); if (type && type.kind !== "void") { - const fnName = w.fnSym.name; + const fnName = ts.symbolName(w.fnSym); const memberName = typeof w.key === "string" ? w.key - : `sym%${w.key.name}%${w.key.declarations[0]?.getStart() ?? 0}`; + : `sym%${ts.symbolName(w.key)}%${(w.key.declarations.length === 0 ? undefined : ts.tokenStartOfNode(w.key.declarations[0]!)) ?? 0}`; const g: IrGlobal = { id: `%g.${tag}${fnName}%.${memberName}`, name: `${fnName}.${memberName}`, type, mutable: true, }; - const entry: ExpandoMember = { global: g, firstWriteStart: node.getStart(), file: sf }; + const entry: ExpandoMember = { + global: g, + firstWriteStart: ts.tokenStartOfNode(node), + file: sf, + }; if (typeof w.key === "string") members.names.set(w.key, entry); else members.symbols.set(w.key, entry); lowerer.globalsList.push(g); @@ -248,7 +252,7 @@ function nsOwnedMember( if (ts.isModuleDeclaration(p)) { return !( p.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.DeclareKeyword) || - p.getSourceFile().isDeclarationFile + ts.getSourceFileOfNode(p).isDeclarationFile ); } } @@ -308,11 +312,11 @@ export function expandoMemberRead( const member = memberOf(lowerer.expandoMembers.get(fnSym), key); if (!member) return null; if ( - access.getSourceFile() === member.file && + ts.getSourceFileOfNode(access) === member.file && initExecutingPosition(access) && - access.getStart() < member.firstWriteStart + ts.tokenStartOfNode(access) < member.firstWriteStart ) { - const name = typeof key === "string" ? key : key.name; + const name = typeof key === "string" ? key : ts.symbolName(key); lowerer.unsupported( "SC1090", access, diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index ac51cfbe6..7789f8a86 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -1,4 +1,4 @@ -import { refuseWebAssemblyUse } from "./builtins/webassembly.js"; +import { lowerWebAssemblyValue, refuseWebAssemblyUse } from "./builtins/webassembly.js"; import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../ir/build.js"; import { InternalCompilerError } from "../../errors.js"; /* Expression lowering: the expression dispatch (lowerExpr), literals @@ -8,6 +8,7 @@ import { InternalCompilerError } from "../../errors.js"; * ToBoolean/ToString coercion helpers, and field/element reads and writes * (FieldTarget). */ import * as ts from "../ts7/adapter.js"; +import { checkedPrivateReceiver, localPrivateBrandClass } from "./private-brands.js"; import { constituentTypes } from "../ts7/checker.js"; import type { Lowerer } from "./lowerer.js"; import { lowerMaybePromiseAwait, maybePromiseUnionOf } from "./maybe-promise.js"; @@ -37,7 +38,14 @@ import { typeEquals, typeKey, } from "../../ir/ir.js"; -import { isJsSourceFile, locOf } from "../program.js"; +import { + cjsExportAssignmentOf, + cjsExportDiscardReason, + cjsExportTargetLiteral, + isJsSourceFile, + locOf, +} from "../program.js"; +import { npmStaticLexerVisibleNames } from "../npm-static.js"; import { type CompoundOp, NARROW_FIRST, @@ -83,6 +91,8 @@ import { lowerElementCompoundParts } from "./expressions/element-access.js"; export { DEFERRED_READ_MESSAGE, abstractPropertyDeclOf, + builtinFunctionValue, + builtinGlobalValue, fieldGetExpr, fieldTarget, isMatchSliceType, @@ -91,6 +101,7 @@ export { lowerIntrinsicProperty, lowerUnionProperty, matchResultNamedGroupsOf, + primitiveConstructorOr, } from "./expressions/property-access.js"; export { lowerAsExpression, @@ -140,6 +151,21 @@ export type FieldTarget = // (the write-slot type; reads arm it with undefined under // noUncheckedIndexedAccess in fieldGetExpr). | { container: "recordOvf"; obj: IrExpr; shapeId: string; field: string; fieldType: IrType } + // A computed key of a record in a read-modify-write (`r[k] += v`, + // `r[k]++`, `r[k] ??= v`; keyed-field-targets.ts): `keys` lists the + // declared fields a literal-union key can name, which share fieldType; + // null keys mean a string key into a pure index-signature record, whose + // entries fieldType types. `key` is null when the one literal is static. + | { + container: "recordKeyed"; + obj: IrExpr; + shapeId: string; + field: string; + keys: readonly string[] | null; + key: IrExpr | null; + fieldType: IrType; + missNaN?: true; + } | { container: "accessor"; obj: IrExpr; className: string; field: string; fieldType: IrType } // A RECORD accessor property (an object-literal `get x()`/`set x(v)` — // the shape carries %get:/%set: closure slots): reads call the getter @@ -167,9 +193,9 @@ export function templateRawTextOf( ): string { const own = (node as { rawText?: string }).rawText; if (own !== undefined) return own; - const sf = node.getSourceFile(); - const start = node.getStart(sf); - const end = node.getEnd(); + const sf = ts.getSourceFileOfNode(node); + const start = ts.tokenStartOfNode(node); + const end = node.end; const tailTrim = node.kind === ts.SyntaxKind.TemplateHead || node.kind === ts.SyntaxKind.TemplateMiddle ? 2 : 1; return sf.text.slice(start + 1, end - tailTrim); @@ -252,7 +278,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { if (externalTypeSpecifier !== null) { lowerer.externalHostFence(externalTypeSpecifier, expr); } - // Every reached use of the WebAssembly API fences at its expression. + // WebAssembly API values lower; every other reached use of the API + // fences at its expression. + const webAssemblyValue = lowerWebAssemblyValue(lowerer, expr); + if (webAssemblyValue !== null) return webAssemblyValue; refuseWebAssemblyUse(lowerer, expr); if (ts.isNumericLiteral(expr)) { @@ -267,14 +296,14 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // integer within ±(2^53−1)) and non-integer spellings carry // nothing, so the IR is unchanged for programs that held their // numbers. - const spelled = expr.getText().replace(/_/g, ""); + const spelled = ts.getTextOfNode(expr).replace(/_/g, ""); if (/^\d+$/.test(spelled) && String(Number(spelled)) !== spelled) { return { kind: "numLit", value: Number(spelled), spelling: spelled, type: F64, loc }; } return { kind: "numLit", value, type: F64, loc }; } if (ts.isBigIntLiteral(expr)) { - const spelling = expr.getText().replace(/_/g, "").replace(/n$/i, ""); + const spelling = ts.getTextOfNode(expr).replace(/_/g, "").replace(/n$/i, ""); return { kind: "libCall", fn: "bigint.parse", @@ -455,9 +484,23 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // Only arrows can see an enclosing method's `this` (JS lexical-this); // `this` in plain nested functions is already a tsc error under // noImplicitThis, so the generic scope walk here is preflight-safe. - const local = lowerer.resolveThis(); + const local = lowerer.resolveThis(expr); if (local) { const value: IrExpr = { kind: "varRef", localId: local.id, type: local.type, loc }; + // `this.#x` in a function-local class's prototype method, whose `this` + // is the call-site value: the field lives on the native instance of + // this evaluation (checkedPrivateReceiver). + const access = expr.parent; + if ( + local.type.kind === "dyn" && + ts.isPropertyAccessExpression(access) && + access.expression === expr && + ts.isPrivateIdentifier(access.name) + ) { + const owner = localPrivateBrandClass(lowerer, access.name); + if (owner) + return checkedPrivateReceiver(lowerer, owner.info, owner.decl, access, value, loc); + } const initialized = lowerer.ctx.superInitialized; if (initialized) return { @@ -506,7 +549,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr { // the read answers the strict-mode plain-call undefined, the old // constant. TypeScript keeps the fence (noImplicitThis makes it a // compile-time story there). - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { // Module scope of a CommonJS file: `this` is module.exports. const exportsThis = lowerCjsTopLevelThis(lowerer, expr); if (exportsThis) return exportsThis; @@ -1014,6 +1057,15 @@ export function nullishBranches( export function lowerCondition(lowerer: Lowerer, expr: ts.Expression): IrExpr { let e: ts.Expression = expr; while (ts.isParenthesizedExpression(e)) e = e.expression; + // `if (Object.setPrototypeOf)`: the feature test of transpiled Error + // subclasses' prototype reset. Node always has it. + if ( + ts.isPropertyAccessExpression(e) && + !e.questionDotToken && + e.name.text === "setPrototypeOf" && + lowerer.isStdlibGlobal(e.expression, "Object") + ) + return { kind: "boolLit", value: true, type: BOOL, loc: locOf(e) }; if (ts.isPropertyAccessExpression(e)) { const presence = lowerPromiseThenPresence(lowerer, e, { kind: "boolLit", @@ -1496,7 +1548,7 @@ export function lowerAbsenceProbe(lowerer: Lowerer, node: ts.Expression): IrExpr // statement ruled non-null without narrowing it (jose's secs()). if ( receiverT?.kind === "union" && - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && !expr.questionDotToken && !hasOptionalChainGuard(expr.expression) ) { @@ -1686,7 +1738,8 @@ export function stringWrapperToString(lowerer: Lowerer, node: ts.Expression): Ir while (ts.isParenthesizedExpression(e)) e = e.expression; if (!ts.isNewExpression(e) || !ts.isIdentifier(e.expression)) return null; const symbol = lowerer.resolveValueSymbol(e.expression); - if (symbol?.name !== "String" || !lowerer.isStdlibSymbol(symbol)) return null; + if (symbol === null || ts.symbolName(symbol) !== "String" || !lowerer.isStdlibSymbol(symbol)) + return null; const args = e.arguments?.nodes ?? []; if (args.length > 1 || args.some(ts.isSpreadElement)) return null; if (args.length === 0) return { kind: "strLit", value: "", type: STRING, loc: locOf(e) }; @@ -1975,7 +2028,7 @@ export function aliasTypeofNarrows( const leaf = aliasTypeofArmOf(lowerer, e, strip); if (leaf) out.push(leaf); }; - if (implicitMonoFile(cond.getSourceFile())) walk(cond, polarity); + if (implicitMonoFile(ts.getSourceFileOfNode(cond))) walk(cond, polarity); return out; } @@ -2042,6 +2095,102 @@ function aliasTypeofArmOf( return null; } +/** The names Node's ES-module loader copies off a CommonJS module that a + * bundler rewrite spells as accessors (npmStaticLexerVisibleNames), with + * their getters in that order; empty for every other module. */ +function lexerVisibleAccessors(dep: ts.SourceFile): ts.GetAccessorDeclaration[] { + const names = npmStaticLexerVisibleNames(dep.fileName); + if (names.length === 0) return []; + let table: ts.ObjectLiteralExpression | null = null; + for (const stmt of dep.statements.nodes) { + const cjs = cjsExportAssignmentOf(stmt); + if (cjs?.kind === "table" && cjsExportDiscardReason(stmt) === null) { + table = cjsExportTargetLiteral(cjs.expr.right, dep); + } + } + if (table === null) return []; + const getters = new Map(); + for (const p of table.properties.nodes) { + if (ts.isGetAccessorDeclaration(p) && (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name))) + getters.set(p.name.text, p); + } + return names.flatMap((n) => { + const g = getters.get(n); + return g === undefined ? [] : [g]; + }); +} + +/** The module global holding the value an accessor export had when an + * ES-module importer first linked its CommonJS module. */ +export function cjsAccessorSnapshotOf( + lowerer: Lowerer, + getter: ts.GetAccessorDeclaration, +): { id: string; type: IrType } | null { + const hit = lowerer.cjsAccessorSnapshots.get(getter); + if (hit !== undefined) return hit; + if (!lexerVisibleAccessors(ts.getSourceFileOfNode(getter)).includes(getter)) return null; + const call = cjsAccessorCall(lowerer, getter, locOf(getter)); + if (call === null) return null; + const snapshot = { id: `%g.cjsSnapshot.${lowerer.cjsAccessorSnapshots.size}`, type: call.type }; + lowerer.globalsList.push({ + id: snapshot.id, + name: "%cjsSnapshot", + type: call.type, + mutable: true, + }); + lowerer.cjsAccessorSnapshots.set(getter, snapshot); + return snapshot; +} + +/** Node's ES-module loader reads every lexer-visible export of a CommonJS + * module once, when an importer first links it, and the import bindings + * keep those values. For the accessors a bundler rewrite introduced, the + * importer's link point runs each getter once, in the lexer's order, and + * stores the values its bindings read. Empty when the module has none. */ +export function cjsAccessorSnapshotStores( + lowerer: Lowerer, + dep: ts.SourceFile, + loc: SrcLoc, +): IrStmt[] { + const getters = lexerVisibleAccessors(dep); + if (getters.length === 0) return []; + let readyId = lowerer.cjsSnapshotReady.get(dep); + if (readyId === undefined) { + readyId = `%g.cjsSnapshotReady.${lowerer.cjsSnapshotReady.size}`; + lowerer.globalsList.push({ id: readyId, name: "%cjsSnapshotReady", type: BOOL, mutable: true }); + lowerer.cjsSnapshotReady.set(dep, readyId); + } + const stores: IrStmt[] = [ + { + kind: "assign", + localId: readyId, + value: { kind: "boolLit", value: true, type: BOOL, loc }, + loc, + }, + ]; + for (const getter of getters) { + const snapshot = cjsAccessorSnapshotOf(lowerer, getter); + const call = cjsAccessorCall(lowerer, getter, loc); + if (snapshot === null || call === null) return []; + stores.push({ kind: "assign", localId: snapshot.id, value: call, loc }); + } + return [ + { + kind: "if", + cond: { + kind: "unary", + op: "!", + operand: { kind: "varRef", localId: readyId, type: BOOL, loc }, + type: BOOL, + loc, + }, + then: stores, + else_: null, + loc, + }, + ]; +} + /** The interned lifted-getter call both accessor-read paths share. */ export function cjsAccessorCall( lowerer: Lowerer, @@ -2118,7 +2267,25 @@ export function caughtRead( // Unnarrowed reads cross into unknown. The caught-value dispatch pass // retains known native class payloads through live identity capsules; // other payload kinds use the runtime's ordinary conversion. - if (narrowed?.kind === "dyn") { + // `catch (e: any)` (or useUnknownInCatchVariables off) reads the same way. + if ( + narrowed?.kind === "dyn" || + (narrowed === null && (lowerer.typeOf(node).flags & ts.TypeFlags.Any) !== 0) + ) { + return { kind: "caughtToDyn", value: ref, type: DYN, loc }; + } + + // A binding a user type predicate (or an assertion) narrowed to a + // structural type (`isErrorLike(e)` → `{ message: string; … }`, an + // intersection like `TypeError & { code: unknown }`): the thrown value is + // still whatever was thrown, so it crosses as a checked-dynamic value and + // each member read validates its own result, as JavaScript reads it. + const checked = lowerer.typeOf(node); + if ( + narrowed?.kind === "record" || + narrowed?.kind === "union" || + (narrowed === null && (checked.flags & (ts.TypeFlags.Object | ts.TypeFlags.Intersection)) !== 0) + ) { return { kind: "caughtToDyn", value: ref, type: DYN, loc }; } lowerer.unsupported("SC1063", node); @@ -2410,7 +2577,7 @@ export function lowerOptionalArrayIndexRead( key: IrExpr, ): IrExpr | null { if ( - !isJsSourceFile(expr.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(expr)) || expr.questionDotToken || hasOptionalChainGuard(expr.expression) ) @@ -2573,6 +2740,24 @@ export function symbolFieldTarget( }; } +/** `return Promise.reject(reason)` in an async function: the checker's + * context is the awaited return type (or its union with PromiseLike), not + * a promise, but the async function adopts the returned promise, so the + * promise of its own result type is the honest type. Null elsewhere. */ +function asyncReturnPromise(lowerer: Lowerer, call: ts.CallExpression): IrType | null { + if (lowerer.ctx.isAsync !== true || lowerer.ctx.generator) return null; + let node: ts.Node = call; + while (ts.isParenthesizedExpression(node.parent)) node = node.parent; + const parent = node.parent; + const returned = + (ts.isReturnStatement(parent) && parent.expression === node) || + (ts.isArrowFunction(parent) && parent.body === node); + if (!returned) return null; + const ret = lowerer.ctx.returnType; + if (ret.kind === "void" || ret.kind === "promise") return null; + return { kind: "promise", inner: ret }; +} + /** `Promise.reject(reason)` on THE Promise global: a pre-rejected * promise. The reason is pinned to the Error hierarchy (the executor * reject parameter's contract — rejection payloads share the @@ -2604,7 +2789,9 @@ export function lowerPromiseRejectCall( // A maybe-promise context (`return Promise.reject(e)` from a function // returning `T | Promise`) names its one promise arm. const maybePromise = contextual ? maybePromiseUnionOf(lowerer, contextual) : null; - const type = maybePromise ? maybePromise.promise : contextual; + const type = maybePromise + ? maybePromise.promise + : (asyncReturnPromise(lowerer, call) ?? contextual); const reasonNode = call.arguments.nodes[0]!; // A CHECKED-DYNAMIC reason (`Promise.reject(value)` where value rode // an untyped binding — the tracingChannel suite's shape): the checked-dynamic tree diff --git a/packages/compiler/src/frontend/lowering/lower-functions.ts b/packages/compiler/src/frontend/lowering/lower-functions.ts index 0ef601672..bec8528c4 100644 --- a/packages/compiler/src/frontend/lowering/lower-functions.ts +++ b/packages/compiler/src/frontend/lowering/lower-functions.ts @@ -1,3 +1,4 @@ +import { declareTypedArguments } from "./typed-arguments.js"; import { varRef } from "../../ir/build.js"; import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; @@ -129,7 +130,7 @@ function declareFunctionArguments( lowerer.ctx.argumentsLocal = local; } if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !ts.isArrowFunction(node) && shapes.length === 1 && shapes[0]?.mode === "dynRest" && @@ -203,7 +204,7 @@ export function lowerLambda( // Fluent methods must return that receiver without a class cast. if ( ts.isMethodDeclaration(node) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && !node.asteriskToken && !hasExplicitJsDocReturn(node) && @@ -214,6 +215,19 @@ export function lowerLambda( ret: funcType.ret.kind === "promise" ? { ...funcType.ret, inner: DYN } : DYN, }; } + // An ASYNC lambda whose signature answered a bare checked-dynamic result + // (`retry(async () => loose())` against a library slot typed + // `(...) => A | Promise` with A = any — the union collapses to `any`): + // calling it still produces a promise, so the value's result is + // Promise, the payload the dyn the body returns. + if ( + funcType.ret.kind === "dyn" && + !ts.isAccessor(node) && + node.asteriskToken === undefined && + node.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true + ) { + funcType = { ...funcType, ret: { kind: "promise", inner: DYN } }; + } // A lambda IS a value: the completed-ABI rule applies at birth. The // contextual (target) type decides — `(x?: number) => void` may flow // into a slot annotated `(x: number | undefined) => void` (same ABI @@ -222,7 +236,7 @@ export function lowerLambda( if ( !( ts.isMethodDeclaration(node) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && canBoxFuncIntoDyn( funcType, (id) => lowerer.shapes.get(id), @@ -273,14 +287,14 @@ export function lowerLambda( if (lowerer.methodLambdasInProgress.has(node)) lowerer.methodLambdasInProgress.set(node, { fnName, type: funcType, selfReads: 0 }); const inferredSignature = - isJsSourceFile(node.getSourceFile()) && !node.type + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type ? lowerer.checker.getSignatureFromDeclaration(node) : undefined; const returnsConstructor = inferredSignature && producesConstructor(lowerer, lowerer.checker.getReturnTypeOfSignature(inferredSignature)); if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !node.type && !isAsync && !isGenerator && @@ -302,13 +316,15 @@ export function lowerLambda( try { const receiver = (checkedReceiver || - isJsSourceFile(node.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(node)) || node.parameters.nodes.some(isThisParameter)) && !ts.isArrowFunction(node) ? declareCallReceiver(lowerer, node, isGenerator) : null; const { params, prologue } = lowerer.declareParams(node.parameters.nodes, shapes); if (receiver) prologue.unshift(receiver); + if (ts.isFunctionExpression(node) && lowerer.typedArgumentsLambdas.has(node)) + declareTypedArguments(lowerer, node, shapes, params, prologue); declareFunctionArguments( lowerer, node, @@ -445,7 +461,7 @@ export function lowerLambda( // position. ICEs (SC9001) stay compile errors, exactly like // lowerStmts; probe mode (diagSink) keeps the poison. if (!(e instanceof PoisonError)) throw e; - if (!isJsSourceFile(node.getSourceFile())) throw e; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) throw e; const params: IrParam[] = funcType.params.map((t, i) => ({ localId: `%pf${i}`, name: `%pf${i}`, @@ -494,7 +510,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I lowerer.fnStack.push(ctx); try { const receiver = - decl.parameters.nodes.some(isThisParameter) || isJsSourceFile(decl.getSourceFile()) + decl.parameters.nodes.some(isThisParameter) || isJsSourceFile(ts.getSourceFileOfNode(decl)) ? declareCallReceiver(lowerer, decl, sig.generator !== undefined) : null; const { params, prologue } = lowerer.declareParams(decl.parameters.nodes, sig.params); @@ -547,7 +563,7 @@ export function lowerFunction(lowerer: Lowerer, decl: ts.FunctionDeclaration): I // at the declaration's position — so a reachable-but-broken // signature stops the RUN at its own site instead of the build. // ICEs (SC9001) stay compile errors, exactly like lowerStmts. - if (isJsSourceFile(decl.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) { // An ABI type naming a class that never REGISTERED (the sentence- // walker idiom's path type — the #private fence) is fine to emit: // callers CAN lower calls to this symbol (a same-typed param diff --git a/packages/compiler/src/frontend/lowering/lower-generators.ts b/packages/compiler/src/frontend/lowering/lower-generators.ts index b1d0a1aa5..19a75a0b4 100644 --- a/packages/compiler/src/frontend/lowering/lower-generators.ts +++ b/packages/compiler/src/frontend/lowering/lower-generators.ts @@ -207,7 +207,7 @@ function isGeneratorMember(lowerer: Lowerer, access: ts.PropertyAccessExpression (d) => ts.isInterfaceDeclaration(d.parent) && (d.parent.name.text === "Generator" || d.parent.name.text === "AsyncGenerator") && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); } @@ -272,7 +272,7 @@ export function isSettledIteratorValueRead( ts.isInterfaceDeclaration(d.parent) && (d.parent.name.text === "IteratorYieldResult" || d.parent.name.text === "IteratorReturnResult") && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return false; @@ -282,13 +282,13 @@ export function isSettledIteratorValueRead( sym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)) && - lowerer.isStdlibFile(d.getSourceFile()), + lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ); const alias = receiver.getAliasSymbol(); const symbol = receiver.getSymbol(); if ( - !(alias?.name === "IteratorResult" && stdlib(alias)) && - !(symbol?.name === "IteratorReturnResult" && stdlib(symbol)) + !(alias !== undefined && ts.symbolName(alias) === "IteratorResult" && stdlib(alias)) && + !(symbol !== undefined && ts.symbolName(symbol) === "IteratorReturnResult" && stdlib(symbol)) ) return false; const checked = lowerer.mapTypeOf(lowerer.typeOf(node)); diff --git a/packages/compiler/src/frontend/lowering/lower-global-value.ts b/packages/compiler/src/frontend/lowering/lower-global-value.ts index a5f516e86..4b8e7b10a 100644 --- a/packages/compiler/src/frontend/lowering/lower-global-value.ts +++ b/packages/compiler/src/frontend/lowering/lower-global-value.ts @@ -3,15 +3,19 @@ import { DYN, STRING, arrayOf, type IrExpr } from "../../ir/ir.js"; import { strLit } from "../../ir/build.js"; import { locOf } from "../program.js"; import type { Lowerer } from "./lowerer.js"; +import { TEMPORAL_GLOBAL } from "../builtin-modules.js"; /** A stored global object has identity and survives module/function boundaries. * Known globals without a value implementation refuse at runtime; absent host * capabilities answer undefined. The checker supplies names, never values. */ export function lowerGlobalValue(lowerer: Lowerer, expr: ts.Expression): IrExpr { const loc = locOf(expr); + // The temporal global is typed for the programs that name it (TS 7's + // lib slice) but absent in Node 24: reads answer undefined. const names = lowerer.checker .getPropertiesOfType(lowerer.typeOf(expr)) - .map((property) => property.name); + .map((property) => ts.symbolName(property)) + .filter((name) => name !== TEMPORAL_GLOBAL); return { kind: "libCall", fn: "global.native", diff --git a/packages/compiler/src/frontend/lowering/lower-inspect.ts b/packages/compiler/src/frontend/lowering/lower-inspect.ts index 245d1b676..a8657f978 100644 --- a/packages/compiler/src/frontend/lowering/lower-inspect.ts +++ b/packages/compiler/src/frontend/lowering/lower-inspect.ts @@ -1,4 +1,5 @@ import { dynUndefinedExpr, boolLit, helperLoc, numLit, strLit, varRef } from "../../ir/build.js"; +import { builtinClassDynValueOf, builtinClassKindOf } from "./builtins/builtin-class-dyn.js"; import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; @@ -21,6 +22,7 @@ import { type SrcLoc, canConvertToDyn, canDynCheckTo, + CLASS_FIELD_ATTRIBUTES_PREFIX, classDynViewSupported, DYN_CLASS_PROPERTIES, isClassOwnEnumerableFieldName, @@ -491,7 +493,7 @@ function classInspectsThroughView(lowerer: Lowerer, info: ClassInfo | undefined) if (info.subclasses.length > 0) return true; return ( info.decl != null && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && classDynViewSupported( info.def.fields, (id) => lowerer.shapes.get(id), @@ -500,12 +502,16 @@ function classInspectsThroughView(lowerer: Lowerer, info: ClassInfo | undefined) ); } -/** Whether a class holds properties added outside its declared layout, which - * only its checked-dynamic view can list. Decided after lowering, when the +/** Whether a class holds properties added outside its declared layout, or + * fields Object.defineProperty can make non-enumerable, which only its + * checked-dynamic view lists as Node does. Decided after lowering, when the * class has its final fields. */ function classInspectsAddedProperties(lowerer: Lowerer, info: ClassInfo): boolean { return ( - info.def.fields.some((field) => field.name === DYN_CLASS_PROPERTIES) && + info.def.fields.some( + (field) => + field.name === DYN_CLASS_PROPERTIES || field.name.startsWith(CLASS_FIELD_ATTRIBUTES_PREFIX), + ) && classDynViewSupported( info.def.fields, (id) => lowerer.shapes.get(id), @@ -2011,6 +2017,47 @@ function formatOArg(lowerer: Lowerer, node: ts.Expression, depth: number, loc: S return inspectExpr(lowerer, value.type, value, numLit(0, loc), numLit(depth, loc), loc); } +/** %d and %i over a checked-dynamic argument, as Node's formatWithOptions + * reads it: a bigint prints as `${n}n`, a symbol as NaN, and any other + * value as `number` (Number(arg) for %d, parseInt(String(arg)) for %i) + * formatted. The argument evaluates once. */ +function formatDynInteger( + lowerer: Lowerer, + value: IrExpr, + number: (held: IrExpr) => IrExpr, + loc: SrcLoc, +): IrExpr { + const held = lowerer.declareHiddenLocal("%formatArg", DYN); + const ref = (): IrExpr => varRef(held.id, DYN, loc); + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: held.id, init: value, loc }], + result: { + kind: "ternary", + cond: { kind: "dynTest", test: "bigint", value: ref(), type: BOOL, loc }, + then: { + kind: "strConcat", + left: { kind: "libCall", fn: "dyn.toStringCoerce", args: [ref()], type: STRING, loc }, + right: strLit("n", loc), + type: STRING, + loc, + }, + else_: { + kind: "ternary", + cond: { kind: "dynTest", test: "symbol", value: ref(), type: BOOL, loc }, + then: strLit("NaN", loc), + else_: { kind: "libCall", fn: "insp.f64", args: [number(ref())], type: STRING, loc }, + type: STRING, + loc, + }, + type: STRING, + loc, + }, + type: STRING, + loc, + }; +} + /** %d and %i over the argument types Node formats without a number * conversion: a bigint prints as `${n}n` (formatBigInt) and a symbol as NaN; * null is Number(null) (0) under %d and parseInt("null") (NaN) under %i, and @@ -2141,19 +2188,20 @@ export function lowerFormatCall( }; } // A checked-dynamic argument (`console.log('... actual %d.', - // context.actual)` — test/common's exit report): VALIDATE it as a - // number (dynCheck) and format. A non-number dyn value throws the - // catchable TypeError where Node would print its ToNumber (NaN - // for objects) — it throws instead of returning a wrong result - // (SEMANTICS.md). + // context.actual)` — test/common's exit report): Number(arg). if (value.type.kind === "dyn") { - return { - kind: "libCall", - fn: "insp.f64", - args: [{ kind: "dynCheck", value, type: F64, loc }], - type: STRING, + return formatDynInteger( + lowerer, + value, + (held) => ({ + kind: "libCall", + fn: "dyn.numberConstructor", + args: [held], + type: F64, + loc, + }), loc, - }; + ); } lowerer.noLowering( `util.format %d of '${lowerer.fmt(value.type)}' values`, @@ -2185,6 +2233,24 @@ export function lowerFormatCall( }; return { kind: "libCall", fn: "insp.f64", args: [parsed], type: STRING, loc }; } + // parseInt(String(arg)) over a checked-dynamic argument. + if (value.type.kind === "dyn") { + return formatDynInteger( + lowerer, + value, + (held) => ({ + kind: "libCall", + fn: "num.parseInt", + args: [ + { kind: "libCall", fn: "dyn.toStringCoerce", args: [held], type: STRING, loc }, + numLit(0, loc), + ], + type: F64, + loc, + }), + loc, + ); + } lowerer.noLowering(`util.format %i of '${lowerer.fmt(value.type)}' values`, node); break; } @@ -2544,8 +2610,15 @@ function lowerUtilCallableCall( const arity = member === "deprecate" ? 4 : member === "callbackify" ? 1 : 2; // Program classes keep static prototypes: there is no function object // whose prototype chain the runtime could relink or wrap. + // EventEmitter and the stream classes are the exception as superCtor: + // the runtime has untyped constructors for them (builtin-class-dyn.ts). const classArgs = member === "inherits" ? 2 : member === "deprecate" ? 1 : 0; - for (const arg of expr.arguments.nodes.slice(0, classArgs)) { + const builtinBase = + member === "inherits" && expr.arguments.nodes[1] !== undefined + ? builtinClassKindOf(lowerer, expr.arguments.nodes[1]) + : null; + for (const [index, arg] of expr.arguments.nodes.slice(0, classArgs).entries()) { + if (index === 1 && builtinBase !== null) continue; if (lowerer.mapTypeOf(lowerer.typeOf(arg))?.kind === "classval") lowerer.noLowering( `util.${member} over a class constructor`, @@ -2564,9 +2637,12 @@ function lowerUtilCallableCall( const stmts: IrStmt[] = []; const args: IrExpr[] = []; for (let i = 0; i < arity; i++) { - const input: IrExpr = expr.arguments.nodes[i] - ? lowerer.lowerExprExpecting(expr.arguments.nodes[i]!, DYN) - : dynUndefinedExpr(loc); + const node = expr.arguments.nodes[i]; + const input: IrExpr = !node + ? dynUndefinedExpr(loc) + : i === 1 && builtinBase !== null + ? (builtinClassDynValueOf(lowerer, node) ?? lowerer.lowerExprExpecting(node, DYN)) + : lowerer.lowerExprExpecting(node, DYN); const saved = lowerer.declareHiddenLocal("%utilInput", DYN); stmts.push({ kind: "varDecl", localId: saved.id, init: input, loc }); args.push(varRef(saved.id, DYN, loc)); @@ -2584,7 +2660,7 @@ function lowerUtilCallableCall( * a statically typed callback parameter cannot receive a value its type * excludes. Untyped (JavaScript or unknown) slots keep the wrapper dyn. */ function callbackifyErrorExcludesNull(lowerer: Lowerer, expr: ts.CallExpression): boolean { - if (isJsSourceFile(expr.getSourceFile())) return false; + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) return false; const checker = lowerer.checker; const slot = checker.getContextualType(expr) ?? lowerer.typeOf(expr); const wrapper = checker.getCallSignatures(slot)[0]; @@ -2690,7 +2766,7 @@ function lowerGetCallSitesCall( loc: SrcLoc, ): IrExpr | null { if (expr.arguments.nodes.length !== 0) return null; - if (!isJsSourceFile(expr.getSourceFile())) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null; const frame = (): IrExpr => { const field = (name: string, value: IrExpr): { key: IrExpr; value: IrExpr } => ({ key: { kind: "strLit", value: name, type: STRING, loc }, @@ -2702,7 +2778,7 @@ function lowerGetCallSitesCall( field("functionName", { kind: "strLit", value: "", type: STRING, loc }), field("scriptName", { kind: "strLit", - value: expr.getSourceFile().fileName, + value: ts.getSourceFileOfNode(expr).fileName, type: STRING, loc, }), diff --git a/packages/compiler/src/frontend/lowering/lower-mixins.ts b/packages/compiler/src/frontend/lowering/lower-mixins.ts index 90789b4a0..766059dfb 100644 --- a/packages/compiler/src/frontend/lowering/lower-mixins.ts +++ b/packages/compiler/src/frontend/lowering/lower-mixins.ts @@ -174,7 +174,7 @@ function mixinFnShapeInner( if (n === t.expression) return undefined; if ( ts.isIdentifier(n) && - n.text === p.name.getText() && + n.text === ts.getTextOfNode(p.name) && lowerer.checker.getSymbolAtLocation(n) === paramSym ) { extraRef = true; @@ -451,8 +451,8 @@ function instantiateMixinCall( const base = mixinBaseClassOf(lowerer, call.arguments.nodes[0]!); const instName = lowerer.qualify( - call.getSourceFile(), - `%mx${call.getStart()}.${shape.classNode.name?.text ?? ""}`, + ts.getSourceFileOfNode(call), + `%mx${ts.tokenStartOfNode(call)}.${shape.classNode.name?.text ?? ""}`, ); const ordinal = lowerer.mixinOrdinals.get(shape.classNode) ?? 0; lowerer.mixinOrdinals.set(shape.classNode, ordinal + 1); @@ -513,7 +513,10 @@ function instantiateMixinCall( ); } if (info.mixinInstance) { - info.mixinInstance.statics = { sf: call.getSourceFile(), pos: holder.getStart() }; + info.mixinInstance.statics = { + sf: ts.getSourceFileOfNode(call), + pos: ts.tokenStartOfNode(holder), + }; } } return info; diff --git a/packages/compiler/src/frontend/lowering/lower-mock.ts b/packages/compiler/src/frontend/lowering/lower-mock.ts index 4f446bb89..fcc68c787 100644 --- a/packages/compiler/src/frontend/lowering/lower-mock.ts +++ b/packages/compiler/src/frontend/lowering/lower-mock.ts @@ -154,7 +154,12 @@ function mockWrapperFactory( const { fn, call, calls, once, onceEntry, result, args } = mockContextFields(lowerer, ctxType)!; const impl = `${name}.impl`; const ctx = () => varRef("ctx.0", ctxType, loc); + // A checked-dynamic rest function (mock.fn()'s default type) takes its + // arguments as one trailing dyn array, recorded as the call's arguments + // and spread into the implementation. + const dynRest = fn.rest === true && fn.restAbi === undefined; const params = fn.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type })); + if (dynRest) params.push({ localId: "rest.0", name: "args", type: DYN }); const paramRefs = () => params.map((p) => varRef(p.localId, p.type, loc)); const argumentsValue = (): IrExpr => fn.rest @@ -219,13 +224,23 @@ function mockWrapperFactory( loc, }; }; - const invoke: IrExpr = { - kind: "callValue", - callee: varRef("f.0", fn, loc), - args: paramRefs(), - type: fn.ret, - loc, - }; + const invoke: IrExpr = dynRest + ? { + kind: "dynCall", + callee: { kind: "dynFrom", value: varRef("f.0", fn, loc), type: DYN, loc }, + calleeName: "implementation", + args: paramRefs(), + spreads: [{ arg: 0, what: "args" }], + type: DYN, + loc, + } + : { + kind: "callValue", + callee: varRef("f.0", fn, loc), + args: paramRefs(), + type: fn.ret, + loc, + }; const tryBody: IrStmt[] = voidReturn ? [ { kind: "exprStmt", expr: invoke, loc }, @@ -629,7 +644,37 @@ function requireUndefinedResultArm( } } -/** mock.fn(original, implementation?) and t.mock.fn(...). */ +/** `%mock.noop.N`: mock.fn()'s original, Node's `function () {}` — it + * returns undefined. Null when the function type's return type cannot hold + * undefined. */ +function mockNoopOriginal( + lowerer: Lowerer, + ctxType: IrType, + fn: FuncType, + loc: SrcLoc, +): IrExpr | null { + const voidReturn = fn.ret.kind === "void" || fn.ret.kind === "undefinedT"; + const undefinedValue = voidReturn ? null : lowerer.wrappedUndefined(fn.ret, loc); + if (!voidReturn && !undefinedValue) return null; + const name = liftOnce(lowerer, ctxType, "noop", (name) => { + const params = fn.params.map((type, i) => ({ localId: `p${i}.0`, name: `p${i}`, type })); + if (fn.rest === true && fn.restAbi === undefined) + params.push({ localId: "rest.0", name: "args", type: DYN }); + lowerer.liftedFns.push({ + name, + params, + returnType: fn.ret, + locals: params.map((p) => local(p.localId, p.name, p.type)), + body: [{ kind: "return", value: undefinedValue, loc }], + loc, + }); + }); + return { kind: "closure", fnName: name, captures: [], type: fn, loc }; +} + +/** mock.fn(original?, implementation?) and t.mock.fn(...). Without an + * original, the mock's original and implementation are a function that + * returns undefined, as in Node. */ export function lowerMockFn( lowerer: Lowerer, call: ts.CallExpression, @@ -658,11 +703,19 @@ export function lowerMockFn( const { fn } = fields; requireUndefinedResultArm(lowerer, fields, call, what); const args = call.arguments.nodes; - if (args.length === 0 || args.length > 2 || args.some(ts.isSpreadElement)) { + if (args.length > 2 || args.some(ts.isSpreadElement)) { lowerer.noLowering( `${what} with ${args.length} arguments`, call, - "the supported forms are mock.fn(original) and mock.fn(original, implementation); mock.fn() and mock options have no lowering", + "the supported forms are mock.fn(), mock.fn(original) and mock.fn(original, implementation); mock options have no lowering", + ); + } + const noop = args.length === 0 ? mockNoopOriginal(lowerer, ctxType!, fn, loc) : null; + if (args.length === 0 && !noop) { + lowerer.noLowering( + `${what}() of a function returning '${lowerer.fmt(fn.ret)}'`, + call, + "mock.fn() without an original returns undefined: pass an original, or use a function type whose return type includes undefined", ); } const stmts: IrStmt[] = []; @@ -670,7 +723,7 @@ export function lowerMockFn( stmts.push({ kind: "varDecl", localId: originalLocal.id, - init: lowerer.lowerExprExpecting(args[0]!, fn), + init: noop ?? lowerer.lowerExprExpecting(args[0]!, fn), loc, }); const implementation = @@ -744,7 +797,7 @@ export function lowerMockMethod( const objShape = objType?.kind === "record" ? lowerer.shapes.get(objType.shapeId) : undefined; const fn = objShape?.fields.find((f) => f.name === field)?.type; if (!objType || !objShape || fn?.kind !== "func") { - const typeName = lowerer.typeOf(args[0]!).getSymbol()?.name; + const typeName = ts.symbolNameOf(lowerer.typeOf(args[0]!).getSymbol()); lowerer.noLowering( typeName && !typeName.startsWith("__") ? `${what} on '${typeName}'` diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index fa4c98372..3a786aa4e 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -145,7 +145,7 @@ import { forkTargetPaths } from "../fork-target.js"; import { workerTargetPaths } from "../worker-target.js"; import { isNativeProxyInitializer } from "./expressions/native-proxy.js"; import { handleEventReaderPrelude } from "./lower-event-target.js"; -import { jsHoldsDynObject } from "./lower-exprs.js"; +import { cjsAccessorSnapshotStores, jsHoldsDynObject } from "./lower-exprs.js"; import { keptIdentityAssertion } from "./assertion-identity.js"; /** One file's declarations, split for collection and init-body lowering. */ @@ -692,7 +692,10 @@ export function collectJsonImports(lowerer: Lowerer, parts: FileParts[]): void { const nameSym = lowerer.checker.getSymbolAtLocation(clause.name); if (!nameSym || !(nameSym.flags & ts.SymbolFlags.Alias)) continue; const target = lowerer.checker.getAliasedSymbol(nameSym); - const jsonSf = target.declarations[0]?.getSourceFile(); + const jsonSf = + target.declarations.length === 0 + ? undefined + : ts.getSourceFileOfNode(target.declarations[0]!); if (!jsonSf || !jsonSf.fileName.endsWith(".json")) continue; try { const tsType = lowerer.typeOf(clause.name); @@ -872,6 +875,14 @@ export function moduleArtifacts( valueInstanceOfRoots.add(root.def.name); } break; + case "libCall": + // The general stream representation's child stdio read bridges + // through the runtime install that boxes both stream classes. + if (node.fn === "child.stdioStream") { + visitClass("%Readable"); + visitClass("%Writable"); + } + break; case "dynFrom": if (boxedChild(node.value.type)) { // A generic child can expose native stdio without importing stream. @@ -1167,7 +1178,7 @@ function jsDynHoldableInitializer(lowerer: Lowerer, init: ts.Expression | undefi if ( (ts.isFunctionExpression(e) || ts.isArrowFunction(e)) && !e.typeParameters?.nodes.length && - isJsSourceFile(e.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(e)) ) return true; if (ts.isIdentifier(e) || ts.isArrowFunction(e) || ts.isFunctionExpression(e)) { @@ -1218,6 +1229,36 @@ function jsDynHoldableInitializer(lowerer: Lowerer, init: ts.Expression | undefi return false; } +/** A JavaScript default-export object literal holding a class the program + * could not lower (`export default { parse, TomlDate }` where TomlDate + * extends an unsupported base): the literal narrows that member away, its + * diagnostics deferred to the reads that reach it, so the binding's type + * drops it too. Otherwise the module's own initialization would fail the + * width check against members no build can supply. */ +function withoutRejectedClassMembers( + lowerer: Lowerer, + expression: ts.Expression, + type: IrType, +): IrType { + let literal = expression; + while (ts.isParenthesizedExpression(literal)) literal = literal.expression; + if (!ts.isObjectLiteralExpression(literal) || type.kind !== "record") return type; + const shape = lowerer.shapes.get(type.shapeId); + if (!shape || shape.tuple) return type; + const rejected = (t: IrType): boolean => + t.kind === "classval" && + !lowerer.classes.has(t.className) && + lowerer.deferredClassByName.has(t.className); + if (!shape.fields.some((f) => rejected(f.type))) return type; + const kept = shape.fields.filter((f) => !rejected(f.type)); + const shapeId = lowerer.shapes.intern( + kept.map((f) => ({ name: f.name, type: f.type })), + false, + shape.indexValue, + ); + return { kind: "record", shapeId }; +} + export function collectGlobals( lowerer: Lowerer, sf: ts.SourceFile, @@ -1266,7 +1307,7 @@ export function collectGlobals( const mutableVar = vd !== undefined && ts.isVariableDeclaration(vd) && - !vd.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(vd).isDeclarationFile && (ts.getCombinedNodeFlags(vd) & ts.NodeFlags.Const) === 0; // Immutable targets: alias plumbing — importers resolve to the // target's storage (the alias-resolved read IS the snapshot when @@ -1320,6 +1361,7 @@ export function collectGlobals( type = unitOnlyUnion(lowerer.unions); } if (type.kind === "void") lowerer.badType(stmt.expression, tsType); + if (isJsSourceFile(sf)) type = withoutRejectedClassMembers(lowerer, stmt.expression, type); const g: IrGlobal = { id: `%g.${tag}default`, name: "default", type, mutable: false }; lowerer.globalsBySymbol.set(symbol, g); lowerer.globalsList.push(g); @@ -1538,7 +1580,7 @@ export function collectGlobals( const mutableVar = vd !== undefined && ts.isVariableDeclaration(vd) && - !vd.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(vd).isDeclarationFile && (ts.getCombinedNodeFlags(vd) & ts.NodeFlags.Const) === 0; if (!mutableVar) continue; const diagsBefore = lowerer.diags.length; @@ -1549,7 +1591,7 @@ export function collectGlobals( } if (type.kind === "void") lowerer.badType(stmt.name, lowerer.typeOf(stmt.name)); const g: IrGlobal = { - id: `%g.${tag}${nsPathPrefix(stmt)}%alias.${stmt.name.text}%${stmt.getStart()}`, + id: `%g.${tag}${nsPathPrefix(stmt)}%alias.${stmt.name.text}%${ts.tokenStartOfNode(stmt)}`, name: stmt.name.text, type, mutable: false, @@ -2274,12 +2316,12 @@ export function collectGlobals( : undefined; const callDeclaration = callSignature && callSignature.declaration; const callType = - callDeclaration && isJsSourceFile(callDeclaration.getSourceFile()) + callDeclaration && isJsSourceFile(ts.getSourceFileOfNode(callDeclaration)) ? lowerer.mapTypeOf(lowerer.typeOf(nameNode)) : null; const inferredJsCall = callDeclaration && - isJsSourceFile(callDeclaration.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callDeclaration)) && (!callType || (unannotatedCall && callType.kind === "union" && @@ -2702,7 +2744,7 @@ export function declSymbolOf( ts.canHaveModifiers(decl) && ts.getModifiers(decl)?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) === true; if (!isDefault) return undefined; - const sym = defaultExportSymbolOf(lowerer, decl.getSourceFile()); + const sym = defaultExportSymbolOf(lowerer, ts.getSourceFileOfNode(decl)); return sym ?? undefined; } @@ -2720,7 +2762,7 @@ export function declSymbolOf( * expression so the expression's OWN diagnostic wins when it has one, * then poisons. */ export function lowerDefaultExport(lowerer: Lowerer, stmt: ts.ExportAssignment): IrStmt | null { - const symbol = defaultExportSymbolOf(lowerer, stmt.getSourceFile()); + const symbol = defaultExportSymbolOf(lowerer, ts.getSourceFileOfNode(stmt)); if (symbol && symbol.flags & ts.SymbolFlags.Alias && !lowerer.globalsBySymbol.has(symbol)) return null; const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; @@ -2733,7 +2775,7 @@ export function lowerDefaultExport(lowerer: Lowerer, stmt: ts.ExportAssignment): while (ts.isParenthesizedExpression(inner)) inner = inner.expression; if ( ts.isClassExpression(inner) && - !isJsSourceFile(stmt.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(stmt)) && inner.typeParameters === undefined && (decoratorNodesOf(inner).length > 0 || inner.members.nodes.some((m) => decoratorNodesOf(m).length > 0)) && @@ -2851,6 +2893,14 @@ export function lowerFileInit( }); } else { header.push({ kind: "exprStmt", expr: call, loc }); + // An ES-module importer links a CommonJS dependency: Node + // copies its lexer-visible exports here. + if ( + (ts.isImportDeclaration(stmt) || ts.isExportDeclaration(stmt)) && + !isCjsJsFile(sf, lowerer.program) && + isCjsJsFile(dep, lowerer.program) + ) + header.push(...cjsAccessorSnapshotStores(lowerer, dep, loc)); } } } @@ -2945,7 +2995,7 @@ export function lowerFileInit( c.decl && ts.isClassDeclaration(c.decl) && !c.mixinInstance && - c.decl.getSourceFile() === sf && + ts.getSourceFileOfNode(c.decl) === sf && (lowerer.classInitFlags.has(c.def.name) || c.callableBase !== undefined || c.def.baseValueGlobal !== undefined || @@ -2954,7 +3004,7 @@ export function lowerFileInit( (c.staticBlocks?.length ?? 0) > 0 || c.classDecorators !== undefined), ) - .map((c) => ({ pos: c.decl!.getStart(), info: c })); + .map((c) => ({ pos: ts.tokenStartOfNode(c.decl!), info: c })); // Statics-bearing MIXIN instantiations whose call evaluates in THIS // file: their declaration-time code runs when the call does — the // top-level statement lower-mixins recorded. Insertion order is @@ -2974,7 +3024,7 @@ export function lowerFileInit( // anything the statement's lowering emits. Hoisted class // declarations never share a kept statement's start, so `<=` // changes nothing for them. - while (at < statics.length && statics[at]!.pos <= stmt.getStart()) { + while (at < statics.length && statics[at]!.pos <= ts.tokenStartOfNode(stmt)) { body.push( ...withClassInitFlag( lowerer, diff --git a/packages/compiler/src/frontend/lowering/lower-namespaces.ts b/packages/compiler/src/frontend/lowering/lower-namespaces.ts index 23b7d0f57..9997eb7d9 100644 --- a/packages/compiler/src/frontend/lowering/lower-namespaces.ts +++ b/packages/compiler/src/frontend/lowering/lower-namespaces.ts @@ -42,6 +42,7 @@ import { boundIdentifiersOf } from "./lowerer.js"; import type { FileParts } from "./lower-modules.js"; import { locOf, resolveImport } from "../program.js"; import { F64, type IrExpr, type IrStmt, type IrType, STRING } from "../../ir/ir.js"; +import { TEMPORAL_GLOBAL } from "../builtin-modules.js"; /** True when this module declaration produces NO runtime construct at all: * ambient (`declare namespace/module`, `declare global`, string-named @@ -177,10 +178,10 @@ export function nsPathPrefix(node: ts.Node, modifierDecl?: ts.Node): string { const nested = p.parent !== undefined && ts.isModuleBlock(p.parent); const nonExported = nested && (ts.getCombinedModifierFlags(p) & ts.ModifierFlags.Export) === 0; - segs.unshift(p.name.text + (nonExported ? `%${p.getStart()}` : "")); + segs.unshift(p.name.text + (nonExported ? `%${ts.tokenStartOfNode(p)}` : "")); } } - return `${segs.join("%")}${exported ? "" : `%${parent.getStart()}`}.`; + return `${segs.join("%")}${exported ? "" : `%${ts.tokenStartOfNode(parent)}`}.`; } /** The registered namespace-block kind a symbol's declaration sits in @@ -274,7 +275,11 @@ function moduleNsSourceFileOf(lowerer: Lowerer, e: ts.Expression): ts.SourceFile stmt.moduleSpecifier !== undefined && ts.isStringLiteral(stmt.moduleSpecifier) ) { - const dep = resolveImport(lowerer.program, d.getSourceFile(), stmt.moduleSpecifier.text); + const dep = resolveImport( + lowerer.program, + ts.getSourceFileOfNode(d), + stmt.moduleSpecifier.text, + ); if (dep && !dep.isDeclarationFile && lowerer.fileTag.has(dep)) return dep; } } @@ -333,7 +338,7 @@ export function ambientNsRootOf(lowerer: Lowerer, e: ts.Expression): ts.Identifi return null; } if (ts.isModuleDeclaration(d) && !ts.isIdentifier(d.name)) return null; - if (d.getSourceFile().isDeclarationFile) return null; // lib/@types keep their own fences + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; // lib/@types keep their own fences if ( ts.isModuleDeclaration(d) ? !isAmbientModuleDecl(d) @@ -425,13 +430,13 @@ export function ambientUndefVarRootOf(lowerer: Lowerer, e: ts.Expression): ts.Id const sym = lowerer.resolveValueSymbol(root); if (!sym) return null; if (lowerer.trapBindings.has(sym)) return root; - if (lowerer.isStdlibSymbol(sym)) return null; + if (lowerer.isStdlibSymbol(sym)) return isTemporalGlobalRoot(lowerer, root, sym) ? root : null; if (ambientUndefinedFnSymbolOf(lowerer, root) !== null) return root; const decls = sym.declarations; if (decls.length === 0) return null; for (const d of decls) { if (!ts.isVariableDeclaration(d) || d.initializer !== undefined) return null; - if (d.getSourceFile().isDeclarationFile) return null; // lib/@types keep their own fences + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; // lib/@types keep their own fences if (!(ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient)) return null; } return root; @@ -440,6 +445,24 @@ export function ambientUndefVarRootOf(lowerer: Lowerer, e: ts.Expression): ts.Id } } +/** True when `ident` reads the ESNext temporal global, which the program + * type-checks against TS 7's lib slice (program.ts adds it only to programs + * that name it) but Node 24 does not define: Node's read throws the + * catchable ReferenceError " is not defined", so every chain rooted + * here lowers to that throw (typeof answers "undefined" in lower-exprs). */ +export function isTemporalGlobalRoot( + lowerer: Lowerer, + ident: ts.Identifier, + sym: ts.Symbol | undefined | null, +): boolean { + return ( + ident.text === TEMPORAL_GLOBAL && + sym !== undefined && + sym !== null && + lowerer.isStdlibSymbol(sym) + ); +} + /** The declaration-classification form of ambientUndefVarRootOf: the root * that makes `decl` a TRAP BINDING — its initializer provably throws the * root's ReferenceError, so the binding never holds a value and needs no @@ -472,7 +495,7 @@ export function trapDeclRootOf( if (root === null) return null; for (const nameNode of boundIdentifiersOf(decl.name)) { const sym = lowerer.checker.getSymbolAtLocation(nameNode); - if (sym && bindingEverWritten(lowerer, sym, decl.getSourceFile())) { + if (sym && bindingEverWritten(lowerer, sym, ts.getSourceFileOfNode(decl))) { const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(sym)); if (mapped !== null && mapped.kind !== "void") return null; } @@ -497,7 +520,7 @@ export function trapDeclRootOf( * initializer into an ordinary lowering that fences). ambientUndefVarRootOf * carries its own collect-guard, so the extra question stays a probe. */ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): boolean { - const symText = sym.name; + const symText = ts.symbolName(sym); const namesSym = (e: ts.Node): boolean => ts.isIdentifier(e) && e.text === symText && lowerer.checker.getSymbolAtLocation(e) === sym; const patternWrites = (target: ts.Node): boolean => { @@ -506,9 +529,9 @@ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile) const walk = (m: ts.Node): void => { if (hit) return; if (namesSym(m)) hit = true; - else m.forEachChild(walk); + else ts.forEachChild(m, walk); }; - target.forEachChild(walk); + ts.forEachChild(target, walk); return hit; }; let written = false; @@ -553,9 +576,9 @@ function bindingEverWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile) written = true; } } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; - sf.forEachChild(visit); + ts.forEachChild(sf, visit); return written; } @@ -616,7 +639,7 @@ export function ambientUndefinedFnSymbolOf( } else if (!ts.isModuleDeclaration(d) && !ts.isClassDeclaration(d)) { return null; } - if (d.getSourceFile().isDeclarationFile) return null; + if (ts.getSourceFileOfNode(d).isDeclarationFile) return null; if (!(ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient)) return null; } return sawFn ? sym : null; @@ -657,7 +680,7 @@ function initExecutingPosition(node: ts.Node): boolean { function earliestSameFileDeclStart(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): number { let earliest = Infinity; for (const d of sym.declarations) { - if (d.getSourceFile() === sf) earliest = Math.min(earliest, d.getStart()); + if (ts.getSourceFileOfNode(d) === sf) earliest = Math.min(earliest, ts.tokenStartOfNode(d)); } return earliest; } @@ -672,10 +695,10 @@ export function fenceEarlyAliasUse(lowerer: Lowerer, ident: ts.Identifier, refNo const aliasDecl = sym.declarations.find(ts.isImportEqualsDeclaration); if (!aliasDecl || ts.isExternalModuleReference(aliasDecl.moduleReference)) return; if (!initExecutingPosition(refNode)) return; - const sf = refNode.getSourceFile(); - if (aliasDecl.getSourceFile() !== sf) return; // cross-file: module init order applies - const aliasStart = aliasDecl.getStart(); - if (refNode.getStart() < aliasStart) { + const sf = ts.getSourceFileOfNode(refNode); + if (ts.getSourceFileOfNode(aliasDecl) !== sf) return; // cross-file: module init order applies + const aliasStart = ts.tokenStartOfNode(aliasDecl); + if (ts.tokenStartOfNode(refNode) < aliasStart) { lowerer.unsupported( "SC1090", refNode, @@ -711,8 +734,8 @@ export function fenceEarlyNsMemberRef( memberSym: ts.Symbol, ): void { if (!initExecutingPosition(access)) return; - const sf = access.getSourceFile(); - const refStart = access.getStart(); + const sf = ts.getSourceFileOfNode(access); + const refStart = ts.tokenStartOfNode(access); const memberStart = earliestSameFileDeclStart(lowerer, memberSym, sf); if (memberStart !== Infinity && refStart < memberStart) { lowerer.unsupported( diff --git a/packages/compiler/src/frontend/lowering/lower-node-module.ts b/packages/compiler/src/frontend/lowering/lower-node-module.ts index fee25b10f..37675777c 100644 --- a/packages/compiler/src/frontend/lowering/lower-node-module.ts +++ b/packages/compiler/src/frontend/lowering/lower-node-module.ts @@ -45,7 +45,7 @@ function cjsGlobal(lowerer: Lowerer, node: ts.Expression, name: "module" | "requ const decls = symbol ? symbol.declarations : []; const sourceShadow = decls.some( (decl) => - !decl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(decl).isDeclarationFile && (ts.isVariableDeclaration(decl) || ts.isParameter(decl) || ts.isBindingElement(decl) || @@ -55,7 +55,7 @@ function cjsGlobal(lowerer: Lowerer, node: ts.Expression, name: "module" | "requ ts.isNamespaceImport(decl) || ts.isImportEqualsDeclaration(decl)), ); - return !sourceShadow && !isNodeEsmFile(node.getSourceFile(), lowerer.program); + return !sourceShadow && !isNodeEsmFile(ts.getSourceFileOfNode(node), lowerer.program); } function graphSyntax(lowerer: Lowerer, sf: ts.SourceFile): boolean { @@ -174,7 +174,7 @@ export function cjsModuleRegistryPrelude(lowerer: Lowerer, loc: IrExpr["loc"]): } export function cjsModuleRef(lowerer: Lowerer, node: ts.Node): IrExpr | null { - const id = lowerer.cjsModuleIdOf.get(node.getSourceFile()); + const id = lowerer.cjsModuleIdOf.get(ts.getSourceFileOfNode(node)); return id === undefined ? null : numLit(id, locOf(node)); } @@ -203,8 +203,8 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { const symbol = type.getSymbol(); if ( symbol && - (symbol.name === "Module" || symbol.name === "ScriptcModule") && - symbol.declarations.some((decl) => lowerer.isStdlibFile(decl.getSourceFile())) + (ts.symbolName(symbol) === "Module" || ts.symbolName(symbol) === "ScriptcModule") && + symbol.declarations.some((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) ) { return true; } @@ -217,7 +217,7 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { (ts.isInterfaceDeclaration(parent) || ts.isClassDeclaration(parent)) && parent.name && (parent.name.text === "ScriptcModule" || parent.name.text === "Module") && - lowerer.isStdlibFile(parent.getSourceFile()) + lowerer.isStdlibFile(ts.getSourceFileOfNode(parent)) ) { return true; } @@ -237,7 +237,7 @@ function nodeModuleType(lowerer: Lowerer, node: ts.Expression): boolean { * file assigns an object or function. */ function moduleExportsAlwaysTruthy(lowerer: Lowerer, expr: ts.PropertyAccessExpression): boolean { if (!truthinessOnly(expr)) return false; - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); if (isNodeEsmFile(sf, lowerer.program)) return false; let truthy = true; ts.walkPreorder(sf, (node) => { @@ -307,7 +307,7 @@ export function isCjsTopLevelThis(lowerer: Lowerer, node: ts.Node): boolean { ) return false; } - return isCjsJsFile(node.getSourceFile(), lowerer.program); + return isCjsJsFile(ts.getSourceFileOfNode(node), lowerer.program); } export function usesCjsTopLevelThis(lowerer: Lowerer, sf: ts.SourceFile): boolean { @@ -341,7 +341,7 @@ export function usesCjsTopLevelThis(lowerer: Lowerer, sf: ts.SourceFile): boolea * behave as in Node. */ export function lowerCjsTopLevelThis(lowerer: Lowerer, expr: ts.Expression): IrExpr | null { if (!isCjsTopLevelThis(lowerer, expr)) return null; - const global = lowerer.cjsExportsThis.get(expr.getSourceFile()); + const global = lowerer.cjsExportsThis.get(ts.getSourceFileOfNode(expr)); if (!global) return null; let receiver: ts.Node = expr; while (ts.isParenthesizedExpression(receiver.parent)) receiver = receiver.parent; @@ -433,7 +433,7 @@ export function lowerNodeModuleIdentifier(lowerer: Lowerer, expr: ts.Identifier) if (!isCjsModuleGlobal(lowerer, expr)) return null; const ref = cjsModuleRef(lowerer, expr); if (ref) return ref; - if (isCjsJsFile(expr.getSourceFile(), lowerer.program)) { + if (isCjsJsFile(ts.getSourceFileOfNode(expr), lowerer.program)) { lowerer.unsupported( "SC1090", expr, diff --git a/packages/compiler/src/frontend/lowering/lower-server.ts b/packages/compiler/src/frontend/lowering/lower-server.ts index 0e1438fc9..afae7dd7b 100644 --- a/packages/compiler/src/frontend/lowering/lower-server.ts +++ b/packages/compiler/src/frontend/lowering/lower-server.ts @@ -53,7 +53,7 @@ import { sideEffectFreeOptionValue, TLS_SERVER_DOCUMENTED_OPTIONS, } from "./surfaces.js"; -import { conditionalSpreadOf } from "./expressions/object-literals.js"; +import { conditionalSpreadOf, lowerDynObjectLiteral } from "./expressions/object-literals.js"; import { resultIsDiscarded } from "./call-position.js"; import { lowerCallbackArg as lowerCallbackArgShared } from "./callback-arg.js"; import { pairsSnapshotHelper } from "./pairs-snapshot.js"; @@ -719,7 +719,7 @@ export function lowerNetModuleCall( // ERR_INVALID_ARG_TYPE on 'options' before any server exists. DYN // result — the checked-dynamic lane's concise arrows box it, and // the throw means it never materializes. - if (isJsSourceFile(expr.getSourceFile())) { + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { const t = lowerer.mapTypeOf(lowerer.typeOf(args[0]!)); if (t !== null && (t.kind === "string" || t.kind === "f64" || t.kind === "bool")) { const raw = lowerer.lowerExpr(args[0]!); @@ -770,7 +770,7 @@ export function lowerNetModuleCall( // invalid-input probes build theirs with computed keys): the // checked-dynamic walk validates Node-order and the compiler-rendered // fence is the post-validation tail. - if (args.length === 1 && isJsSourceFile(expr.getSourceFile())) { + if (args.length === 1 && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const t = lowerer.mapTypeOf(lowerer.typeOf(args[0]!)); if (t !== null && (t.kind === "dyn" || t.kind === "record")) { const raw = lowerer.lowerExpr(args[0]!); @@ -885,7 +885,7 @@ function lowerNetConnectOptions( ): IrExpr { const args = expr.arguments.nodes; const optsNode = args[0] as ts.ObjectLiteralExpression; - const isJs = isJsSourceFile(expr.getSourceFile()); + const isJs = isJsSourceFile(ts.getSourceFileOfNode(expr)); // The checked-dynamic option-bag route (JS sources): a literal with // computed keys (the invalid-input probes' spelling) or an objectMode- // trio member rides WHOLE to the runtime walk — Node's Socket-ctor @@ -1524,7 +1524,7 @@ function lowerNetServerMethodCall( } else if ( key === "signal" && ts.isPropertyAssignment(prop) && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { // A provably-non-AbortSignal signal (the invalid-input probes: // strings, numbers, plain records) throws Node's @@ -1627,6 +1627,29 @@ function lowerNetServerMethodCall( : [receiver, port, host, v6only, reusePort, cbV], ); } + // listen(path[, cb]): a string first argument names a Unix socket + // (the runtime takes a numeric string as a port, Node's isPipeName). + if (lowerer.mapTypeOf(lowerer.typeOf(args[0]!))?.kind === "string") { + if (args.length > 2) { + lowerer.noLowering( + `listen(path) with ${args.length} arguments`, + call, + "the supported form is listen(path[, callback])", + ); + } + const path = lowerer.lowerExprExpecting(args[0]!, STRING); + if (args.length === 1) return listenResult("net.listenPath", [receiver, path]); + const { cb } = lowerCallbackArg( + lowerer, + args[1]!, + "listen callbacks", + 0, + () => false, + "use ()", + [], + ); + return listenResult("net.listenPathCb", [receiver, path, cb]); + } const port = lowerer.lowerExprExpecting(args[0]!, F64); // The optional middle host — listen(port, '127.0.0.1'[, cb]): a // string-typed second argument is the bind address (the net.connect @@ -1682,6 +1705,21 @@ function lowerNetServerMethodCall( if (args.length === 0) { return { kind: "libCall", fn: "net.serverClose", args: [receiver], type: VOID, loc }; } + if ( + lowerer.checker + .getCallSignatures(lowerer.typeOf(args[0]!)) + .some((sig) => sig.getParameters().length > 0) + ) { + // close((err) => ...): undefined once the server closes, or Node's + // ERR_SERVER_NOT_RUNNING when it was not listening. + return { + kind: "libCall", + fn: "net.serverCloseDyn", + args: [receiver, lowerSocketDynListener(lowerer, args[0]!, "close callbacks")], + type: VOID, + loc, + }; + } const { cb } = lowerCallbackArg( lowerer, args[0]!, @@ -2055,6 +2093,32 @@ function lowerNetServerMethodCall( const result = lowerer.mapTypeOf(lowerer.typeOf(call)); if (result && isAddressInfoRecord(lowerer, result)) { const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T); + if (unixListenReachable(lowerer)) { + // A Unix-socket server's address() is its path, which this record + // cannot hold: the check throws for one before the record reads. + const local = lowerer.declareHiddenLocal("%addressServer", NETSERVER_T); + const server = (): IrExpr => varRef(local.id, NETSERVER_T, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: local.id, init: receiver, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "net.serverAddressCheck", + args: [server()], + type: VOID, + loc, + }, + loc, + }, + ], + result: { kind: "libCall", fn: "net.serverAddress", args: [server()], type: result, loc }, + type: result, + loc, + }; + } return { kind: "libCall", fn: "net.serverAddress", args: [receiver], type: result, loc }; } // @types/node's `string | AddressInfo | null`: the record while the @@ -2074,20 +2138,50 @@ function lowerNetServerMethodCall( const receiver = coerceToHandle(lowerer, access.expression, NETSERVER_T); const local = lowerer.declareHiddenLocal("%addressServer", NETSERVER_T); const server = varRef(local.id, NETSERVER_T, loc); + const recordArm: IrExpr = { + kind: "unionWrap", + unionId, + tag: lowerer.armTag(unionId, record), + value: { kind: "libCall", fn: "net.serverAddress", args: [server], type: record, loc }, + type: result, + loc, + }; + const stringTag = lowerer.armTag(unionId, STRING); + let listeningArm: IrExpr = recordArm; + const stmts: IrStmt[] = [{ kind: "varDecl", localId: local.id, init: receiver, loc }]; + if (stringTag >= 0 && unixListenReachable(lowerer)) { + // Node answers a Unix-socket server's address() with its path. + const pathLocal = lowerer.declareHiddenLocal("%addressPath", STRING); + const path = (): IrExpr => varRef(pathLocal.id, STRING, loc); + stmts.push({ + kind: "varDecl", + localId: pathLocal.id, + init: { kind: "libCall", fn: "net.serverUnixPath", args: [server], type: STRING, loc }, + loc, + }); + listeningArm = { + kind: "ternary", + cond: { + kind: "strEq", + negated: true, + left: path(), + right: strLit("", loc), + type: BOOL, + loc, + }, + then: { kind: "unionWrap", unionId, tag: stringTag, value: path(), type: result, loc }, + else_: recordArm, + type: result, + loc, + }; + } return { kind: "seqExpr", - stmts: [{ kind: "varDecl", localId: local.id, init: receiver, loc }], + stmts, result: { kind: "ternary", cond: { kind: "libCall", fn: "net.serverListening", args: [server], type: BOOL, loc }, - then: { - kind: "unionWrap", - unionId, - tag: lowerer.armTag(unionId, record), - value: { kind: "libCall", fn: "net.serverAddress", args: [server], type: record, loc }, - type: result, - loc, - }, + then: listeningArm, else_: { kind: "unionWrap", unionId, @@ -2143,6 +2237,34 @@ function socketEncodingReachable(lowerer: Lowerer): boolean { return reachable; } +/** Whether some server in the program may listen on a Unix socket: a + * `.listen(x, ...)` call on a server (or an untyped value) whose first + * argument is string-typed. Only then does address() check for the socket + * path, so other programs keep the record-only lowering. */ +function unixListenReachable(lowerer: Lowerer): boolean { + if (lowerer.unixListenReachable !== null) return lowerer.unixListenReachable; + let reachable = false; + for (const file of lowerer.program.getSourceFiles()) { + if (file.isDeclarationFile || !file.text.includes("listen")) continue; + ts.walkPreorder(file, (node) => { + if (reachable) return "skip"; + if (!ts.isCallExpression(node) || node.arguments.nodes.length === 0) return undefined; + const callee = node.expression; + if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== "listen") return undefined; + const kind = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(callee.expression))?.kind; + if (kind !== undefined && kind !== "netServer" && kind !== "dyn") return undefined; + const arg = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(node.arguments.nodes[0]!)); + // Only a string-typed argument lowers to listen(path) (an untyped + // one checks into a port). + if (arg?.kind === "string") reachable = true; + return undefined; + }); + if (reachable) break; + } + lowerer.unixListenReachable = reachable; + return reachable; +} + /** An inline 'data' listener whose chunk parameter is typed Buffer, for a * socket that may have an encoding set: the chunk binds as a checked * dynamic value, so the body sees what Node passes (a string after @@ -2212,7 +2334,7 @@ function lowerNetSocketMethodCall( if ( encT.value === "buffer" && chunkT.kind === "string" && - isJsSourceFile(call.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(call)) ) { lowerer.lowerExpr(args[0]!); // evaluation order (effect-free in practice) return nodeThrowExpr(1, "ERR_UNKNOWN_ENCODING", "Unknown encoding: buffer", VOID, loc); @@ -2476,6 +2598,20 @@ function lowerNetSocketMethodCall( ? value : lowerer.coerceInto(call, value, declared); } + if (name === "isSessionReused" && args.length === 0) { + // TLSSocket.isSessionReused(): the client never offers a cached + // session (no session resumption, a documented TLS divergence), so + // no handshake resumes one. A plain socket asserted to be a + // TLSSocket answers false too, where Node has no such method. + const receiver = coerceToHandle(lowerer, access.expression, NETSOCKET_T); + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: receiver, loc }], + result: boolLit(false, loc), + type: BOOL, + loc, + }; + } if (name === "resetAndDestroy") { // An RST close; a connecting socket resets once it connects. Answers // the socket, Node's chaining. @@ -2722,6 +2858,17 @@ function lowerNetSocketMethodCall( : "net.sockOnConnect"; return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc }; } + if (event === "lookup") { + // (err, address, family, host) after the host name resolves, before + // the connect: once per resolved address, or once with the error. + return { + kind: "libCall", + fn: "net.sockOnLookup", + args: [receiver, lowerSocketDynListener(lowerer, args[1]!, "lookup listeners"), once], + type: VOID, + loc, + }; + } if (event === "finish") { // Fires once when the FIN goes out — once either way (the event // happens at most once per socket). @@ -2781,7 +2928,7 @@ function lowerNetSocketMethodCall( lowerer.noLowering( `socket.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`, args[0]!, - '"data", "end", "close", "error", "connect", "ready", "timeout", "readable", "secureConnect", and "session" are the supported socket events (as literals)', + '"data", "end", "close", "error", "connect", "lookup", "ready", "timeout", "readable", "secureConnect", and "session" are the supported socket events (as literals)', ); } lowerer.noLowering( @@ -3957,6 +4104,30 @@ export function lowerServerProperty( if (lowerer.mapTypeOf(lowerer.typeOf(inner.expression))?.kind !== "netServer") return null; if (!lowerer.isStdlibMember(inner)) return null; const receiver = coerceToHandle(lowerer, inner.expression, NETSERVER_T); + if (unixListenReachable(lowerer)) { + const local = lowerer.declareHiddenLocal("%addressServer", NETSERVER_T); + const server = (): IrExpr => varRef(local.id, NETSERVER_T, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: local.id, init: receiver, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "net.serverAddressCheck", + args: [server()], + type: VOID, + loc, + }, + loc, + }, + ], + result: { kind: "libCall", fn: "net.serverPort", args: [server()], type: F64, loc }, + type: F64, + loc, + }; + } return { kind: "libCall", fn: "net.serverPort", args: [receiver], type: F64, loc }; } @@ -4680,7 +4851,7 @@ function tlsLiteralNeedsRuntimeWalk( node: ts.ObjectLiteralExpression, includeHttpTimeoutOptions = false, ): boolean { - const jsSource = isJsSourceFile(node.getSourceFile()); + const jsSource = isJsSourceFile(ts.getSourceFileOfNode(node)); return node.properties.nodes.some( (p) => (ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p)) && @@ -4900,7 +5071,7 @@ function lowerTlsModuleCall( bi.member === "getCACertificates" && args.length === 1 && !args.some(ts.isSpreadElement) && - isJsSourceFile(expr.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(expr)) ) { // The type-argument ladder (validateString + the documented name // set); the real CA list has no lowering, so a valid name meets the @@ -5566,7 +5737,7 @@ function lowerHttp2ModuleCall( if (ts.isConditionalExpression(inner)) { const whenTrue = stripParensAndCasts(inner.whenTrue); const whenFalse = stripParensAndCasts(inner.whenFalse); - const condText = inner.condition.getText(); + const condText = ts.getTextOfNode(inner.condition); const ok = ts.isObjectLiteralExpression(whenTrue) && ts.isObjectLiteralExpression(whenFalse) && @@ -5577,7 +5748,7 @@ function lowerHttp2ModuleCall( ts.isStringLiteral(whenTrue.properties.nodes[0]!.name)) && (whenTrue.properties.nodes[0]!.name as ts.Identifier | ts.StringLiteral).text === "SNICallback" && - whenTrue.properties.nodes[0]!.initializer.getText() === condText; + ts.getTextOfNode(whenTrue.properties.nodes[0]!.initializer) === condText; if (!ok) { lowerer.noLowering( "createSecureServer options with a conditional spread", @@ -5813,6 +5984,44 @@ function lowerHttp2ModuleCall( ); } +/** The checked-dynamic options value of a request whose options the + * runtime reads: a non-literal options value (a RequestOptions variable — + * checked-dynamic storage — or a record that converts to one), or a + * literal the static walk cannot lower because it spreads another options + * value or sets socketPath. Null keeps the static literal walk (and its + * fences), which every other literal takes unchanged. */ +function runtimeRequestOptions( + lowerer: Lowerer, + optsNode: ts.Expression, + secureish: boolean, +): IrExpr | null { + if (ts.isObjectLiteralExpression(optsNode)) { + const needsRuntime = optsNode.properties.nodes.some((prop) => { + if (ts.isSpreadAssignment(prop)) { + // the static walk's one spread: `...(c ? { rejectUnauthorized } : {})` on https + const cs = conditionalSpreadOf(prop.expression); + return !( + secureish && + cs !== null && + cs !== "unsupported" && + cs.props.length === 1 && + cs.props[0]!.name.text === "rejectUnauthorized" + ); + } + return ( + (ts.isPropertyAssignment(prop) || ts.isShorthandPropertyAssignment(prop)) && + (ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) && + prop.name.text === "socketPath" + ); + }); + if (!needsRuntime) return null; + return lowerDynObjectLiteral(lowerer, optsNode); + } + const t = lowerer.mapTypeOf(lowerer.typeOf(optsNode)); + if (!t || !(t.kind === "dyn" || (t.kind === "record" && lowerer.dynConvertible(t)))) return null; + return lowerer.coerceToExpected(lowerer.lowerExpr(optsNode), DYN); +} + /** http.request(options[, cb]) / http.get(options[, cb]) — the client. * The options argument must be an OBJECT LITERAL; the lowered keys are * hostname/host, port, path, method, timeout, and headers (a nested @@ -5855,6 +6064,40 @@ function lowerHttpClientCall( ); } const optsNode = args[0]!; + const runtimeOptions = + binding === null ? runtimeRequestOptions(lowerer, optsNode, secureish) : null; + if (runtimeOptions !== null) { + // The options record is read at run time (the ClientRequest + // constructor's walk in the runtime): a RequestOptions variable, or a + // literal that spreads one or sets socketPath. + const autoEnd = boolLit(member === "get", loc); + const secureDyn = secure === true; + if (args.length === 1) { + return { + kind: "libCall", + fn: secureDyn ? "https.requestDyn" : "http.requestDyn", + args: [runtimeOptions, autoEnd], + type: HTTPCLIENTREQ_T, + loc, + }; + } + const { cb } = lowerCallbackArg( + lowerer, + args[1]!, + "response callbacks", + 1, + (p) => p.kind === "httpReq", + "use (res) or ()", + [HTTPREQ_T], + ); + return { + kind: "libCall", + fn: secureDyn ? "https.requestDynCb" : "http.requestDynCb", + args: [runtimeOptions, autoEnd, cb], + type: HTTPCLIENTREQ_T, + loc, + }; + } if (!ts.isObjectLiteralExpression(optsNode)) { const t = lowerer.mapTypeOf(lowerer.typeOf(optsNode)); if ((t?.kind === "string" || t?.kind === "url") && binding === null) { @@ -7135,6 +7378,27 @@ function lowerHttpReqMethodCall( const event = evT.isStringLiteralType() ? evT.value : null; const receiver = coerceToHandle(lowerer, access.expression, HTTPREQ_T); if (event === "data") { + const sigs = lowerer.checker.getCallSignatures(lowerer.typeOf(args[1]!)); + const firstParam = sigs.length === 1 ? sigs[0]!.getParameters()[0] : undefined; + const param = + firstParam !== undefined + ? lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(firstParam)) + : null; + if (param !== null && param.kind === "string") { + // A chunk typed string (the setEncoding('utf8') shape), as on a + // socket: the listener takes the chunk through a checked + // boundary, so a string arrives after setEncoding and a Buffer + // without it fails the check instead of being decoded. + const boxed = lowerSocketDynListener(lowerer, args[1]!, "data listeners"); + const cb: IrExpr = { kind: "dynCheck", value: boxed, type: funcOf([DYN], VOID), loc }; + return { + kind: "libCall", + fn: "http.reqOnData", + args: [receiver, cb, once], + type: VOID, + loc, + }; + } const { cb } = lowerCallbackArg( lowerer, args[1]!, @@ -7269,7 +7533,7 @@ function lowerHttpResMethodCall( } if (name === "writeContinue" || name === "writeProcessing" || name === "writeEarlyHints") { requireStatementPosition(lowerer, call, `res.${name}(...)`); - if (lowerer.typeOf(access.expression).getSymbol()?.name === "Http2ServerResponse") { + if (ts.symbolNameOf(lowerer.typeOf(access.expression).getSymbol()) === "Http2ServerResponse") { lowerer.noLowering( `Http2ServerResponse.${name}`, call, @@ -7378,7 +7642,7 @@ function lowerHttpResMethodCall( } if (name === "addTrailers") { requireStatementPosition(lowerer, call, "res.addTrailers(...)"); - if (lowerer.typeOf(access.expression).getSymbol()?.name === "Http2ServerResponse") { + if (ts.symbolNameOf(lowerer.typeOf(access.expression).getSymbol()) === "Http2ServerResponse") { lowerer.noLowering( "Http2ServerResponse.addTrailers", call, @@ -7697,6 +7961,30 @@ function lowerHttpResMethodCall( if (cbArg !== null) return endWithCb(fn, [receiver, data], cbArg); return { kind: "libCall", fn, args: [receiver, data], type: VOID, loc }; } + // end(payload ? text : undefined): an absent chunk ends without data, + // as end() does. The checked-dynamic end takes each arm at run time. + if (name === "end" && data.type.kind === "union" && lowerer.dynConvertible(data.type)) { + const arms = lowerer.unions.get(data.type.unionId)?.arms ?? []; + if ( + arms.every( + (arm) => + arm.kind === "string" || + (arm.kind === "bytes" && arm.elem === "u8") || + arm.kind === "undefinedT" || + arm.kind === "nullT", + ) + ) { + const dynData: IrExpr = { kind: "dynFrom", value: data, type: DYN, loc: locOf(dataNode) }; + if (cbArg !== null) return endWithCb("http.resEndDyn", [receiver, dynData], cbArg); + return { + kind: "libCall", + fn: "http.resEndDyn", + args: [receiver, dynData], + type: VOID, + loc, + }; + } + } lowerer.noLowering( `${name} of '${lowerer.fmt(data.type)}' data`, dataNode ?? call, diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index 2f0e1c66f..bc8955a81 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -4,6 +4,7 @@ import { InternalCompilerError } from "../../errors.js"; import * as ts from "../ts7/adapter.js"; import { lowerToNumberArgument } from "./lower-exprs.js"; import type { Lowerer } from "./lowerer.js"; +import { isDeclaredInAmbientNamespace } from "../type-mapper.js"; import { arrayOf, BOOL, @@ -42,6 +43,8 @@ import { STATIC_RESPONSE_CALLS, STATIC_RESPONSE_READS, } from "../../compat/fetch-profile.js"; +import { classGeneratorIterable } from "./class-iterables.js"; +import { generatorDrain } from "./iterator-adapters.js"; export function refuseUnsupportedApi( lowerer: Lowerer, @@ -664,7 +667,7 @@ export function lowerStaticResponseCall(lowerer: Lowerer, call: ts.CallExpressio } const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; const recv = lowerer.lowerExpr(access.expression); if (recv.type.kind !== "dyn") return null; return lowerStaticFixedFetchMethodCall(lowerer, call, access, member, recv, [], (receiver) => @@ -750,7 +753,7 @@ function lowerStaticFixedFetchMethodCall( loc, }, then: answer, - else_: nodeThrowExpr(1, "", `${access.getText()} is not a function`, answer.type, loc), + else_: nodeThrowExpr(1, "", `${ts.getTextOfNode(access)} is not a function`, answer.type, loc), type: answer.type, loc, }; @@ -924,7 +927,7 @@ export function fenceStaticResponseMember( ): IrExpr | null { const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Response" || !lowerer.isStdlibSymbol(sym)) return null; const members = fetchAccessMemberNames(lowerer, access); if (members === null) return null; const status = (member: string) => @@ -966,7 +969,7 @@ export function fenceStaticHeadersMember( ): IrExpr | null { const recvType = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const sym = recvType.getAliasSymbol() ?? recvType.getSymbol(); - if (!sym || sym.name !== "Headers" || !lowerer.isStdlibSymbol(sym)) return null; + if (!sym || ts.symbolName(sym) !== "Headers" || !lowerer.isStdlibSymbol(sym)) return null; const members = fetchAccessMemberNames(lowerer, access); if (members === null) return null; const status = (member: string) => @@ -1021,7 +1024,16 @@ export function fenceStaticAbortControllerMemberRead( function isStdlibFetchInterface(lowerer: Lowerer, node: ts.Node, owner: string): ts.Symbol | null { const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node)); for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) { - if (symbol?.name === owner && lowerer.isStdlibSymbol(symbol)) return symbol; + // NodeJS.ReadableStream shares the WHATWG interface's name. + if ( + symbol !== undefined && + ts.symbolName(symbol) === owner && + lowerer.isStdlibSymbol(symbol) && + !symbol.declarations.some( + (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"), + ) + ) + return symbol; } return null; } @@ -1082,10 +1094,10 @@ function fetchInterfaceInventoryOwner( const symbol = [type.getSymbol(), type.getAliasSymbol()].find( (candidate) => candidate !== undefined && - NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(candidate.name) && + NODE24_FETCH_COMPAT_PROFILE.inventory.interfaces.includes(ts.symbolName(candidate)) && lowerer.isStdlibSymbol(candidate), ); - return symbol ? { owner: symbol.name, symbol } : null; + return symbol ? { owner: ts.symbolName(symbol), symbol } : null; } function fenceFetchObjectMembers( @@ -1297,7 +1309,7 @@ export function lowerFetchElementMethodCall( kind: "dynInvoke", recv: receiverRef, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1310,7 +1322,7 @@ export function lowerFetchElementMethodCall( let result: IrExpr = nodeThrowExpr( 1, "", - `${access.getText()} is not a function`, + `${ts.getTextOfNode(access)} is not a function`, invocationType, loc, ); @@ -1464,7 +1476,8 @@ export function lowerStaticFetchCompanionCall( } const root = call.expression.expression; const symbol = lowerer.resolveValueSymbol(root); - if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; + if ((!symbol || !lowerer.isStdlibSymbol(symbol)) && !isStreamWebReadableStream(lowerer, root)) + return null; const loc = locOf(call); if (root.text === "AbortSignal") { noteEventListeners(lowerer, loc); @@ -1615,9 +1628,10 @@ export function lowerStaticAbortControllerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - symbol?.name !== "AbortController" || + symbol === undefined || + ts.symbolName(symbol) !== "AbortController" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1640,7 +1654,7 @@ export function lowerStaticAbortControllerCall( kind: "dynInvoke", recv: receiverRef, method: "abort", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1669,9 +1683,10 @@ export function lowerStaticReadableStreamControllerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - symbol?.name !== "ReadableStreamDefaultController" || + symbol === undefined || + ts.symbolName(symbol) !== "ReadableStreamDefaultController" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1695,7 +1710,7 @@ export function lowerStaticReadableStreamControllerCall( kind: "dynInvoke", recv: receiver, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1744,7 +1759,7 @@ export function lowerStaticReadableStreamCancelCall( kind: "dynInvoke", recv: receiverRef, method: "cancel", - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1777,9 +1792,10 @@ export function lowerStaticAbortSignalListenerCall( const receiverTs = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(access.expression)); const symbol = receiverTs.getAliasSymbol() ?? receiverTs.getSymbol(); if ( - (symbol?.name !== "AbortSignal" && symbol?.name !== "EventTarget") || + ((symbol === undefined || ts.symbolName(symbol) !== "AbortSignal") && + (symbol === undefined || ts.symbolName(symbol) !== "EventTarget")) || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -1802,7 +1818,7 @@ export function lowerStaticAbortSignalListenerCall( kind: "dynInvoke", recv: receiver, method: member, - calleeName: access.getText(), + calleeName: ts.getTextOfNode(access), args: argumentRefs, type: DYN, loc, @@ -1877,6 +1893,12 @@ export function lowerAbortControllerNew(lowerer: Lowerer, expr: ts.NewExpression }; } +/** An import binding of node:stream/web's ReadableStream, which IS the + * global class (`import { ReadableStream } from "node:stream/web"`). */ +function isStreamWebReadableStream(lowerer: Lowerer, ident: ts.Identifier): boolean { + return ident.text === "ReadableStream" && lowerer.isStdlibGlobal(ident, "ReadableStream"); +} + export function lowerStaticReadableStreamNew( lowerer: Lowerer, expr: ts.NewExpression, @@ -1885,7 +1907,11 @@ export function lowerStaticReadableStreamNew( return null; } const symbol = lowerer.resolveValueSymbol(expr.expression); - if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; + if ( + (!symbol || !lowerer.isStdlibSymbol(symbol)) && + !isStreamWebReadableStream(lowerer, expr.expression) + ) + return null; const args = expr.arguments?.nodes ?? []; if (args.length > 1) return null; const loc = locOf(expr); @@ -2124,9 +2150,10 @@ export function lowerStaticReadableStreamReaderCall( const receiverTs = lowerer.typeOf(access.expression); const symbol = receiverTs.getSymbol(); if ( - symbol?.name !== "ReadableStreamDefaultReader" || + symbol === undefined || + ts.symbolName(symbol) !== "ReadableStreamDefaultReader" || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return null; @@ -2167,7 +2194,7 @@ export function lowerDynamicImportCall(lowerer: Lowerer, call: ts.CallExpression if (call.arguments.nodes.length !== 1) { lowerer.unsupported("SC1090", call, "dynamic import() with import attributes"); } - const res = lowerer.dynImports.get(`${call.getSourceFile().fileName}\u0000${arg.text}`); + const res = lowerer.dynImports.get(`${ts.getSourceFileOfNode(call).fileName}\u0000${arg.text}`); if (res === undefined) lowerer.refuseUnsupportedApi("'import()'", call); if (res.kind === "program-module") { return lowerOwnStaticModuleImport(lowerer, call, arg); @@ -2436,6 +2463,10 @@ export function lowerStdlibMethodCall( src = lowerer.coerceInto(spread.expression, src, inferred); } } + // A generator, or a class whose [Symbol.iterator] is a generator + // method, drains into the number[] the fold reads. + src = classGeneratorIterable(lowerer, src) ?? src; + if (src.type.kind === "generator") src = generatorDrain(lowerer, src, F64, loc) ?? src; if (src.type.kind !== "array" || src.type.elem.kind !== "f64") { lowerer.unsupported( "SC1090", @@ -2543,6 +2574,6 @@ export function npmMemberFence(lowerer: Lowerer, access: ts.PropertyAccessExpres export function npmPackageOfSymbol(lowerer: Lowerer, sym: ts.Symbol | undefined): string | null { const decls = sym ? sym.declarations : undefined; if (!decls || decls.length === 0) return null; - if (!decls.every((d) => lowerer.isNpmFile(d.getSourceFile()))) return null; - return npmPackageNameOf(decls[0]!.getSourceFile().fileName); + if (!decls.every((d) => lowerer.isNpmFile(ts.getSourceFileOfNode(d)))) return null; + return npmPackageNameOf(ts.getSourceFileOfNode(decls[0]!).fileName); } diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 2c36e4973..69f6cc99b 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -1,3 +1,8 @@ +import { lowerTupleAliasDecl, stripTypeWrappers } from "./typed-arguments.js"; +import { declaredMemberOf } from "./expressions/property-access.js"; +import { hostsAll } from "./expressions/hosted-narrowing.js"; +import { requestGeneralStreamSurface } from "./general-streams.js"; +import { lowerForAwaitDynStream } from "./dyn-streams.js"; import { matchAllRowsReadByElements, storedMatchAllRowsReadByElements, @@ -15,7 +20,7 @@ import { InternalCompilerError } from "../../errors.js"; * flow (if/while/for/for-of/do, switch, try/catch, abrupt completions), * explicit resource management, and blocked-binding poisoning. */ import * as ts from "../ts7/adapter.js"; -import { processDataMemberValue } from "./builtins/process.js"; +import { processDataMemberValue, programReadsExitCode } from "./builtins/process.js"; import { requireInitFlagStmt } from "./require-initialization.js"; import { isJsonRequireDecl } from "./json-require.js"; import { bindingSource } from "../binding-source.js"; @@ -31,6 +36,7 @@ import { import { cjsExportMemberWriteGlobalOf, isCjsExportsIdentifier, + isCjsModuleExports, isFreshExportsReset, } from "./cjs-exports-object.js"; import { isDeadTscHelperDecl, tscInteropBindingOfDecl } from "./cjs-tsc-interop.js"; @@ -86,11 +92,11 @@ import { isCjsJsFile, isEsModuleStamp, isJsSourceFile, - isNodeEsmFile, locOf, requireEarlyReference, requireSpecOf, } from "../program.js"; +import { isStrictCode } from "./strict-mode.js"; import { COMPOUND_ASSIGN_OPS, type CompoundOp, @@ -194,6 +200,7 @@ import { import { globalSymbolKey } from "./expressions/global-symbols.js"; import { lowerShortCircuitAssignment } from "./expressions/nullish-assignment.js"; import { + builtinGlobalValue, jsHoldsDynObject, lowerEnvironmentKey, lowerNativeFunctionAssignment, @@ -222,6 +229,7 @@ import { constituentTypes } from "../ts7/checker.js"; import { npmStaticPackageOfPath } from "../npm-static.js"; import { fenceNodeModuleMutation } from "./lower-node-module.js"; import { fencePerfEntryKeyWalk } from "./builtins/performance.js"; +import { classGeneratorIterable } from "./class-iterables.js"; interface UsingRegistration { acquire: IrStmt[]; @@ -248,8 +256,8 @@ function npmStaticReturnedEmptyArrayType( decl.initializer === undefined || !ts.isArrayLiteralExpression(decl.initializer) || decl.initializer.elements.nodes.length !== 0 || - !isJsSourceFile(decl.getSourceFile()) || - npmStaticPackageOfPath(decl.getSourceFile().fileName) === null || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) === null || lowerer.ctx.returnType.kind !== "array" ) { return null; @@ -288,8 +296,8 @@ function npmStaticOptionalSelfWriteType( ): (IrType & { kind: "union" }) | null { if ( !ts.isIdentifier(decl.name) || - !isJsSourceFile(decl.getSourceFile()) || - npmStaticPackageOfPath(decl.getSourceFile().fileName) === null || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) === null || initial.kind === "union" ) { return null; @@ -351,7 +359,9 @@ function stdlibTypeNamed(lowerer: Lowerer, node: ts.Node, name: string): boolean if (t.flags & ts.TypeFlags.Union) return constituentTypes(t).some(visit); const sym = t.getAliasSymbol() ?? t.getSymbol(); return ( - sym?.name === name && sym.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())) + sym !== undefined && + ts.symbolName(sym) === name && + sym.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) ); }; return visit(lowerer.typeOf(node)); @@ -605,7 +615,7 @@ function pureAnnotatedDeadConst(stmt: ts.Statement, sf: ts.SourceFile): boolean return decls.every((d) => { let init = d.initializer; if (init === undefined) return false; - const trivia = sf.text.slice(init.pos, init.getStart(sf)); + const trivia = sf.text.slice(init.pos, ts.tokenStartOfNode(init)); if (!trivia.includes("@__PURE__") && !trivia.includes("#__PURE__")) return false; // `as` casts are type-world wrappers (`(() => {...})() as unknown as // { new (): any }` — cookie's NullObject shape); the runtime @@ -632,7 +642,7 @@ function pureAnnotatedDeadConst(stmt: ts.Statement, sf: ts.SourceFile): boolean * probes that would otherwise claim (and diagnose) the call-initializer * shape. */ export function provenanceElidedConstDecl(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (!isProvenanceSourceFile(sf.fileName)) return false; const stmt = decl.parent?.parent; if (!ts.isVariableStatement(stmt) || !pureAnnotatedDeadConst(stmt, sf)) return false; @@ -675,7 +685,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir // runtimeFence — a catchable, SC-coded throw at the statement's // position — instead of failing the whole build). TypeScript keeps // compile fences. ICEs (SC9001) always stay compile errors. - const sf = stmts[0]?.getSourceFile(); + const sf = stmts.length === 0 ? undefined : ts.getSourceFileOfNode(stmts[0]!); const deferFences = sf !== undefined && isJsSourceFile(sf); // Register this list as open for the forward-capture machinery: a // nested function lowering mid-list may pre-declare a LATER const of @@ -951,7 +961,7 @@ export function predeclareForwardCapture(lowerer: Lowerer, symbol: ts.Symbol): b return false; let type = lowerer.irTypeOf(decl.name); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && (ts.isObjectLiteralExpression(decl.initializer) || @@ -1099,7 +1109,7 @@ function jsBindingKeepsCheckedObject( nameNode: ts.Identifier, type: IrType, ): boolean { - if (!isJsSourceFile(nameNode.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(nameNode))) return false; const host = hostVariableDeclarationOf(nameNode); if (!host || host.type || hasJsTypeAnnotation(host)) return false; const arms = type.kind === "union" ? (lowerer.unions.get(type.unionId)?.arms ?? []) : [type]; @@ -1114,8 +1124,9 @@ function jsBindingKeepsCheckedObject( function mentionsSymbol(lowerer: Lowerer, node: ts.Node, symbol: ts.Symbol): boolean { if (ts.isIdentifier(node)) return lowerer.checker.getSymbolAtLocation(node) === symbol; return ( - node.forEachChild((child) => (mentionsSymbol(lowerer, child, symbol) ? true : undefined)) === - true + ts.forEachChild(node, (child) => + mentionsSymbol(lowerer, child, symbol) ? true : undefined, + ) === true ); } @@ -1161,7 +1172,8 @@ function lowerJsVarFromExisting( symbol: ts.Symbol, ): IrStmt | null { if (!ts.isIdentifier(decl.name) || !decl.initializer) return null; - if (!isJsSourceFile(decl.getSourceFile()) || lowerer.ctx.hoistedVars.has(symbol)) return null; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl)) || lowerer.ctx.hoistedVars.has(symbol)) + return null; if (lowerer.bindingIn(lowerer.ctx, symbol)) return null; let node: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(node) || ts.isSatisfiesExpression(node)) @@ -1204,13 +1216,13 @@ function varBindingType(lowerer: Lowerer, nameNode: ts.Identifier): IrType | nul ) return DYN; if ( - isJsSourceFile(nameNode.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(nameNode)) && ts.isVariableDeclaration(nameNode.parent) && isNativeBuiltinValueInitializer(lowerer, nameNode.parent.initializer) ) return DYN; let type = lowerer.mapTypeOf(lowerer.typeOf(nameNode)); - if (type?.kind === "record" && isJsSourceFile(nameNode.getSourceFile())) { + if (type?.kind === "record" && isJsSourceFile(ts.getSourceFileOfNode(nameNode))) { const shape = lowerer.shapes.get(type.shapeId); if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple) type = DYN; } @@ -1476,7 +1488,21 @@ function lowerLabeled(lowerer: Lowerer, stmt: ts.LabeledStatement): IrStmt | IrS return { kind: "block", body, labels, loc: locOf(stmt) }; } +// Value kinds a `throw` converts to a checked-dynamic value, so the catch +// binding sees the thrown object rather than a type-erased reference. +const THROWN_AS_DYN: ReadonlySet = new Set([ + "array", + "union", + "map", + "set", + "bytes", + "func", + "regex", +]); + export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt[] | null { + if (ownPrototypeResetStatement(lowerer, stmt)) + return { kind: "block", body: [], loc: locOf(stmt) }; if (ts.isClassDeclaration(stmt)) { if (!stmt.name) lowerer.unsupported("SC1090", stmt, "anonymous local class declarations"); const info = lowerer.lowerClassExpressionInfo(stmt); @@ -1654,6 +1680,12 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt value = lowerer.coerceToExpected(value, DYN); } else if (value.type.kind === "record" && lowerer.dynConvertible(value.type)) { value = { kind: "dynFrom", value, liveRef: true, type: DYN, loc: locOf(stmt.expression) }; + } else if (THROWN_AS_DYN.has(value.type.kind) && lowerer.dynConvertible(value.type)) { + // Arrays, Maps, Sets, typed arrays, Buffers, functions, regexes and + // unions over them keep their identity and contents the same way: + // the catch binding, `typeof`, `instanceof`, `Array.isArray`, + // inspection and typed views read the thrown object. + value = lowerer.coerceInto(stmt.expression, value, DYN); } if (value.type.kind === "date") { lowerer.unsupported( @@ -1793,7 +1825,7 @@ function lowerVarDeclarator( requireSpecOf(decl.initializer) !== null && !isNativeFfiRequire(lowerer, decl.initializer) && !isJsonRequireDecl(lowerer, decl) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { const spec = requireSpecOf(decl.initializer)!; if (lowerer.externalTypes.has(spec)) lowerer.externalHostFence(spec, decl, false); @@ -2042,7 +2074,7 @@ function lowerMatchAllDrainDecl( export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): boolean { const widened = lowerer.checker.getBaseTypeOfLiteralType(type); const declarationBelongs = (d: ts.Node): boolean => { - if (!lowerer.isStdlibFile(d.getSourceFile())) return false; + if (!lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) return false; let inParseArgsType = false; for (let node: ts.Node | undefined = d.parent; node; node = node.parent) { if ( @@ -2058,7 +2090,11 @@ export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): bool return false; }; const sym = widened.getAliasSymbol() ?? widened.getSymbol(); - if (sym && isParseArgsDynTypeName(sym.name) && sym.declarations.some(declarationBelongs)) { + if ( + sym && + isParseArgsDynTypeName(ts.symbolName(sym)) && + sym.declarations.some(declarationBelongs) + ) { return true; } // A discriminant check erases the token alias and leaves one anonymous @@ -2069,43 +2105,6 @@ export function isParseArgsDynCheckerType(lowerer: Lowerer, type: ts.Type): bool .some((p) => p.declarations.some(declarationBelongs)); } -/** True when an `any` value comes from a JavaScript function's result: - * a call (possibly awaited) whose callee is declared in a JavaScript file, - * or a const bound to one. A TypeScript program importing a JavaScript - * package sees the package's inferred types (`jwtVerify` from jose - * answers `any`), and its object destructuring reads the members of that - * checked-dynamic value as JavaScript does — member reads on the same - * value already compile. Other `any` sources keep the fence. */ -function javaScriptAnyOrigin(lowerer: Lowerer, node: ts.Expression, depth: number): boolean { - let expr = node; - while (ts.isParenthesizedExpression(expr) || ts.isAwaitExpression(expr)) expr = expr.expression; - if (ts.isCallExpression(expr)) { - const callee = ts.isPropertyAccessExpression(expr.expression) - ? expr.expression.name - : expr.expression; - if (!ts.isIdentifier(callee)) return false; - const symbol = lowerer.resolveValueSymbol(callee); - const declarations = symbol ? symbol.declarations : []; - return ( - declarations.length > 0 && - declarations.every((d) => isJsSourceFile(d.getSourceFile()) && ts.isFunctionLike(d)) - ); - } - if (ts.isIdentifier(expr) && depth === 0) { - const symbol = lowerer.resolveValueSymbol(expr); - const declaration = symbol ? symbol.valueDeclaration : undefined; - return ( - declaration !== undefined && - ts.isVariableDeclaration(declaration) && - ts.isIdentifier(declaration.name) && - declaration.initializer !== undefined && - (ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const) !== 0 && - javaScriptAnyOrigin(lowerer, declaration.initializer, depth + 1) - ); - } - return false; -} - /** Destructuring declarations, desugared as sugar over indexed/field * reads: the initializer evaluates ONCE into a hidden temp, then each * bound name declares (or assigns its pre-registered module global) from @@ -2144,18 +2143,18 @@ export function lowerDestructuringDecl( const parseArgsDynObject = init.type.kind === "dyn" && isParseArgsDynCheckerType(lowerer, lowerer.typeOf(decl.initializer)); - // An `any`-origin source needs a represented record or array shape for - // destructuring. JS files retain their per-site runtime fences. + // An `any`-origin source destructures through a represented record or + // array shape, or through the checked-dynamic value it lowered to: the + // pattern reads members (or iterates) the dyn value exactly as a + // JavaScript file's pattern does, with the same per-site runtime fences + // (null/undefined sources throw Node's TypeError). Primitive and unit + // sources keep their own fences below; other carriers keep this one. if ( lowerer.anyOrigin(decl.initializer) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && init.type.kind !== "record" && init.type.kind !== "array" && - !( - init.type.kind === "dyn" && - ts.isObjectBindingPattern(decl.name) && - javaScriptAnyOrigin(lowerer, decl.initializer, 0) - ) + init.type.kind !== "dyn" ) { lowerer.anyOpFence("destructuring", decl); } @@ -2168,7 +2167,7 @@ export function lowerDestructuringDecl( // the checker's own type instead of the carrier. if ( init.type.kind === "string" && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !checkerStringSource(lowerer, decl.initializer) ) { lowerer.unsupported( @@ -2186,7 +2185,7 @@ export function lowerDestructuringDecl( init.type.kind === "bool" || init.type.kind === "string" || isUnitType(init.type)) && - !isJsSourceFile(decl.getSourceFile()) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && !( init.type.kind === "string" && staticStringPattern(lowerer, decl.name as ts.ArrayBindingPattern | ts.ObjectBindingPattern) @@ -2210,7 +2209,7 @@ export function lowerDestructuringDecl( // (the message is the fence's — a unit value here is the failure // path either way). TypeScript keeps the fence: narrowing first is // the annotated fix. - if (isJsSourceFile(decl.getSourceFile()) && init.type.kind === "union") { + if (isJsSourceFile(ts.getSourceFileOfNode(decl)) && init.type.kind === "union") { const def = lowerer.unions.get(init.type.unionId); const dataArms = (def?.arms ?? []).filter((a) => !isUnitType(a)); const arm = dataArms[0]; @@ -2260,6 +2259,50 @@ export function lowerDestructuringDecl( if (groupsSource !== null) init = groupsSource; const tmp = lowerer.declareHiddenLocal("%destr", init.type); out.push({ kind: "varDecl", localId: tmp.id, init, loc }); + // A TypeScript `any` source destructured through its checked-dynamic + // value: RequireObjectCoercible with V8's own TypeError ("Cannot + // destructure property 'a' of 'f(...)' as it is null.") for the source + // spellings the assignment form reproduces; other sources are refused, + // as in the assignment form. JavaScript files keep their existing + // per-read errors. + if ( + init.type.kind === "dyn" && + ts.isObjectBindingPattern(decl.name) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && + lowerer.anyOrigin(decl.initializer) + ) { + const spelling = destrSpellingOf(decl.initializer); + if (spelling === null) { + lowerer.unsupported( + "SC1031", + decl.initializer, + "destructuring declaration from an `any` source whose expression spelling is unsupported (use a variable, ordinary property chain, or ordinary call)", + ); + } + const first = decl.name.elements.nodes[0]; + const firstKey = + first === undefined || first.dotDotDotToken !== undefined + ? undefined + : (first.propertyName ?? first.name); + // V8 names the first property when its key is an identifier, a string + // or a number; a computed first key takes the bare (source-only) form. + const firstProp = + firstKey === undefined || ts.isComputedPropertyName(firstKey) + ? undefined + : (patternKeyNameOf(lowerer, firstKey as ts.PropertyName) ?? undefined); + out.push({ + kind: "exprStmt", + expr: { + kind: "dynDestrCheck", + value: { kind: "varRef", localId: tmp.id, type: init.type, loc }, + spelling, + ...(firstProp !== undefined ? { firstProp } : {}), + type: init.type, + loc, + }, + loc, + }); + } // V8's destructuring TypeError spells STATIC sources: an identifier // source reads " is not iterable", an identifier-callee call // " is not a function or its return value is not iterable"; @@ -2306,7 +2349,7 @@ export function matchGroupsPatternSource( !ts.isObjectBindingPattern(pattern) || pattern.elements.nodes.length === 0 || !(row.type.kind === "dyn" - ? isJsSourceFile(pattern.getSourceFile()) + ? isJsSourceFile(ts.getSourceFileOfNode(pattern)) : isMatchSliceType(lowerer, row.type)) || !pattern.elements.nodes.every((el) => { if (el.dotDotDotToken !== undefined || el.name === undefined) return false; @@ -2349,7 +2392,9 @@ export function lowerBindingPattern( const patternType = lowerer.typeOf(pattern); const patternSymbol = patternType.getSymbol(); allowDynObject ||= - patternSymbol?.name === "SegmentData" && lowerer.isStdlibSymbol(patternSymbol); + patternSymbol !== undefined && + ts.symbolName(patternSymbol) === "SegmentData" && + lowerer.isStdlibSymbol(patternSymbol); allowDynObject ||= (patternType.flags & ts.TypeFlags.Object) !== 0 && lowerer.mapTypeOf(patternType)?.kind === "dyn"; @@ -2379,20 +2424,30 @@ export function lowerBindingPattern( lowerer.fenceStaticHeadersIteration(pattern); if ( srcType.kind === "union" && - lowerer.unions.get(srcType.unionId)?.arms.every((arm) => arm.kind === "array") + lowerer.unions + .get(srcType.unionId) + ?.arms.every( + (arm) => + arm.kind === "array" || + arm.kind === "string" || + (arm.kind === "record" && lowerer.shapes.get(arm.shapeId)?.tuple === true), + ) && + lowerer.unions.get(srcType.unionId)!.arms.some((arm) => arm.kind !== "string") ) { for (let i = 0; i < pattern.elements.nodes.length; i++) { const el = pattern.elements.nodes[i]!; if (ts.isOmittedExpression(el) || el.name === undefined) continue; if (el.dotDotDotToken) - lowerer.unsupported("SC1031", el, "rest elements over a union of array layouts"); + lowerer.unsupported("SC1031", el, "rest elements over a union of array or tuple layouts"); const loc = locOf(el); - let value = unionArrayValueRead(lowerer, srcRef(), numLit(i, loc), loc); + let value = unionArrayValueRead(lowerer, srcRef(), numLit(i, loc), loc, (text) => + strCharsCall(lowerer, text, loc), + ); if (!value) lowerer.unsupported( "SC1031", el, - "array union elements without a common native representation", + "array or tuple union elements without a common native representation", ); if (el.initializer) value = applyBindingDefault(lowerer, el, value); lowerer.bindPatternTarget(el.name, value, isLet, out); @@ -2797,7 +2852,7 @@ export function lowerBindingPattern( // object patterns use the same exact keyed-read desugar. if ( srcType.kind === "union" && - isJsSourceFile(pattern.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(pattern)) && lowerer.dynConvertible(srcType) ) { lowerer.lowerBindingPattern( @@ -3081,6 +3136,11 @@ export function lowerBindingPattern( lowerArrayPositionObjectPattern(lowerer, pattern, srcRef, srcType, isLet, out); return; } + const nsSource = lowerer.sourceFileOfModuleNamespace(srcType); + if (nsSource !== null) { + lowerModuleNamespacePattern(lowerer, pattern, nsSource, isLet, out); + return; + } if (srcType.kind !== "record") { lowerer.unsupported( "SC1031", @@ -3200,7 +3260,7 @@ export function lowerBindingPattern( lowerer.bindPatternTarget(el.name, value, isLet, out); continue; } - if (!fieldType && isJsSourceFile(el.getSourceFile())) { + if (!fieldType && isJsSourceFile(ts.getSourceFileOfNode(el))) { // Inferred JavaScript shapes can omit optional own properties and // inherit values from their prototype. Read the live checked view. const loc = locOf(el); @@ -3334,10 +3394,73 @@ function patternBindingType(lowerer: Lowerer, name: ts.BindingName): IrType | nu const type = lowerer.typeOf(name); return ( lowerer.mapTypeOf(type) ?? - (isJsSourceFile(name.getSourceFile()) ? dynFallbackType(lowerer, name, type) : null) + (isJsSourceFile(ts.getSourceFileOfNode(name)) ? dynFallbackType(lowerer, name, type) : null) ); } +/** `const { a, b: c } = ns` / `const { f } = await import("./m")` over + * a program module's namespace: each element reads the export exactly + * like `ns.a` does — the member resolves to the exporter's own binding + * (nsMemberIdentOf's rule), read once at the pattern's position, so the + * bound name holds the export's value at that moment (destructuring + * copies; it does not alias the live binding). The namespace value + * itself was already evaluated into the pattern's temp, so its side + * effects (the dynamic import) keep their order. Rest elements would + * materialize the namespace object and keep their fence. The exports + * come from the source's module, not from the pattern's type: an + * annotation can name another module with a compatible shape. */ +function lowerModuleNamespacePattern( + lowerer: Lowerer, + pattern: ts.ObjectBindingPattern, + source: ts.SourceFile, + isLet: boolean, + out: IrStmt[], +): void { + const moduleSym = lowerer.checker.getSymbolAtLocation(source); + if (moduleSym === undefined) { + lowerer.unsupported( + "SC1031", + pattern, + "destructuring a module namespace without a module symbol", + ); + } + const nsType = lowerer.checker.getTypeOfSymbol(moduleSym); + for (const el of pattern.elements.nodes) { + if (el.name === undefined) continue; + if (el.dotDotDotToken) { + lowerer.unsupported( + "SC1031", + el, + "rest bindings over module namespaces (the namespace object is not materialized — bind the exports you need by name)", + ); + } + const prop = el.propertyName ?? el.name; + const propName = + ts.isIdentifier(prop) || + ts.isComputedPropertyName(prop) || + ts.isStringLiteralLike(prop) || + ts.isNumericLiteral(prop) + ? patternKeyNameOf(lowerer, prop as ts.PropertyName) + : null; + let exported = + propName === null ? undefined : lowerer.checker.getPropertyOfType(nsType, propName); + if (exported !== undefined && exported.flags & ts.SymbolFlags.Alias) { + exported = lowerer.checker.getAliasedSymbol(exported); + } + const declared = exported?.valueDeclaration?.name as ts.Node | undefined; + if (declared === undefined || !ts.isIdentifier(declared)) { + lowerer.unsupported( + "SC1031", + el, + "destructuring module namespace members that are not named value exports", + ); + } + let value = lowerer.lowerExpr(declared); + if (el.initializer) value = applyBindingDefault(lowerer, el, value); + lowerer.bindPatternTarget(el.name, value, isLet, out); + } +} + function patternKeyNameOf(lowerer: Lowerer, prop: ts.PropertyName): string | null { if (ts.isIdentifier(prop) || ts.isPrivateIdentifier(prop)) return prop.text; if (ts.isComputedPropertyName(prop)) return lowerer.foldedStringKeyOf(prop.expression); @@ -3364,7 +3487,7 @@ function isRegexMatchResultPattern(lowerer: Lowerer, pattern: ts.ObjectBindingPa const symbol = type.getAliasSymbol() ?? type.getSymbol(); return ( symbol !== undefined && - (symbol.name === "RegExpExecArray" || symbol.name === "RegExpMatchArray") && + (ts.symbolName(symbol) === "RegExpExecArray" || ts.symbolName(symbol) === "RegExpMatchArray") && lowerer.isStdlibSymbol(symbol) ); } @@ -3675,7 +3798,7 @@ function stdlibGlobalTokenDestructure( decl: ts.VariableDeclaration, isLet: boolean, ): IrStmt[] | null { - if (decl.initializer === undefined || !isJsSourceFile(decl.getSourceFile())) return null; + if (decl.initializer === undefined || !isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; const globalName = stdlibGlobalNameOf(lowerer, decl.initializer); if (globalName === null) return null; if (!ts.isObjectBindingPattern(decl.name)) { @@ -3751,9 +3874,12 @@ function stdlibGlobalTokenDestructure( `destructuring the non-builtin member '${propName}' of 'globalThis'`, ); } - const canonical = member.name === "global" ? "globalThis" : member.name; + const canonical = ts.symbolName(member) === "global" ? "globalThis" : ts.symbolName(member); token = `[builtin ${canonical}]`; alias = canonical; + // Builtin constructors read as their interned function values, the + // same values the bare and `globalThis.X` spellings read. + value = builtinGlobalValue(canonical, locOf(el), true); } else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) { token = `[builtin console.${propName}]`; } else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) { @@ -3797,6 +3923,10 @@ function stdlibGlobalTokenDestructure( const out: IrStmt[] = []; for (const b of binds) { const loc = locOf(b.name); + if (b.value !== undefined && b.alias !== null) { + const symbol = lowerer.checker.getSymbolAtLocation(b.name); + if (symbol) lowerer.stdlibGlobalAliases.set(symbol, b.alias); + } if (b.value !== undefined) { if (b.g !== undefined) { out.push({ @@ -4348,7 +4478,7 @@ function classInstanceRestValue( const deferOrderCheck = lowerer.instantiationContext !== null && lowerer.onEdge !== null && - !isJsSourceFile(blame.getSourceFile()); + !isJsSourceFile(ts.getSourceFileOfNode(blame)); if (!deferOrderCheck) { if (!orderMatches(emitOrder)) { lowerer.unsupported( @@ -4366,7 +4496,7 @@ function classInstanceRestValue( // matters. const context = lowerer.instantiationContext; const countFailure = !lowerer.suppressStats; - const sf = blame.getSourceFile(); + const sf = ts.getSourceFileOfNode(blame); lowerer.shapeOrderMetadataFinalizers.push(() => { const current = lowerer.shapes.get(restT.shapeId) ?? restShape; const currentOrder = current.declaredOrder ?? current.fields.map((f) => f.name); @@ -4627,7 +4757,7 @@ function inferredEvolvingLetType(lowerer: Lowerer, decl: ts.VariableDeclaration) if ( ts.isCallExpression(node.right) && (implicitMethodCallInfersReturn(lowerer, node.right) || - isJsSourceFile(node.right.getSourceFile())) + isJsSourceFile(ts.getSourceFileOfNode(node.right))) ) { invalid = true; return "stop"; @@ -4706,6 +4836,10 @@ export function lowerVarDecl( } } if (!ts.isIdentifier(decl.name)) lowerer.unsupported("SC1031", decl.name); + { + const alias = lowerTupleAliasDecl(lowerer, decl); + if (alias) return alias; + } // A NULLISH generic binding (`const i: I = null as any` — the // declared type has no mapping, the value is provably null/undefined @@ -4744,9 +4878,9 @@ export function lowerVarDecl( (decl.name.text === "module" || decl.name.text === "exports") && ts.isVariableStatement(decl.parent?.parent) && ts.isSourceFile(decl.parent?.parent?.parent) && - !isJsSourceFile(decl.getSourceFile()) && - !decl - .getSourceFile() + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && + !ts + .getSourceFileOfNode(decl) .statements.nodes.some( (s) => ts.isImportDeclaration(s) || @@ -4951,7 +5085,7 @@ export function lowerVarDecl( : undefined, ); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && raw.type.kind === "dyn" && @@ -5023,7 +5157,7 @@ export function lowerVarDecl( // immediately (the type covers the unassigned state), so they // initialize to the interned undefined arm like an omitted optional. let type = lowerer.irTypeOf(decl.name); - if (type.kind === "dyn" && isLet && isJsSourceFile(decl.getSourceFile())) { + if (type.kind === "dyn" && isLet && isJsSourceFile(ts.getSourceFileOfNode(decl))) { type = inferredEvolvingLetType(lowerer, decl) ?? type; } // `var v: void;` / `let x: undefined;` — a unit-only binding rides @@ -5050,7 +5184,7 @@ export function lowerVarDecl( // function (with its hint), not the unmappable declared type. If it // poisons, still declare the local when its type is representable, so // later references to this name don't produce cascading errors. - const inferredObjectType = isJsSourceFile(decl.getSourceFile()) + const inferredObjectType = isJsSourceFile(ts.getSourceFileOfNode(decl)) ? lowerer.mapTypeOf(lowerer.typeOf(decl.name)) : null; const inferredObjectArms = @@ -5067,7 +5201,7 @@ export function lowerVarDecl( inferredObjectArms.some( (arm) => arm.kind === "record" && jsObjectBindingExpands(lowerer, decl, arm), ) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && ts.isObjectLiteralExpression(decl.initializer) && @@ -5083,7 +5217,7 @@ export function lowerVarDecl( try { init = expandsObject || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && isNativeBuiltinValueInitializer(lowerer, decl.initializer)) ? lowerer.lowerExprExpecting(decl.initializer, DYN) @@ -5120,12 +5254,22 @@ export function lowerVarDecl( (init.type.kind === "moduleNs" ? init.type : null) ?? (init.type.kind === "dyn" ? DYN : null); if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && init.type.kind === "func" ) type = init.type; + // A typed-arguments function expression (typed-arguments.ts): the + // checker types the binding variadic, but its value has the fixed + // signature every use asserts. + if ( + !decl.type && + init.type.kind === "func" && + (lowerer.typedArgumentsLambdas.has(stripTypeWrappers(decl.initializer)) || + lowerer.nullishFuncResults.has(stripTypeWrappers(decl.initializer))) + ) + type = init.type; // A TypeScript lambda whose closure returns the runtime result (a missing // read, a `??` default of another type) instead of the checker's bare // return type: the binding keeps the checker's parameters and takes the @@ -5141,8 +5285,8 @@ export function lowerVarDecl( if ( lowerer.implicitParamTypes !== null && init.type.kind === "dyn" && - isJsSourceFile(decl.getSourceFile()) && - npmStaticPackageOfPath(decl.getSourceFile().fileName) !== null + isJsSourceFile(ts.getSourceFileOfNode(decl)) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(decl).fileName) !== null ) { type = DYN; } @@ -5150,17 +5294,17 @@ export function lowerVarDecl( init.type.kind === "dyn" && (inferredObjectType?.kind === "record" || inferredObjectType?.kind === "array" || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectArms.some((arm) => arm.kind === "record") && inferredObjectArms.every((arm) => isUnitType(arm) || arm.kind === "record")) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectType !== null && (inferredObjectType.kind === "date" || inferredObjectType.kind === "func" || inferredObjectType.kind === "object" || jsOpenObjectType(decl, inferredObjectType, lowerer.shapes, lowerer.unions).kind === "dyn")) || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && inferredObjectType !== null && (inferredObjectType.kind === "void" || isUnitType(inferredObjectType) || @@ -5178,14 +5322,14 @@ export function lowerVarDecl( expandsObject || preservesObjectIdentity || keepsAssertedObject || - (isJsSourceFile(decl.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && isNativeBuiltinValueInitializer(lowerer, decl.initializer)) ) { type = DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && init.type.kind === "array" && init.type.elem.kind === "dyn" && @@ -5201,14 +5345,14 @@ export function lowerVarDecl( // cannot hold the binding's future. The binding is checked-dynamic // instead (irTypeOf's JS story): writes dynFrom, typed exits dynCheck. // Const keeps the static empty record — no reassignment exists. - if (isLet && type?.kind === "record" && isJsSourceFile(decl.getSourceFile())) { + if (isLet && type?.kind === "record" && isJsSourceFile(ts.getSourceFileOfNode(decl))) { const shape = lowerer.shapes.get(type.shapeId); if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple) type = DYN; } if ( isLet && type !== null && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && (type.kind === "object" || type.kind === "symbol" || jsBindingHasOpenWrites(lowerer, decl)) @@ -5232,7 +5376,7 @@ export function lowerVarDecl( if ( returnedEmptyArrayType === null && (type === null || type.kind === "array") && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { let initExpr: ts.Expression = decl.initializer; while (ts.isParenthesizedExpression(initExpr)) initExpr = initExpr.expression; @@ -5295,7 +5439,7 @@ export function lowerVarDecl( if ( type?.kind === "func" && init.type.kind === "dyn" && - (isJsSourceFile(decl.getSourceFile()) || + (isJsSourceFile(ts.getSourceFileOfNode(decl)) || (!decl.type && !hasJsTypeAnnotation(decl) && type.rest === true && @@ -5304,7 +5448,7 @@ export function lowerVarDecl( type = DYN; } if ( - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !hasJsTypeAnnotation(decl) && init.type.kind === "dyn" && @@ -5331,7 +5475,7 @@ export function lowerVarDecl( // type (a union with a fence-folded arm — the typeof-'bigint' // dual-mode ternary) adopts the initializer's static type, the // same rule genuine `any` consts take. - (type === null && isJsSourceFile(decl.getSourceFile()))) && + (type === null && isJsSourceFile(ts.getSourceFileOfNode(decl)))) && init.type.kind !== "void" && init.type.kind !== "caught" && !isUnitType(init.type) @@ -5352,7 +5496,7 @@ export function lowerVarDecl( let settledType: IrType = type; if ( !isLet && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !hasJsTypeAnnotation(decl) && decl.initializer && ts.isNewExpression(decl.initializer) @@ -5463,6 +5607,23 @@ export function lowerVarDecl( settledType = init.type; } } + // `const view = route as Route & { order: number }`: the asserted type + // refines the record arms the value is stored in (the assertion keeps + // that value, see hosted-narrowing.ts). The binding keeps the stored + // type, so writes through it reach the asserted object. An annotation + // the stored type hosts (`const view: Route & { order: number } = ...`) + // keeps it too: coercing into the annotated layout would copy the + // value and read members the writes have not created yet. + if ( + !isLet && + decl.initializer && + isAssertionInitializer(decl.initializer) && + (settledType.kind === "union" || settledType.kind === "record") && + (init.type.kind === "union" || init.type.kind === "record") && + hostsAll(lowerer, init.type, settledType) + ) { + settledType = init.type; + } // An exact class behind a generic interface keeps its native // representation so generic calls can specialize against its body. if ( @@ -5607,7 +5768,7 @@ function jsObjectBindingExpands( decl.type || !decl.initializer || !ts.isIdentifier(decl.name) || - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return false; let initializer = decl.initializer; @@ -5717,7 +5878,10 @@ function jsObjectBindingExpands( export function hasJsTypeAnnotation(decl: ts.VariableDeclaration): boolean { const statement = decl.parent?.parent; return ( - !!statement && /@type\b/.test(decl.getSourceFile().text.slice(statement.pos, decl.getStart())) + !!statement && + /@type\b/.test( + ts.getSourceFileOfNode(decl).text.slice(statement.pos, ts.tokenStartOfNode(decl)), + ) ); } @@ -5725,7 +5889,7 @@ export function hasJsTypeAnnotation(decl: ts.VariableDeclaration): boolean { * an any-valued callback. Keep those bindings open, including captured writes. */ export function jsBindingHasOpenWrites(lowerer: Lowerer, decl: ts.VariableDeclaration): boolean { if ( - !isJsSourceFile(decl.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(decl)) || decl.type || hasJsTypeAnnotation(decl) || !ts.isIdentifier(decl.name) @@ -6140,35 +6304,6 @@ export function lowerTry(lowerer: Lowerer, stmt: ts.TryStatement): IrStmt { }; } -function hasUseStrictDirective(statements: readonly ts.Statement[]): boolean { - for (const statement of statements) { - if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) - return false; - if (statement.expression.text === "use strict") return true; - } - return false; -} - -function isStrictDelete(node: ts.DeleteExpression): boolean { - for (let parent: ts.Node | undefined = node.parent; parent; parent = parent.parent) { - if (ts.isClassDeclaration(parent) || ts.isClassExpression(parent)) return true; - if ( - ts.isFunctionLike(parent) && - parent.body && - ts.isBlock(parent.body) && - hasUseStrictDirective(parent.body.statements.nodes) - ) - return true; - if (ts.isSourceFile(parent)) { - return ( - hasUseStrictDirective(parent.statements.nodes) || - (!isCjsJsFile(parent) && (isNodeEsmFile(parent) || ts.isExternalModule(parent))) - ); - } - } - return false; -} - /** Statement-position `delete`: process.env keys → process.envUnset * (unsetenv), pure `Record` keys → recordKeyDelete (the * overflow Map delete), declared OPTIONAL fields → the field's ABSENT @@ -6256,7 +6391,7 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt } if (accessor && errorBase) return { kind: "exprStmt", expr: obj, loc }; } - if (isJsSourceFile(expr.getSourceFile()) && obj.type.kind === "object") { + if (isJsSourceFile(ts.getSourceFileOfNode(expr)) && obj.type.kind === "object") { const info = lowerer.classes.get(obj.type.className); const keyType = ts.isElementAccessExpression(target) ? lowerer.typeOf(target.argumentExpression) @@ -6277,14 +6412,14 @@ function lowerDeleteStatement(lowerer: Lowerer, expr: ts.DeleteExpression): IrSt } } if ( - isJsSourceFile(expr.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && obj.type.kind === "func" && lowerer.dynConvertible(obj.type) ) { obj = lowerer.coerceToExpected(obj, DYN); } if (obj.type.kind === "dyn") { - const strict = { kind: "boolLit", value: isStrictDelete(expr), type: BOOL, loc } as const; + const strict = { kind: "boolLit", value: isStrictCode(expr), type: BOOL, loc } as const; if (ts.isElementAccessExpression(target)) { const key = lowerer.lowerExprExpecting(target.argumentExpression, DYN); return { @@ -6388,7 +6523,7 @@ function lowerCjsExportStatement( const symbol = lowerer.checker.getSymbolAtLocation(nameNode) ?? (ts.isIdentifier(nameNode) || ts.isPrivateIdentifier(nameNode) - ? lowerer.cjsModuleExportSymbol(cjs.expr.getSourceFile(), nameNode.text) + ? lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(cjs.expr), nameNode.text) : undefined); const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; if (!g) { @@ -6447,7 +6582,10 @@ function lowerCjsExportStatement( // storage — collectGlobals skipped it; importers re-resolve through // cjsMemberExportClassSymbol): the statement lowers to nothing. if (lowerer.cjsMemberExportClassSymbol(cjs.expr) !== null) { - return { kind: "block", body: [], loc }; + // An inline require still evaluates its module here. + const req = inlineProgramRequireOf(lowerer, cjs.value); + const init = req !== null ? lowerer.requireInitStmt(req.spec, req.call) : null; + return init ?? { kind: "block", body: [], loc }; } // The tsc VOID-INIT PREAMBLE (`exports.leaf = void 0;` with the real // `exports.leaf = leaf;` further down — every tsc-emitted dist file @@ -6460,7 +6598,7 @@ function lowerCjsExportStatement( // later real assignment keep the ordinary path (the export IS // undefined then). if (cjsVoidInitPreambleReals(cjs) !== null) return { kind: "block", body: [], loc }; - const sf = cjs.expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(cjs.expr); let root: IrGlobal | undefined; for (const stmt of sf.statements.nodes) { const whole = cjsExportAssignmentOf(stmt); @@ -6523,7 +6661,7 @@ function lowerCjsExportStatement( // were already evaluated by the const's own declaration, so this // statement may not RE-evaluate anything: expression-valued entries // (which the direct-table lowering assigns here) keep a fence. - tableObj = cjsExportTargetLiteral(cjs.expr.right, cjs.expr.getSourceFile()); + tableObj = cjsExportTargetLiteral(cjs.expr.right, ts.getSourceFileOfNode(cjs.expr)); resolvedTarget = tableObj !== null; if (!tableObj) { const single = lowerCjsSingleValueExport(lowerer, cjs.expr, loc); @@ -6585,7 +6723,7 @@ function lowerCjsExportStatement( lowerer.rejectThisInObjectMethod(method.body); const symbol = lowerer.checker.getSymbolAtLocation(method.name) ?? - lowerer.cjsModuleExportSymbol(cjs.expr.getSourceFile(), method.name.text); + lowerer.cjsModuleExportSymbol(ts.getSourceFileOfNode(cjs.expr), method.name.text); const g = symbol ? lowerer.globalsBySymbol.get(symbol) : undefined; if (g) { const fn = lowerer.lowerLambda(method); @@ -6860,7 +6998,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm // global — the expression path owns it (lowerCreateRequireCall). createRequireCalleeFileOf(lowerer, expr.expression) === null ) { - const sf = expr.getSourceFile(); + const sf = ts.getSourceFileOfNode(expr); const spec = requireSpecOf(expr)!; if (lowerer.externalTypes.has(spec)) lowerer.externalHostFence(spec, expr, false); // A bare require of a package NOTHING INSTALLED resolves, in ANY @@ -6900,6 +7038,25 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm "require() outside the module's top level (move it to the top of the file)", ); } + // Replacing the export object (or rebinding `exports`) anywhere but a + // top-level statement makes the module's exports depend on which path + // ran (`cond ? module.exports = a : module.exports = b`, an + // environment-dependent branch); the static export table has one shape. + if ( + !topLevelJs && + isJsSourceFile(ts.getSourceFileOfNode(expr)) && + ts.isBinaryExpression(expr) && + expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && + (isCjsModuleExports(lowerer, expr.left) || isCjsExportsIdentifier(lowerer, expr.left)) + ) { + lowerer.unsupported( + "SC1090", + expr.left, + isCjsModuleExports(lowerer, expr.left) + ? "replacing module.exports outside a top-level statement (a conditional or environment-dependent export object)" + : "rebinding 'exports' outside a top-level statement (a conditional or environment-dependent export object)", + ); + } if (topLevelJs && ts.isExpressionStatement(stmtNode)) { if (isFreshExportsReset(lowerer, stmtNode)) return { kind: "block", body: [], loc: locOf(expr) }; @@ -6908,12 +7065,13 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm if ( ts.isBinaryExpression(expr) && expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && - isCjsExportsIdentifier(lowerer, expr.left) + isCjsExportsIdentifier(lowerer, expr.left) && + cjsExportAssignmentOf(stmtNode)?.kind !== "table" ) { 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", + "rebinding 'exports' without module.exports (exports = value): members attached through 'exports' afterwards are properties of that value, which module.exports does not hold — write exports = module.exports = value", ); } const cjs = cjsExportAssignmentOf(stmtNode); @@ -7266,9 +7424,12 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm ) { const loc = locOf(expr); const value = lowerer.lowerExprExpecting(expr.right, F64); + const fn = programReadsExitCode(lowerer) + ? "process.setExitCodeTracked" + : "process.setExitCode"; return { kind: "exprStmt", - expr: { kind: "libCall", fn: "process.setExitCode", args: [value], type: VOID, loc }, + expr: { kind: "libCall", fn, args: [value], type: VOID, loc }, loc, }; } @@ -7387,7 +7548,7 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm if ( recv && (recv.type.kind === "dyn" || - (isJsSourceFile(expr.getSourceFile()) && + (isJsSourceFile(ts.getSourceFileOfNode(expr)) && recv.type.kind === "union" && lowerer.dynConvertible(recv.type))) ) { @@ -7408,6 +7569,34 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm loc, }; } + // `err.code = v` on an Error value (`err as Error & { code?: + // string }`): typed reads come from the runtime error's code + // slot, so the write lands there. A value that is not statically + // a string checks at runtime: strings take the slot, anything + // else clears it and stays a dynamic own property. The + // system-error members (errno, syscall, path, dest) read + // through the checked-dynamic node and keep the dynamic write, as + // do JavaScript files, whose reads use that node too. + if ( + !isJsSourceFile(ts.getSourceFileOfNode(expr)) && + recv?.type.kind === "object" && + lowerer.isErrorRootedClass(recv.type.className) && + expr.left.name.text === "code" + ) { + const loc = locOf(expr); + const stringValue = isStringTyped(lowerer.typeOf(expr.right)); + return { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: stringValue ? "error.setCode" : "error.setCodeDyn", + args: [recv, lowerer.lowerExprExpecting(expr.right, stringValue ? STRING : DYN)], + type: VOID, + loc, + }, + loc, + }; + } if (recv?.type.kind === "object" && recv.type.className === "%Error") { const loc = locOf(expr); return { @@ -7461,6 +7650,31 @@ export function lowerExprStatement(lowerer: Lowerer, expr: ts.Expression): IrStm const recvIr = lowerer.mapTypeOf(lowerer.typeOf(expr.left.expression)); if (recvIr?.kind === "record") { const shape = lowerer.shapes.get(recvIr.shapeId); + if ( + shape?.indexValue && + shape.fields.length === 0 && + declaredMemberOf(lowerer, expr.left.expression, expr.left.name.text) + ) { + // A declared member of a literal stored as a pure index-signature + // record (dictSpreadLiteralShape): a keyed write of that key. + const loc = locOf(expr); + const obj = lowerer.lowerExpr(expr.left.expression); + if (obj.type.kind === "record") { + const value = lowerer.intoIndexValueSlot( + lowerer.lowerExprExpecting(expr.right, shape.indexValue), + shape.indexValue, + expr.right, + ); + return { + kind: "recordKeySet", + obj, + shapeId: obj.type.shapeId, + key: { kind: "strLit", value: expr.left.name.text, type: STRING, loc }, + value, + loc, + }; + } + } if ( shape?.indexValue && !shape.fields.some( @@ -7637,8 +7851,8 @@ function lowerDiscardedLogical(lowerer: Lowerer, expr: ts.BinaryExpression): IrS // picks another lowering, exactly as before. if (!(e instanceof PoisonError) || lowerer.diagSink !== null) throw e; const added = lowerer.diags.slice(diagsBefore); - const file = expr.getSourceFile().fileName; - const start = expr.getStart(); + const file = ts.getSourceFileOfNode(expr).fileName; + const start = ts.tokenStartOfNode(expr); const local = added.length > 0 && added.every( @@ -7788,16 +8002,42 @@ export function lowerDestructuringAssign( /** V8's spelling of a destructuring RHS in its TypeError message * ("Cannot destructure 'a' as it is undefined.") — plain identifiers - * spell their name (parens unwrap), and calls append an ellipsis to a - * supported callee spelling. Other CallPrinter forms keep a fence. */ + * spell their name (parens unwrap), `this`, property chains and + * identifier or numeric element reads spell their source, awaited and + * yielded values spell "(intermediate value)", and calls append an + * ellipsis to a supported callee spelling. Other CallPrinter forms keep + * a fence. */ function destrSpellingOf(rhs: ts.Expression): string | null { let e = rhs; - while (ts.isParenthesizedExpression(e)) e = e.expression; + // Parentheses and the type-only wrappers are not part of the emitted + // JavaScript expression V8 prints. + while ( + ts.isParenthesizedExpression(e) || + ts.isAsExpression(e) || + ts.isSatisfiesExpression(e) || + ts.isTypeAssertion(e) || + ts.isNonNullExpression(e) + ) { + e = e.expression; + } if (ts.isIdentifier(e)) return e.text; + if (e.kind === ts.SyntaxKind.ThisKeyword) return "this"; + // V8 prints an awaited or yielded operand as "(intermediate value)". + if (ts.isAwaitExpression(e) || ts.isYieldExpression(e)) return "(intermediate value)"; if (ts.isPropertyAccessExpression(e) && !e.questionDotToken) { const base = destrSpellingOf(e.expression); return base === null ? null : `${base}.${e.name.text}`; } + // `table[key]` and `list[0]`; V8 prints a numeric key in its canonical + // number form (`o[1.50]` reads "o[1.5]"). + if (ts.isElementAccessExpression(e) && !e.questionDotToken) { + const base = destrSpellingOf(e.expression); + const key = e.argumentExpression; + if (base === null) return null; + if (ts.isIdentifier(key)) return `${base}[${key.text}]`; + if (ts.isNumericLiteral(key)) return `${base}[${String(Number(key.text))}]`; + return null; + } if (ts.isCallExpression(e) && !e.questionDotToken) { const callee = destrSpellingOf(e.expression); return callee === null ? null : `${callee}(...)`; @@ -7883,7 +8123,7 @@ function destructuringAssignInto( const patternEmpty = ts.isObjectLiteralExpression(target) ? target.properties.nodes.length === 0 : target.elements.nodes.length === 0; - if (!patternEmpty && rhs !== null && isJsSourceFile(rhs.getSourceFile())) { + if (!patternEmpty && rhs !== null && isJsSourceFile(ts.getSourceFileOfNode(rhs))) { const globalName = stdlibGlobalNameOf(lowerer, rhs); if (globalName !== null) { lowerer.unsupported( @@ -8900,8 +9140,15 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { ) { return lowerForAwaitStdin(lowerer, stmt); } + // A checked-dynamic NodeJS.ReadableStream iterates its native stream. + const dynStream = lowerForAwaitDynStream(lowerer, stmt, () => + lowerForAwaitBuiltin(lowerer, stmt), + ); + if (dynStream) return dynStream; const builtin = lowerForAwaitBuiltin(lowerer, stmt); if (builtin) return builtin; + // A NodeJS.ReadableStream iterates as the general Readable. + requestGeneralStreamSurface(lowerer, stmt.expression); lowerer.unsupported( "SC1070", stmt, @@ -9135,6 +9382,16 @@ export function lowerForOf(lowerer: Lowerer, stmt: ts.ForOfStatement): IrStmt { ); } if (iterable.type.kind === "object") { + // A [Symbol.iterator] generator method: the loop drives the + // generator it returns. + const generator = classGeneratorIterable(lowerer, iterable); + if (generator) + return lowerForOfGenerator( + lowerer, + stmt, + generator as IrExpr & { type: IrType & { kind: "generator" } }, + labels, + ); // Class iterables ([Symbol.iterator]() + next() — classIteratorOf) // drive the same protocol with ordinary method calls. const cit = lowerer.classIteratorOf(iterable.type); @@ -10661,7 +10918,7 @@ function lowerForIn(lowerer: Lowerer, stmt: ts.ForInStatement): IrStmt { while (root?.base) root = root.base; if ( info?.decl && - isJsSourceFile(info.decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(info.decl)) && !info.def.runtime && !root?.def.runtime && !info.builtinError && @@ -11190,3 +11447,144 @@ export function stdlibBufferVarAlias(lowerer: Lowerer, decl: ts.VariableDeclarat requireEarlyReference(lowerer.program, decl) === null ); } + +/** A checker type that is a string or string literal (or a union of them). */ +function isStringTyped(t: ts.Type): boolean { + if (t.isUnionType()) return ts.constituentTypes(t).every(isStringTyped); + return (t.flags & (ts.TypeFlags.String | ts.TypeFlags.StringLiteral)) !== 0; +} + +/** The Error-subclass prototype reset of transpiled code, inside a class + * constructor: + * + * const proto = new.target.prototype; + * if (Object.setPrototypeOf) Object.setPrototypeOf(this, proto); + * else this.__proto__ = proto; + * + * (also `Object.setPrototypeOf(this, new.target.prototype)` and + * `Object.setPrototypeOf(this, C.prototype)` in C's own constructor when no + * class extends C). A compiled instance already has exactly that prototype, + * so each statement is a no-op. True for those statements. */ +function ownPrototypeResetStatement(lowerer: Lowerer, stmt: ts.Statement): boolean { + if (ts.isVariableStatement(stmt)) { + const decls = stmt.declarationList.declarations.nodes; + if (decls.length !== 1 || (stmt.declarationList.flags & ts.NodeFlags.Const) === 0) return false; + const decl = decls[0]!; + if (!ts.isIdentifier(decl.name) || !decl.initializer) return false; + const ctor = enclosingConstructor(decl); + if (!ctor || !ownPrototypeRef(lowerer, decl.initializer, ctor)) return false; + const symbol = lowerer.checker.getSymbolAtLocation(decl.name); + if (!symbol) return false; + // Every other use of the binding must be one of the reset statements. + let onlyResets = true; + ts.walkPreorder(ctor, (n) => { + if (!onlyResets) return "stop"; + if (!ts.isIdentifier(n) || n === decl.name) return undefined; + if (lowerer.checker.getSymbolAtLocation(n) !== symbol) return undefined; + const statement = n.parent?.parent; + if ( + !statement || + !ts.isExpressionStatement(statement) || + resetTarget(lowerer, statement.expression) !== n + ) + onlyResets = false; + return undefined; + }); + return onlyResets; + } + if (!ts.isExpressionStatement(stmt)) return false; + const ctor = enclosingConstructor(stmt); + if (!ctor) return false; + const target = resetTarget(lowerer, stmt.expression); + return target !== null && ownPrototypeRef(lowerer, target, ctor); +} + +/** The prototype operand of `Object.setPrototypeOf(this, P)` or + * `this.__proto__ = P`, or null. */ +function resetTarget(lowerer: Lowerer, expr: ts.Expression): ts.Expression | null { + if ( + ts.isCallExpression(expr) && + expr.arguments.nodes.length === 2 && + expr.arguments.nodes[0]!.kind === ts.SyntaxKind.ThisKeyword && + ts.isPropertyAccessExpression(expr.expression) && + expr.expression.name.text === "setPrototypeOf" && + lowerer.isStdlibGlobal(expr.expression.expression, "Object") + ) + return expr.arguments.nodes[1]!; + if ( + ts.isBinaryExpression(expr) && + expr.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(expr.left) && + expr.left.expression.kind === ts.SyntaxKind.ThisKeyword && + expr.left.name.text === "__proto__" + ) + return expr.right; + return null; +} + +/** The class constructor whose body holds `node`, crossing only statements + * and blocks (any nested function rebinds `this`). */ +function enclosingConstructor(node: ts.Node): ts.ConstructorDeclaration | null { + for (let cur = node.parent; cur !== undefined; cur = cur.parent) { + if (ts.isConstructorDeclaration(cur)) return cur; + if ( + ts.isFunctionLike(cur) || + ts.isClassDeclaration(cur) || + ts.isClassExpression(cur) || + ts.isSourceFile(cur) + ) + return null; + } + return null; +} + +/** Whether `node` denotes the prototype of the object `ctor` constructs: + * `new.target.prototype`, a const holding it, or the class's own + * `C.prototype` when no class extends C. */ +function ownPrototypeRef( + lowerer: Lowerer, + node: ts.Expression, + ctor: ts.ConstructorDeclaration, +): boolean { + while (ts.isParenthesizedExpression(node)) node = node.expression; + if (ts.isPropertyAccessExpression(node) && node.name.text === "prototype") { + const base = node.expression; + if ( + ts.isMetaProperty(base) && + base.keywordToken === ts.SyntaxKind.NewKeyword && + base.name.text === "target" + ) + return true; + const cls = ctor.parent; + if (!ts.isIdentifier(base) || !(ts.isClassDeclaration(cls) || ts.isClassExpression(cls))) + return false; + const symbol = lowerer.resolveValueSymbol(base); + const info = symbol ? lowerer.classBySymbol.get(symbol) : undefined; + if (!info || symbol!.valueDeclaration !== cls) return false; + for (const other of lowerer.classes.values()) { + if (other !== info && lowerer.isSubclassOf(other.def.name, info.def.name)) return false; + } + return true; + } + if (ts.isIdentifier(node)) { + const symbol = lowerer.resolveValueSymbol(node); + const decl = symbol?.valueDeclaration; + return ( + decl !== undefined && + ts.isVariableDeclaration(decl) && + decl.initializer !== undefined && + ts.isVariableDeclarationList(decl.parent) && + (decl.parent.flags & ts.NodeFlags.Const) !== 0 && + enclosingConstructor(decl) === ctor && + !ts.isIdentifier(decl.initializer) && + ownPrototypeRef(lowerer, decl.initializer, ctor) + ); + } + return false; +} + +function isAssertionInitializer(node: ts.Expression): boolean { + let e = node; + while (ts.isParenthesizedExpression(e)) e = e.expression; + return ts.isAsExpression(e) || ts.isTypeAssertion(e); +} diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 5769e1e0a..b9add95a6 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -49,6 +49,8 @@ import { sideEffectFreeOptionValue } from "./surfaces.js"; import { lowerDynObjectLiteral } from "./expressions/object-literals.js"; import { dynUndefinedExpr, varRef } from "../../ir/build.js"; import { genResultRecord } from "../type-mapper.js"; +import { noteUntypedStreamFlow, requestGeneralStreamSurface } from "./general-streams.js"; +import { dynNativeBranch } from "./dyn-streams.js"; import { extractIteratorValue, genStep, genValue, stepDrivable } from "./lower-generators.js"; import { BOOL, @@ -169,6 +171,16 @@ export const UNDERSCORE_METHODS: ReadonlyMap = new Map([ ["flush", "_flush"], ]); +/** The completion callback's parameter position in each underscore + * method that takes one (cbTuples' "fn" positions). */ +export const STREAM_COMPLETION_CALLBACK_INDEX: ReadonlyMap = new Map([ + ["_write", 2], + ["_final", 0], + ["_destroy", 1], + ["_transform", 2], + ["_flush", 0], +]); + /** The accepted option-callback names (canonical flag order) and the * duplex-shaped-head answer for one runtime stream class. */ export function streamCtorShape(cls: string): { @@ -586,6 +598,33 @@ function dynOptionsValue(lowerer: Lowerer, arg: ts.Expression): IrExpr | null { return v.type.kind === "dyn" ? v : null; } +/** @types/node declares the transform/flush completion callback as + * `(error?: Error | null, data?: any) => void`. Outside objectMode Node + * accepts only a Buffer, a string or nothing as `data`, so the callback + * parameter is typed with that union while the option callback lowers: a + * checked-dynamic value passed as `data` converts at the call. Returns the + * pinned parameter symbol (the caller unpins it), or null when the + * declared type needs no pin. */ +function pinAnyDataCompletion( + lowerer: Lowerer, + which: string, + fnNode: ts.MethodDeclaration | ts.FunctionExpression | ts.ArrowFunction, +): ts.Symbol | null { + const index = which === "transform" ? 2 : which === "flush" ? 0 : -1; + const param = index < 0 ? undefined : fnNode.parameters.nodes[index]; + if (param === undefined || !ts.isIdentifier(param.name) || param.dotDotDotToken) return null; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (!symbol || lowerer.stageSourceTypes.has(symbol)) return null; + const declared = lowerer.paramShape(param).type; + if (declared.kind !== "func" || declared.rest || declared.params[1]?.kind !== "dyn") return null; + const data = unionOf(lowerer, [BYTES, STRING, { kind: "undefinedT" }]); + lowerer.stageSourceTypes.set( + symbol, + funcOf([declared.params[0]!, data, ...declared.params.slice(2)], declared.ret), + ); + return symbol; +} + /** Parses the options object literal of a stream constructor. `which` * names the callbacks this class accepts, in canonical flag order. */ function parseStreamOptions( @@ -671,22 +710,27 @@ function parseStreamOptions( continue; } const full = tuples[name]!.map((t) => (t === "fn" ? null : t)); - // The completion-callback tail: typed by the checker contextually; - // accept the user's declared func type as the tuple's entry so the - // prefix check compares like with like. - const { funcType } = lowerer.lambdaSignature(fnNode); - const tuple: IrType[] = full.map((t, i) => { - if (t !== null) return t; - const declared = funcType.params[i]; - if (declared !== undefined && declared.kind !== "func" && declared.kind !== "dyn") { - lowerer.noLowering( - `a '${name}' callback whose parameter ${i} is '${lowerer.fmt(declared)}' where Node passes the completion callback`, - fnNode!, - ); - } - return declared ?? funcOf([], VOID); - }); - present.set(name, lowerStreamCallback(lowerer, name, fnNode, thisType, tuple)); + const pinned = pinAnyDataCompletion(lowerer, name, fnNode); + try { + // The completion-callback tail: typed by the checker contextually; + // accept the user's declared func type as the tuple's entry so the + // prefix check compares like with like. + const { funcType } = lowerer.lambdaSignature(fnNode); + const tuple: IrType[] = full.map((t, i) => { + if (t !== null) return t; + const declared = funcType.params[i]; + if (declared !== undefined && declared.kind !== "func" && declared.kind !== "dyn") { + lowerer.noLowering( + `a '${name}' callback whose parameter ${i} is '${lowerer.fmt(declared)}' where Node passes the completion callback`, + fnNode!, + ); + } + return declared ?? funcOf([], VOID); + }); + present.set(name, lowerStreamCallback(lowerer, name, fnNode, thisType, tuple)); + } finally { + if (pinned) lowerer.stageSourceTypes.delete(pinned); + } continue; } // `{ signal }` shorthand: the binding is the value. @@ -999,6 +1043,15 @@ export function lowerStreamNew(lowerer: Lowerer, expr: ts.NewExpression, info: C }; } +/** True when a value of Node's argument type `passed` widens implicitly + * into the declared parameter type `declared` (one of its union arms, or a + * sub-union of it). */ +function widensInto(lowerer: Lowerer, passed: IrType, declared: IrType, loc: SrcLoc): boolean { + if (declared.kind !== "union") return false; + const probe: IrExpr = { kind: "varRef", localId: "%probe", type: passed, loc }; + return typeEquals(lowerer.coerceToExpected(probe, declared).type, declared); +} + /* ── user subclasses (`extends Readable`, the underscore-override form) ── */ /** The wrapper closure binding one overridden underscore method as the @@ -1075,10 +1128,17 @@ function streamMethodWrapper( ); } } else if (!typeEquals(got.type, want)) { + // A declared supertype of what Node passes (`chunk: Buffer | string`) + // is sound: the wrapper takes Node's value and widens it into the + // declared parameter. + if (widensInto(lowerer, want, got.type, loc)) { + paramTypes.push(want); + continue; + } lowerer.noLowering( `a '${methodName}' method whose parameter ${i} is '${lowerer.fmt(got.type)}' where Node passes '${lowerer.fmt(want)}'`, blame, - "annotate the parameter with the Node signature's type (chunks are Buffers here)", + "annotate the parameter with the Node signature's type or a supertype of it (chunks are Buffers here)", ); } paramTypes.push(got.type); @@ -1094,7 +1154,11 @@ function streamMethodWrapper( for (let i = 0; i < paramTypes.length; i++) { const local = lowerer.declareHiddenLocal(`a${i}`, paramTypes[i]!); params.push({ localId: local.id, name: local.name, type: paramTypes[i]! }); - argRefs.push({ kind: "varRef", localId: local.id, type: paramTypes[i]!, loc }); + const arg: IrExpr = { kind: "varRef", localId: local.id, type: paramTypes[i]!, loc }; + const declaredType = declared[i]!.type; + argRefs.push( + typeEquals(arg.type, declaredType) ? arg : lowerer.coerceToExpected(arg, declaredType), + ); } const thisRef: IrExpr = { kind: "varRef", localId: thisLocal.id, type: thisType, loc }; let call: IrExpr; @@ -1411,7 +1475,7 @@ export function lowerStreamStaticCall( member === "toWeb" && cls === "%Readable" && call.arguments.nodes.length === 2 && - isJsSourceFile(call.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(call)) && ts.isObjectLiteralExpression(call.arguments.nodes[1]!) ) { for (const p of call.arguments.nodes[1]!.properties.nodes) { @@ -1445,7 +1509,7 @@ export function lowerStreamStaticCall( ); } const type: IrType = { kind: "object", className: "%Readable" }; - const js = isJsSourceFile(call.getSourceFile()); + const js = isJsSourceFile(ts.getSourceFileOfNode(call)); const srcNode = args[0]; const optsNode = args[1]; const srcT = srcNode ? lowerer.mapTypeOf(lowerer.typeOf(srcNode)) : null; @@ -2024,6 +2088,27 @@ function lowerStreamArg( const v = isHashValueType(lowered.type) ? hashStreamOf(lowered, what === "pipeline" || what === "compose") : lowered; + // A checked-dynamic pipeline stage (an untyped source or destination): + // a boxed native stream unwraps at run time; other values (iterables, + // functions) throw the not-supported Error there. + if (v.type.kind === "dyn" && what === "pipeline") { + noteUntypedStreamFlow(lowerer, null); + return { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + v, + strLit( + lowerer.dynNodeStreams + ? "pipeline stages other than native streams (JavaScript stream objects, iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet" + : "pipeline stages other than native streams (iterables, generators, plain functions, and { readable, writable } pairs) are not supported yet", + locOf(node), + ), + ], + type: { kind: "object", className: "%Duplex" }, + loc: locOf(node), + }; + } const info = v.type.kind === "object" ? lowerer.classes.get(v.type.className) : undefined; if (!streamSidesOf(lowerer, info)) { // A provably-non-stream value in a JS source (the invalid-input @@ -2042,7 +2127,7 @@ function lowerStreamArg( loc: locOf(node), }); }; - if (isJsSourceFile(node.getSourceFile()) && lowerer.dynConvertible(v.type)) { + if (isJsSourceFile(ts.getSourceFileOfNode(node)) && lowerer.dynConvertible(v.type)) { if (what === "pipeline") { // Strings and arrays are iterables — VALID pipeline stages in // Node — and a record carrying a readable/writable member may be @@ -3056,7 +3141,7 @@ function lowerOperatorCallback( if (v.type.kind === "dyn") return v; if ( v.type.kind !== "func" && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && lowerer.dynConvertible(v.type) ) { // JavaScript passes any value; Node's validateFunction throws. @@ -3276,6 +3361,235 @@ function pipeEndOption(lowerer: Lowerer, options: ts.Expression | undefined, loc return end; } +/** `readable.pipe(process.stdout)`: the process stream joins the pipe as + * an fd-backed Writable (stream.stdioWritable), and — like Node, which + * never ends process.stdout or process.stderr from a pipe — the source's + * end does not end it. pipe() answers the process stream itself. */ +function pipeIntoStdio( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + target: IrExpr, +): IrExpr { + const loc = locOf(call); + // JS order: the receiver, then the destination, then the options. + const receiverValue = lowerer.lowerExpr(access.expression); + const source = lowerer.declareHiddenLocal("%pipeSource", receiverValue.type); + const stdio = lowerer.declareHiddenLocal("%pipeStdio", target.type); + const options = call.arguments.nodes[1]; + const stmts: IrStmt[] = [ + { kind: "varDecl", localId: source.id, init: receiverValue, loc }, + { kind: "varDecl", localId: stdio.id, init: target, loc }, + ]; + // The options literal still evaluates; its end flag has no effect here. + if (options !== undefined) { + stmts.push({ kind: "exprStmt", expr: pipeEndOption(lowerer, options, loc), loc }); + } + const receiver: IrExpr = { kind: "varRef", localId: source.id, type: receiverValue.type, loc }; + const writable: IrType = { kind: "object", className: "%Writable" }; + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "readable.pipe", + args: [ + receiver, + { + kind: "libCall", + fn: "stream.stdioWritable", + args: [{ kind: "varRef", localId: stdio.id, type: target.type, loc }], + type: writable, + loc, + }, + boolLit(false, loc), + ], + type: writable, + loc, + }, + loc, + }); + return { + kind: "seqExpr", + stmts, + result: { kind: "varRef", localId: stdio.id, type: target.type, loc }, + type: target.type, + loc, + }; +} + +/** `readable.unpipe(untyped)`: a native destination (boxed) unpipes as a + * typed one does; any other value unpipes the JavaScript destination a + * pipe into it registered (scr_stream_dyn.c), or does nothing when there + * is none, as Node's unpipe does for a destination it is not piped into. + * The call answers the source. */ +function unpipeDyn(lowerer: Lowerer, receiver: IrExpr, dst: IrExpr, loc: SrcLoc): IrExpr { + noteUntypedStreamFlow(lowerer, null); + const source = lowerer.declareHiddenLocal("%unpipeSource", receiver.type); + const destination = lowerer.declareHiddenLocal("%unpipeDestination", dst.type); + const sourceRef = varRef(source.id, receiver.type, loc); + const destinationRef = varRef(destination.id, dst.type, loc); + const stmts: IrStmt[] = [ + { kind: "varDecl", localId: source.id, init: receiver, loc }, + { kind: "varDecl", localId: destination.id, init: dst, loc }, + ]; + const unpipeNative = (native: IrExpr): IrStmt => ({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "readable.unpipe", + args: [sourceRef, native], + type: receiver.type, + loc, + }, + loc, + }); + if (lowerer.dynNodeStreams) { + stmts.push( + dynNativeBranch(lowerer, destinationRef, "w", unpipeNative, { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "stream.unpipeDyn", + args: [sourceRef, destinationRef], + type: VOID, + loc, + }, + loc, + }), + ); + } else { + stmts.push( + unpipeNative({ + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + destinationRef, + strLit("unpipe destinations other than native streams are not supported yet", loc), + ], + type: { kind: "object", className: "%Duplex" }, + loc, + }), + ); + } + return { kind: "seqExpr", stmts, result: sourceRef, type: receiver.type, loc }; +} + +/** The pipe() libCall for a destination of type `dst`. Under the general + * stream representation a Writable slot can hold the converted + * process.stdout / process.stderr, which a pipe never ends (as in Node), so + * the pipe takes readable.pipeGeneral, which checks for them. */ +function generalPipe(lowerer: Lowerer, dst: IrType): "readable.pipe" | "readable.pipeGeneral" { + return lowerer.generalNodeStreams && dst.kind === "object" && dst.className === "%Writable" + ? "readable.pipeGeneral" + : "readable.pipe"; +} + +/** `readable.pipe(untyped)`: a checked-dynamic destination unwraps a boxed + * native stream at run time (stream.unboxDyn; other values throw the + * not-supported Error), and pipe() answers the destination value itself. */ +function pipeIntoDyn( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, + target: IrExpr, +): IrExpr { + const loc = locOf(call); + noteUntypedStreamFlow(lowerer, null); + const receiverValue = lowerer.lowerExpr(access.expression); + const source = lowerer.declareHiddenLocal("%pipeSource", receiverValue.type); + const destination = lowerer.declareHiddenLocal("%pipeDestination", target.type); + const stmts: IrStmt[] = [ + { kind: "varDecl", localId: source.id, init: receiverValue, loc }, + { kind: "varDecl", localId: destination.id, init: target, loc }, + ]; + const end = pipeEndOption(lowerer, call.arguments.nodes[1], loc); + const duplex: IrType = { kind: "object", className: "%Duplex" }; + // Checked-dynamic stream slots (dyn-streams.ts): a JavaScript stream + // object is a destination too, piped with Node's listener protocol. + if (lowerer.dynNodeStreams) { + const sourceRef: IrExpr = { kind: "varRef", localId: source.id, type: receiverValue.type, loc }; + const destinationRef: IrExpr = { + kind: "varRef", + localId: destination.id, + type: target.type, + loc, + }; + const ending = lowerer.declareHiddenLocal("%pipeEnd", BOOL); + stmts.push({ kind: "varDecl", localId: ending.id, init: end, loc }); + const endRef: IrExpr = { kind: "varRef", localId: ending.id, type: BOOL, loc }; + stmts.push( + dynNativeBranch( + lowerer, + destinationRef, + "w", + (native) => ({ + kind: "exprStmt", + expr: { + kind: "libCall", + // Checked-dynamic slots come with the general representation, + // where a boxed native destination can be the converted + // process.stdout (generalPipe). + fn: "readable.pipeGeneral", + args: [sourceRef, native, endRef], + type: native.type, + loc, + }, + loc, + }), + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "stream.pipeDyn", + args: [sourceRef, lowerer.coerceToExpected(sourceRef, DYN), destinationRef, endRef], + type: VOID, + loc, + }, + loc, + }, + ), + ); + return { + kind: "seqExpr", + stmts, + result: destinationRef, + type: target.type, + loc, + }; + } + stmts.push({ + kind: "exprStmt", + expr: { + kind: "libCall", + fn: lowerer.generalNodeStreams ? "readable.pipeGeneral" : "readable.pipe", + args: [ + { kind: "varRef", localId: source.id, type: receiverValue.type, loc }, + { + kind: "libCall", + fn: "stream.unboxDyn", + args: [ + { kind: "varRef", localId: destination.id, type: target.type, loc }, + strLit("pipe destinations other than native streams are not supported yet", loc), + ], + type: duplex, + loc, + }, + end, + ], + type: duplex, + loc, + }, + loc, + }); + return { + kind: "seqExpr", + stmts, + result: { kind: "varRef", localId: destination.id, type: target.type, loc }, + type: target.type, + loc, + }; +} + export function lowerStreamMethodCall( lowerer: Lowerer, call: ts.CallExpression, @@ -3525,22 +3839,26 @@ export function lowerStreamMethodCall( loc, }; } - const dst = lowerer.lowerExpr(args[0]!); + const target = lowerer.lowerExpr(args[0]!); + if (target.type.kind === "procStream") return pipeIntoStdio(lowerer, call, access, target); + if (target.type.kind === "dyn") return pipeIntoDyn(lowerer, call, access, target); + const dst = target; const dstInfo = dst.type.kind === "object" ? lowerer.classes.get(dst.type.className) : undefined; const dstSides = streamSidesOf(lowerer, dstInfo); if (dstSides !== "w" && dstSides !== "rw") { + requestGeneralStreamSurface(lowerer, args[0]!); lowerer.noLowering( `pipe into a '${lowerer.fmt(dst.type)}'`, args[0]!, - "the destination must be a stream Writable/Duplex (process streams and sockets are not pipe destinations yet)", + "the destination must be a stream Writable/Duplex, process.stdout or process.stderr (sockets are not pipe destinations yet)", ); } const end = pipeEndOption(lowerer, args[1], loc); const receiver = lowerer.lowerExpr(access.expression); return { kind: "libCall", - fn: "readable.pipe", + fn: generalPipe(lowerer, dst.type), args: [receiver, dst, end], type: dst.type, loc, @@ -3553,6 +3871,7 @@ export function lowerStreamMethodCall( const receiver = lowerer.lowerExpr(access.expression); if (args[0] !== undefined) { const dst = lowerer.lowerExpr(args[0]); + if (dst.type.kind === "dyn") return unpipeDyn(lowerer, receiver, dst, loc); const dstInfo = dst.type.kind === "object" ? lowerer.classes.get(dst.type.className) : undefined; const dstSides = streamSidesOf(lowerer, dstInfo); @@ -3567,6 +3886,44 @@ export function lowerStreamMethodCall( loc, }; } + // Checked-dynamic slots: unpipe() also unpipes the JavaScript + // destinations stream.pipeDyn registered (stream.unpipeDyn with an + // undefined destination), as Node's unpipe() empties every pipe. + if (lowerer.dynNodeStreams) { + const source = lowerer.declareHiddenLocal("%unpipeSource", receiver.type); + const sourceRef = varRef(source.id, receiver.type, loc); + return { + kind: "seqExpr", + stmts: [ + { kind: "varDecl", localId: source.id, init: receiver, loc }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "readable.unpipe", + args: [sourceRef], + type: receiver.type, + loc, + }, + loc, + }, + { + kind: "exprStmt", + expr: { + kind: "libCall", + fn: "stream.unpipeDyn", + args: [sourceRef, dynUndefinedExpr(loc)], + type: VOID, + loc, + }, + loc, + }, + ], + result: sourceRef, + type: receiver.type, + loc, + }; + } return { kind: "libCall", fn: "readable.unpipe", args: [receiver], type: receiver.type, loc }; } @@ -3654,8 +4011,10 @@ export function lowerStreamMethodCall( : lowerer.lowerExprExpecting(args[0]!, STRING); const fn = kind === "bytes" ? "writable.write" : "writable.writeStr"; if (cbArg !== undefined) { - const cb = lowerer.lowerExprExpecting(cbArg, funcOf([], VOID)); - if (cb.type.kind !== "func" || cb.type.params.length > 0) { + const cb = callbackDeclaresParameters(cbArg) + ? null + : lowerer.lowerExprExpecting(cbArg, funcOf([], VOID)); + if (cb === null || cb.type.kind !== "func" || cb.type.params.length > 0) { lowerer.noLowering( "write completion callbacks taking the error argument", cbArg, @@ -3712,8 +4071,10 @@ export function lowerStreamMethodCall( ); } if (cbArg !== undefined) { - const cb = lowerer.lowerExprExpecting(cbArg, funcOf([], VOID)); - if (cb.type.kind !== "func" || cb.type.params.length > 0) { + const cb = callbackDeclaresParameters(cbArg) + ? null + : lowerer.lowerExprExpecting(cbArg, funcOf([], VOID)); + if (cb === null || cb.type.kind !== "func" || cb.type.params.length > 0) { lowerer.noLowering( "end callbacks with parameters", cbArg, @@ -3978,3 +4339,17 @@ export function lowerStreamProperty( } return null; } + +/** Whether an inline completion callback declares a parameter. Node calls + * write and end callbacks with an error argument (null on success), but the + * native callback receives none, so the parameter would read undefined + * where Node passes null or the error. A function literal with parameters + * would otherwise adapt to the zero-parameter slot silently. */ +function callbackDeclaresParameters(node: ts.Expression): boolean { + let callback = node; + while (ts.isParenthesizedExpression(callback)) callback = callback.expression; + return ( + (ts.isArrowFunction(callback) || ts.isFunctionExpression(callback)) && + callback.parameters.nodes.length > 0 + ); +} diff --git a/packages/compiler/src/frontend/lowering/lower-test.ts b/packages/compiler/src/frontend/lowering/lower-test.ts index 32870c2cf..391a77245 100644 --- a/packages/compiler/src/frontend/lowering/lower-test.ts +++ b/packages/compiler/src/frontend/lowering/lower-test.ts @@ -86,8 +86,8 @@ function requireStatementPosition(lowerer: Lowerer, call: ts.CallExpression, wha * reports the `test` property's column), at the call for plain ones. */ function atStringOf(expr: ts.CallExpression): string { const target = ts.isPropertyAccessExpression(expr.expression) ? expr.expression.name : expr; - const sf = expr.getSourceFile(); - const pos = sf.getLineAndCharacterOfPosition(target.getStart()); + const sf = ts.getSourceFileOfNode(expr); + const pos = sf.getLineAndCharacterOfPosition(ts.tokenStartOfNode(target)); return `${sf.fileName}:${pos.line + 1}:${pos.character + 1}`; } diff --git a/packages/compiler/src/frontend/lowering/lower-timers.ts b/packages/compiler/src/frontend/lowering/lower-timers.ts index 22983fbb4..6b6a1b5d1 100644 --- a/packages/compiler/src/frontend/lowering/lower-timers.ts +++ b/packages/compiler/src/frontend/lowering/lower-timers.ts @@ -420,7 +420,7 @@ function lowerTimerCall( lowerer.noLowering(`clearTimeout with ${expr.arguments.nodes.length} arguments`, expr); } let handle = lowerer.lowerExpr(expr.arguments.nodes[0]!); - if (handle.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) { + if (handle.type.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { const stored = lowerer.declareHiddenLocal("%timer.clear", DYN); const ref = varRef(stored.id, DYN, loc); handle = { diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 27011df24..8708a0845 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -1,4 +1,12 @@ +import { onlyForwarded } from "./typed-arguments.js"; +import { + needsGeneralNodeStreams, + noteUntypedStreamFlow, + stdioIntoGeneralStream, +} from "./general-streams.js"; +import { extendAssertedRecordMembers } from "./assertion-members.js"; import { jsDynamicDeclarations } from "./js-dynamic-objects.js"; +import { builtinClassValue, builtinMemberClassKind } from "./builtins/builtin-class-dyn.js"; import { lifetimeRegionType, lowerLifetimeRegionCall, @@ -24,13 +32,21 @@ import { scanDivergences } from "./divergences.js"; import { applyKeyOrderWitnesses, KeyOrderNotes } from "./key-order-witness.js"; import { checkedClassAssertion } from "./class-assertions.js"; import { narrowStoredClassValue, narrowGenericClassValue } from "./class-unions.js"; -import { isOptionalProcessStreamProperty } from "./builtins/process.js"; +import { + isOptionalProcessStreamProperty, + isOptionalProcessVersionsRead, +} from "./builtins/process.js"; import { isSettledIteratorValueRead } from "./lower-generators.js"; import { concreteCollectionNarrow } from "./collection-narrowing.js"; import { lowerWorkerMetadata } from "./builtins/workers.js"; import { fenceStaticBlobMember, lowerBlobNew, lowerStaticBlobCall } from "./builtins/blob.js"; -import { refuseWebAssemblyUse } from "./builtins/webassembly.js"; +import { + lowerWebAssemblyCall, + lowerWebAssemblyNew, + refuseWebAssemblyUse, +} from "./builtins/webassembly.js"; import { discoverWasmModules, type WasmModuleReport } from "../../wasm/discover.js"; +import { encodeBase64 } from "../../wasm/base64.js"; import { resolveRequireRuntime } from "../runtime-resolve.js"; import { lowerKeyObjectCall } from "./builtins/key-object.js"; import { fenceWebCryptoMember, lowerWebCryptoCall } from "./builtins/webcrypto.js"; @@ -116,6 +132,7 @@ import { InternalCompilerError } from "../../errors.js"; import { defaultAfterUndefined, lowerStaticallyUndefinedArgument } from "./optional-arguments.js"; import { type ProtocolDispatch } from "./class-protocol-dispatch.js"; import { ClassDynamicDispatch, trackClassFieldCreation } from "./class-dynamic-dispatch.js"; +import { guardClassAttributeWrites } from "./class-attributes.js"; import { ClassProtocols } from "./class-protocols.js"; import { earlyReadVarSymbols } from "./var-hoisting.js"; import { recordTextCodecClass } from "../../ir/ir.js"; @@ -129,7 +146,8 @@ import { } 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"; +import { finalizePrivateBrands } from "./private-brands.js"; +import { type ClassSymbolKey, tableKeyFieldType } from "./symbol-fields.js"; import type { HttpClientFnBinding } from "./lower-server.js"; export type { WidthLift } from "./width-lift.js"; export { newFnCtx, type FnCtx } from "./function-context.js"; @@ -182,6 +200,7 @@ import type { IrGlobal, IrLocal, IrModule, + IrWasmModule, IrParam, IrRecordShape, IrStmt, @@ -299,6 +318,7 @@ import { import { prepareCjsModuleGraph, usesCjsTopLevelThis } from "./lower-node-module.js"; import { cjsNamespaceModule, + cjsWholeExportValueOf, inlineNamespaceRequireOf, inlineProgramRequireOf, } from "./cjs-inline-require.js"; @@ -873,6 +893,15 @@ export interface LowererMode { /** Statically resolved child_process.fork program roots, in stable target-id order. */ forkTargets?: readonly ts.SourceFile[]; frontendServices?: FrontendServices | undefined; + /** The program's general stream representation (TypeMapperCtx. + * generalNodeStreams). lowerToIr turns it on only after a pass without + * it saw a stream flow that needs it (requiresGeneralNodeStreams). */ + generalNodeStreams?: boolean; + /** With generalNodeStreams, the checked-dynamic stream slots + * (TypeMapperCtx.dynNodeStreams). lowerToIr turns it on only after a + * pass saw an untyped value flow into a stream slot + * (requiresDynNodeStreams). */ + dynNodeStreams?: boolean; /** LoadResult.inlineRequireCycleCalls. */ inlineRequireCycleCalls?: ReadonlySet | undefined; /** LowerOptions.libraryCallbacks (see there). */ @@ -1029,10 +1058,53 @@ export function lowerToIr( for (const d of extraModuleCycleDiags) reachableEmit.pushDiag(d); for (const d of ffiValidation.diagnostics) reachableEmit.pushDiag(d); const emitted = reachableEmit.emitReachable(options.libRoots); - const { reachable } = emitted; + let { reachable } = emitted; let result = emitted.result; let resultLowerer = reachableEmit; timing("reachable-emit", { reachable: reachable.size }); + // The general stream representation (TypeMapperCtx.generalNodeStreams) + // is a per-program decision: only a program whose reachable code moved + // a general stream into a stdio-represented stream slot lowers again + // with it. Every other program keeps the single pass above unchanged. + const generalNodeStreams = reachableEmit.requiresGeneralNodeStreams; + // Checked-dynamic stream slots (dyn-streams.ts) are a further per-program + // decision under the general representation: only a program that moves an + // untyped value into a stream slot lowers again with them. + let dynNodeStreams = reachableEmit.requiresDynNodeStreams; + const streamPass = (dyn: boolean): Lowerer => { + const general = new Lowerer(program, entry, moduleOrder, { + frontendServices: options.frontendServices, + projectCheckJs: options.projectCheckJs ?? false, + targetPlatform, + startupCrash, + inlineRequireCycleCalls: options.inlineRequireCycleCalls, + ffiImports, + libraryCallbacks, + forkTargets, + workerTargets, + externalTypes, + externalTypeSpecifiersByFile, + tierMode, + ffiBindingSymbols: ffiValidation.symbolsByName, + generalNodeStreams: true, + ...(dyn ? { dynNodeStreams: true } : {}), + }); + for (const d of extraModuleCycleDiags) general.pushDiag(d); + for (const d of ffiValidation.diagnostics) general.pushDiag(d); + const generalEmitted = general.emitReachable(options.libRoots); + reachable = generalEmitted.reachable; + result = generalEmitted.result; + resultLowerer = general; + timing(dyn ? "dyn-streams-relower" : "general-streams-relower", { reachable: reachable.size }); + return general; + }; + if (generalNodeStreams) { + const general = streamPass(dynNodeStreams); + if (!dynNodeStreams && general.requiresDynNodeStreams) { + dynNodeStreams = true; + streamPass(true); + } + } // A generic class-rest support decision can depend on record metadata // whose historical owner is discovered only while retained bodies lower. // If the settled answer is a fence, rerun the ordinary reachable emit so @@ -1040,10 +1112,12 @@ export function lowerToIr( // declarators stay unvisited, bindings block, cascades and stats match the // historical compiler. This is a rare compatibility fallback; programs // without such a settled fence retain checker-backed IR exactly once. - if (reachableEmit.requiresHistoricalOrderRelower) { + if (resultLowerer.requiresHistoricalOrderRelower) { const emit = new Lowerer(program, entry, moduleOrder, { frontendServices: options.frontendServices, projectCheckJs: options.projectCheckJs ?? false, + ...(generalNodeStreams ? { generalNodeStreams: true } : {}), + ...(dynNodeStreams ? { dynNodeStreams: true } : {}), reachable, targetPlatform, startupCrash, @@ -1099,25 +1173,75 @@ export function lowerToIr( diagnostics: [...result.diagnostics, ...contracts.diagnostics], }; else if (contracts.count > 0) result = { ...result, contracts }; + const discoverModules = ( + onModule?: (sha256: string, bytes: Uint8Array) => void, + ): WasmModuleReport[] => + discoverWasmModules({ + files: moduleOrder.includes(entry) ? moduleOrder : [...moduleOrder, entry], + entry: entry.fileName, + configured: options.wasmFiles ?? [], + resolveRequest: (fromFile, specifier) => { + const resolved = resolveRequireRuntime( + fromFile, + specifier, + targetPlatform, + undefined, + options.frontendServices, + ); + return resolved.ok ? resolved.value : null; + }, + ...(onModule === undefined ? {} : { onModule }), + }); + // A program that uses the WebAssembly API carries every module the build + // can see and translate (wasm/translate.ts); the rest fail at run time + // with the CompileError for untranslated bytes. + if (result.module !== null && resultLowerer.usesWebAssembly) { + const bytesByHash = new Map(); + const reports = discoverModules((sha256, bytes) => bytesByHash.set(sha256, bytes)); + const wasmModules: IrWasmModule[] = []; + for (const report of reports) { + const bytes = bytesByHash.get(report.sha256); + if (report.status !== "valid" || bytes === undefined) continue; + wasmModules.push({ sha256: report.sha256, bytes: encodeBase64(bytes) }); + } + result.module.wasmModules = wasmModules; + if (resultLowerer.usesWasmCustomSections) result.module.wasmCustomSections = true; + timing("wasm-discovery", { modules: wasmModules.length }); + } if (options.coverage !== true) return result; - const remainder = new Lowerer(program, entry, moduleOrder, { - frontendServices: options.frontendServices, - projectCheckJs: options.projectCheckJs ?? false, - reachable, - remainder: true, - alreadyFlushed: resultLowerer.flushedSymbols, - inlineRequireCycleCalls: options.inlineRequireCycleCalls, - targetPlatform, - ffiImports, - libraryCallbacks, - forkTargets, - workerTargets, - ffiBindingSymbols: ffiValidation.symbolsByName, - externalTypes, - externalTypeSpecifiersByFile, - tierMode, - }); - const rem = remainder.run(); + const remainderPass = (general: boolean, dyn = dynNodeStreams): Lowerer => + new Lowerer(program, entry, moduleOrder, { + frontendServices: options.frontendServices, + projectCheckJs: options.projectCheckJs ?? false, + ...(general ? { generalNodeStreams: true } : {}), + ...(general && dyn ? { dynNodeStreams: true } : {}), + reachable, + remainder: true, + alreadyFlushed: resultLowerer.flushedSymbols, + inlineRequireCycleCalls: options.inlineRequireCycleCalls, + targetPlatform, + ffiImports, + libraryCallbacks, + forkTargets, + workerTargets, + ffiBindingSymbols: ffiValidation.symbolsByName, + externalTypes, + externalTypeSpecifiersByFile, + tierMode, + }); + let remainder = remainderPass(generalNodeStreams); + let rem = remainder.run(); + // Unreached code reports what it would compile to once reached: a + // stream flow there that needs the general representation lowers the + // remainder again with it, as reachable emit would. + if (!generalNodeStreams && remainder.requiresGeneralNodeStreams) { + remainder = remainderPass(true, remainder.requiresDynNodeStreams); + rem = remainder.run(); + } + if (!dynNodeStreams && remainder.generalNodeStreams && remainder.requiresDynNodeStreams) { + remainder = remainderPass(true, true); + rem = remainder.run(); + } timing("coverage-remainder"); // Tiers (tier-analysis.ts): reached statements in dynamic-tier functions, // and with tierReport the report-only demotion analysis. @@ -1152,21 +1276,7 @@ export function lowerToIr( }, }; if (tiers !== undefined) covered.tiers = tiers; - const wasmModules = discoverWasmModules({ - files: moduleOrder.includes(entry) ? moduleOrder : [...moduleOrder, entry], - entry: entry.fileName, - configured: options.wasmFiles ?? [], - resolveRequest: (fromFile, specifier) => { - const resolved = resolveRequireRuntime( - fromFile, - specifier, - targetPlatform, - undefined, - options.frontendServices, - ); - return resolved.ok ? resolved.value : null; - }, - }); + const wasmModules = discoverModules(); timing("wasm-discovery", { modules: wasmModules.length }); if (wasmModules.length > 0) covered.wasmModules = wasmModules; return covered; @@ -1242,7 +1352,7 @@ export function uncheckedOverloadHandleCall( * `never` at the ROOT stays out (`for (const v of [])`'s loop var — the * dead read the f64 mapping is FOR). */ export function neverTaintedJsType(lowerer: Lowerer, node: ts.Node, t: ts.Type): boolean { - if (!isJsSourceFile(node.getSourceFile())) return false; + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) return false; const walk = (x: ts.Type, depth: number): boolean => { if (depth === 0) return false; if (x.isUnionType()) return ts.constituentTypes(x).some((a) => walk(a, depth - 1)); @@ -1272,7 +1382,7 @@ export function ladderFenceExpr( const loc = locOf(node); const d = noLoweringDiag(surface, loc, hint); lowerer.runtimeFences.push(d); - const sf = node.getSourceFile(); + const sf = ts.getSourceFileOfNode(node); const pos = ts.getLineAndCharacterOfPosition(sf, loc.start); return { kind: "strLit", @@ -1284,7 +1394,7 @@ export function ladderFenceExpr( export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): IrType | null { if (t.flags & ts.TypeFlags.Void) return null; - if (!isJsSourceFile(node.getSourceFile())) { + if (!isJsSourceFile(ts.getSourceFileOfNode(node))) { if (t.flags & ts.TypeFlags.Any) return DYN; const symbol = t.getSymbol(); const args = @@ -1299,16 +1409,17 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir ts.TypeFlags.Any) !== 0; if ( - symbol?.name === "Promise" && + symbol !== undefined && + ts.symbolName(symbol) === "Promise" && args[0] && (args[0].flags & ts.TypeFlags.Any || anyArray(args[0])) ) - return symbol.name === "Promise" && anyArray(args[0]) + return ts.symbolName(symbol) === "Promise" && anyArray(args[0]) ? { kind: "promise", inner: DYN } : jsFallbackPromiseType(lowerer, t); if ( symbol && - ["Generator", "AsyncGenerator", "IterableIterator"].includes(symbol.name) && + ["Generator", "AsyncGenerator", "IterableIterator"].includes(ts.symbolName(symbol)) && args.some((arg) => arg.flags & ts.TypeFlags.Any) ) return jsFallbackGeneratorType(lowerer, t); @@ -1330,7 +1441,8 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir if (generator) return generator; const symbol = t.getSymbol(); if ( - (symbol?.name === "Map" || symbol?.name === "ReadonlyMap") && + ((symbol !== undefined && ts.symbolName(symbol) === "Map") || + (symbol !== undefined && ts.symbolName(symbol) === "ReadonlyMap")) && lowerer.isStdlibSymbol(symbol) ) { const args = lowerer.checker.getTypeArguments(t as ts.TypeReference); @@ -1419,7 +1531,12 @@ export function dynFallbackType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir * has no fixed shape. The settled value crosses the checked native boundary. */ function jsFallbackPromiseType(lowerer: Lowerer, t: ts.Type): IrType | null { const symbol = t.getSymbol(); - if (symbol?.name !== "Promise" || !lowerer.isStdlibSymbol(symbol)) return null; + if ( + symbol === undefined || + ts.symbolName(symbol) !== "Promise" || + !lowerer.isStdlibSymbol(symbol) + ) + return null; const inner = lowerer.checker.getTypeArguments(t as ts.TypeReference)[0]; return inner ? { kind: "promise", inner: lowerer.mapTypeOf(inner) ?? DYN } : null; } @@ -1430,7 +1547,7 @@ function jsFallbackGeneratorType(lowerer: Lowerer, t: ts.Type): IrType | null { const symbol = t.getSymbol(); if ( !symbol || - !["Generator", "AsyncGenerator", "IterableIterator"].includes(symbol.name) || + !["Generator", "AsyncGenerator", "IterableIterator"].includes(ts.symbolName(symbol)) || !lowerer.isStdlibSymbol(symbol) ) return null; @@ -1438,7 +1555,7 @@ function jsFallbackGeneratorType(lowerer: Lowerer, t: ts.Type): IrType | null { const unit = ts.TypeFlags.Void | ts.TypeFlags.Undefined | ts.TypeFlags.Never; return { kind: "generator", - ...(symbol.name === "AsyncGenerator" ? { async: true as const } : {}), + ...(ts.symbolName(symbol) === "AsyncGenerator" ? { async: true as const } : {}), yieldT: yielded && yielded.flags & ts.TypeFlags.Never ? VOID : DYN, retT: !returned || returned.flags & unit ? VOID : DYN, nextT: !resumed || resumed.flags & unit ? UNDEFINED_T : DYN, @@ -1472,7 +1589,7 @@ function jsFallbackFunctionType(lowerer: Lowerer, node: ts.Node, t: ts.Type): Ir const sigDecl = sig.declaration; const jsUnitReturn = sigDecl !== undefined && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && (retT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; const ret: IrType = jsUnitReturn ? DYN @@ -1506,7 +1623,7 @@ function jsArgumentsFunctionType(lowerer: Lowerer, t: ts.Type): IrType | null { ts.isFunctionExpression(decl) || ts.isArrowFunction(decl) ) || - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return null; const hasRest = decl.parameters.nodes.some((p) => p.dotDotDotToken !== undefined); @@ -1514,7 +1631,8 @@ function jsArgumentsFunctionType(lowerer: Lowerer, t: ts.Type): IrType | null { !hasRest && (ts.isArrowFunction(decl) || !bodyReadsArguments(decl) || - (decl.parameters.nodes.length > 0 && !isNodeEsmFile(decl.getSourceFile(), lowerer.program))) + (decl.parameters.nodes.length > 0 && + !isNodeEsmFile(ts.getSourceFileOfNode(decl), lowerer.program))) ) return null; const shapes = paramShapes(lowerer, decl.parameters.nodes); @@ -1917,6 +2035,18 @@ export class Lowerer { * type parameters are bound to `unknown`, so lambdaSignature admits * them. */ readonly erasingLambdas = new Set(); + /** Function expressions lowered against a fixed target signature with a + * tuple-typed `arguments` (typed-arguments.ts). */ + readonly typedArgumentsLambdas = new Set(); + /** Const bindings holding a forwarded fixed tuple (`const args = + * arguments as any` in a typed-arguments function) → the tuple type. */ + readonly tupleAliasSymbols = new Map(); + /** `??` expressions whose result is the optional callback's own function + * type (an adapted function default); their bindings take that type. */ + readonly nullishFuncResults = new Set(); + /** The static names of the current generic instance's name-keyed + * function parameters (static-function-names.ts). */ + staticFnNames: Map | null = null; /** Nesting depth of erased generic bodies being lowered (erased lambdas * and erased instances). */ erasingDepth = 0; @@ -1946,6 +2076,10 @@ export class Lowerer { * setEncoding call whose receiver is a socket or an untyped value. * Computed once on first use (null until then). */ socketEncodingReachable: boolean | null = null; + /** Whether some server in this program may listen on a Unix socket: a + * listen call whose first argument is a string. Computed once on first + * use (null until then); address() answers the path only when true. */ + unixListenReachable: boolean | null = null; /** IMPLICIT-ANY instances lowered EAGERLY at first demand (their return * types are inferred from the body — the call site needs them settled), * collected here for run()'s function list (the liftedFns discipline). */ @@ -1961,8 +2095,13 @@ export class Lowerer { * their symbol/node keys; no module-level cache should retain a pass. */ readonly classSymbolKeys = new Map(); readonly registeredClassSymbols = new Map(); + /** Symbol-key identities by description, in resolution order (the slot + * ordinals of key-table keys — symbolKeyOf). */ + readonly symbolFieldIdentities = new Map(); readonly httpClientFnBindings = new Map(); streamPropMutatedSyms: Set | null = null; + /** Whether some source reads process.exitCode (processReadsExitCode). */ + readsExitCode: boolean | null = null; requestInitPropMutatedSyms: Set | null = null; emitterEventTable: Map | null = null; emitterComputedPatterns: ComputedEventPattern[] = []; @@ -2009,7 +2148,9 @@ export class Lowerer { /** A pipeline() function stage's `source` parameter: the IR type of what * the pipeline passes it (the previous stage as an async iterable), set * while the stage lowers. Parameter typing and `for await` heads read - * it before the checker's `AsyncIterable`. */ + * it before the checker's `AsyncIterable`. A stream option callback's + * completion parameter declared with an any-typed `data` is pinned here + * too while the callback lowers (pinAnyDataCompletion). */ readonly stageSourceTypes = new Map(); /** Set when a match result lowers to execGroups/matchGroups, the inputs * of regex span folding. */ @@ -2115,6 +2256,51 @@ export class Lowerer { return fallback ?? null; } + /** A TypeScript object literal handed to a JavaScript checked-value + * parameter whose inferred type is open (`constructor({ factory, ...options + * } = {})` — the rest element admits any property): the literal names a + * property the contextual JS type does not declare, so it has no record + * layout there and is built as the plain object the JS side receives. */ + private excessJsContextualProperty( + literal: ts.ObjectLiteralExpression, + contextual: readonly ts.Type[], + ): boolean { + const objects = contextual.filter( + (part) => !(part.flags & (ts.TypeFlags.Undefined | ts.TypeFlags.Null)), + ); + if (objects.length === 0) return false; + // Only a destructuring pattern with a rest element is open; other + // inferred JS types keep their record lowering. + const fromJs = objects.every((part) => { + const sym = part.getSymbol(); + return ( + sym !== undefined && + sym.declarations.some((d) => { + if (!isJsSourceFile(ts.getSourceFileOfNode(d))) return false; + // The pattern itself, or the `= {}` default of a pattern parameter. + const pattern = + ts.isObjectLiteralExpression(d) && + d.parent !== undefined && + ts.isParameter(d.parent) && + d.parent.initializer === d + ? d.parent.name + : d; + return ( + ts.isObjectBindingPattern(pattern) && + pattern.elements.nodes.some((element) => element.dotDotDotToken !== undefined) + ); + }) + ); + }); + if (!fromJs) return false; + return literal.properties.nodes.some((prop) => { + if (!prop.name || !(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) + return false; + const name = prop.name.text; + return objects.every((part) => !this.checker.getPropertyOfType(part, name)); + }); + } + /** The index value of a keyed read of a record's index signature that * can answer undefined for a missing key while its checker type does not * admit undefined, or null. A literal key naming a declared field is a @@ -2206,7 +2392,7 @@ export class Lowerer { // A checked JavaScript indexed read can already be boxed. The checker // calls the element present, but that inference is not a runtime guard. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && value.kind === "dynCheck" && value.value.type.kind === "dyn" ) @@ -2442,6 +2628,21 @@ export class Lowerer { /** Settled generic class-rest metadata requires the historical emit * fallback so its fence can poison the original statement atomically. */ requiresHistoricalOrderRelower = false; + /** A general stream value (a runtime stream class instance) reached a + * slot the child/process stdio handles represent — NodeJS.ReadableStream, + * NodeJS.WritableStream, or @types/node's stream.Readable/Writable — or + * a stdio handle reached an 'unknown' slot. lowerToIr then lowers the + * program again under the general stream representation. */ + requiresGeneralNodeStreams = false; + /** An untyped value reached a stream slot (a checked unwrap into a + * runtime stream class, or an untyped pipe/pipeline stage). lowerToIr + * then lowers the program again with checked-dynamic stream slots + * (dyn-streams.ts). */ + requiresDynNodeStreams = false; + /** Receiver representations forced by the per-value stream dispatch + * (dyn-streams.ts): the native arm lowers a member call on the unboxed + * stream through the ordinary class-method path. */ + readonly receiverTypeOverrides = new Map(); /** Helpers that snapshot shape declaration order into their bodies. * Reachability lowers inits before the declarations they discover, so * these rebuild after the worklist restores historical shape metadata. */ @@ -2520,6 +2721,10 @@ export class Lowerer { * lifted lazily as module-level functions and interned per accessor * declaration: member reads call the getter (lower-exprs). */ readonly cjsAccessorFns = new Map(); + /** Accessor exports' link-time values for ES-module importers, and the + * per-module flag that the copy already ran (cjsAccessorSnapshotStores). */ + readonly cjsAccessorSnapshots = new Map(); + readonly cjsSnapshotReady = new Map(); /** Interned `%iter.drain.` helpers (classIteratorDrainCall): one per * receiver class — the eager drain of a class iterable's protocol into * a fresh element array, behind array/call spreads. */ @@ -2599,6 +2804,10 @@ export class Lowerer { /** Named method slots observed before class collection need a callable * ABI and shared prototype descriptors instead of call-site specialization. */ readonly prototypeMethodAccesses = new Map(); + /** Well-known symbol methods (`sym:iterator`) the program may replace + * through a symbol-keyed write: an instance or prototype property, or a + * reflection call. */ + readonly replacedSymbolMethods = new Set(); /** Typed calls through class-backed protocols, by signature; class * dynamic dispatch adds their native arms (class-protocol-dispatch.ts). */ readonly protocolDispatches = new Map(); @@ -2688,6 +2897,12 @@ export class Lowerer { readonly workerTargets: readonly ts.SourceFile[]; readonly workerTargetIdByPath = new Map(); usesWorkers = false; + /** The program uses the WebAssembly API (builtins/webassembly.ts): the + * build translates the modules it discovers. */ + usesWebAssembly = false; + /** The program calls WebAssembly.Module.customSections: translated + * modules keep their custom sections. */ + usesWasmCustomSections = false; /** The program constructs a LifetimeRegion (lifetime-regions.ts). */ usesLifetimeRegions = false; /** Per-directory answers of experimental-import.ts's declaration-file test. */ @@ -2889,6 +3104,8 @@ export class Lowerer { * refuse and fence exactly as before. Stage S6 demotes the units of * committed refusals in "auto" and keeps them refusals in "static-only". */ readonly tierMode: TierMode; + readonly generalNodeStreams: boolean; + readonly dynNodeStreams: boolean; /** Functions finishModule placed in the dynamic tier (ir/tiers.ts). */ dynamicFunctions: DynamicFunction[] = []; /** Their IR, for coverage's dynamic statement count. */ @@ -3017,6 +3234,8 @@ export class Lowerer { this.startupCrash = mode.startupCrash ?? null; this.targetPlatform = mode.targetPlatform ?? process.platform; this.tierMode = mode.tierMode ?? "auto"; + this.generalNodeStreams = mode.generalNodeStreams ?? false; + this.dynNodeStreams = this.generalNodeStreams && (mode.dynNodeStreams ?? false); this.workerTargets = mode.workerTargets ?? []; this.workerTargets.forEach((sf, id) => { this.workerTargetIdByPath.set(tsgoPath(resolve(sf.fileName)), id); @@ -3097,7 +3316,8 @@ export class Lowerer { if (this.localClassInstantiations.some((entry) => entry.owner === parent)) break; if ( parent.typeParameters?.nodes.length || - (implicitMonoFile(parent.getSourceFile()) && monomorphizedImplicit(this, parent)) + (implicitMonoFile(ts.getSourceFileOfNode(parent)) && + monomorphizedImplicit(this, parent)) ) return null; } @@ -3113,6 +3333,8 @@ export class Lowerer { isExternalTypeFile: (sf) => this.externalTypeSpecifiersByFile.has(tsgoPath(resolve(sf.fileName))), typeMemo: this.typeMemo, + ...(this.generalNodeStreams ? { generalNodeStreams: true } : {}), + ...(this.dynNodeStreams ? { dynNodeStreams: true } : {}), canMemoizeType: () => this.typeParamBindings === null && this.typeParamTsBindings === null && @@ -3121,15 +3343,15 @@ export class Lowerer { // lowering, long after the constructor completes. isProgramFile: (sf) => this.fileTag.has(sf), lifetimeRegionClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? lifetimeRegionType(this) : null, internTableClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? internTableType(this) : null, threadSlotsClass: (decl) => - isExperimentalDeclarationFile(decl.getSourceFile(), this.experimentalFiles) + isExperimentalDeclarationFile(ts.getSourceFileOfNode(decl), this.experimentalFiles) ? threadSlotsType(this) : null, moduleNamespaceId: (type) => this.moduleNamespaceIdOfType(type), @@ -3195,9 +3417,10 @@ export class Lowerer { const weak = (part: ts.Type): boolean => { const symbol = part.getSymbol(); return ( - (symbol?.name === "WeakMap" || symbol?.name === "WeakSet") && + ((symbol !== undefined && ts.symbolName(symbol) === "WeakMap") || + (symbol !== undefined && ts.symbolName(symbol) === "WeakSet")) && symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && this.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && this.isStdlibFile(ts.getSourceFileOfNode(d)), ) ); }; @@ -3306,9 +3529,12 @@ export class Lowerer { } return false; })() - ? this.qualify(decl.getSourceFile(), `%cx${decl.getStart()}.${decl.name?.text ?? ""}`) + ? this.qualify( + ts.getSourceFileOfNode(decl), + `%cx${ts.tokenStartOfNode(decl)}.${decl.name?.text ?? ""}`, + ) : this.qualify( - decl.getSourceFile(), + ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + (decl.name ? decl.name.text : "%anon"), ); for (let index = this.localClassInstantiations.length - 1; index >= 0; index--) { @@ -3552,7 +3778,10 @@ export class Lowerer { return null; if (this.externalTypes.has(importDecl.moduleSpecifier.text)) return importDecl.moduleSpecifier.text; - const dep = this.moduleSourceFileOf(importDecl.getSourceFile(), importDecl.moduleSpecifier); + const dep = this.moduleSourceFileOf( + ts.getSourceFileOfNode(importDecl), + importDecl.moduleSpecifier, + ); return dep !== null && !dep.isDeclarationFile ? this.externalTypeSpecifierOfModuleExport(dep, member, new Set(), new Set()) : null; @@ -3600,7 +3829,7 @@ export class Lowerer { // A namespace OBJECT from a project module is not wholly external; // property accesses resolve their selected member separately above. if (importedName === null) return null; - const dep = this.moduleSourceFileOf(decl.getSourceFile(), specNode); + const dep = this.moduleSourceFileOf(ts.getSourceFileOfNode(decl), specNode); return dep !== null && !dep.isDeclarationFile ? this.externalTypeSpecifierOfModuleExport(dep, importedName, seenSymbols, seenExports) : null; @@ -3608,7 +3837,7 @@ export class Lowerer { for (const decl of declarations) { const owners = this.externalTypeSpecifiersByFile.get( - tsgoPath(resolve(decl.getSourceFile().fileName)), + tsgoPath(resolve(ts.getSourceFileOfNode(decl).fileName)), ); if (owners !== undefined && owners.length > 0) return owners[0]!; } @@ -3653,7 +3882,7 @@ export class Lowerer { } if (d === undefined || spec === undefined || !ts.isStringLiteral(spec) || name !== "default") return null; - const dep = resolveImport(this.program, d.getSourceFile(), spec.text); + const dep = resolveImport(this.program, ts.getSourceFileOfNode(d), spec.text); if (!dep) return null; sym = defaultExportSymbolOf(this, dep) ?? undefined; } @@ -3719,13 +3948,18 @@ export class Lowerer { ? d.parent : null; if (!assign || assign.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return null; - if (!ts.isIdentifier(assign.right)) return null; const stmt = assign.parent; if (!stmt || !ts.isExpressionStatement(stmt) || !ts.isSourceFile(stmt.parent)) return null; if (!isJsSourceFile(stmt.parent)) return null; const cjs = cjsExportAssignmentOf(stmt); if (cjs?.kind !== "member" || cjs.expr !== assign) return null; if (cjsExportDiscardReason(stmt) !== null) return null; + // `module.exports.C = require('./c')` over a module whose whole export + // is a class identifier: the same alias plumbing; the statement keeps + // the require's module evaluation at its position. + const required = this.memberExportRequiredClass(assign.right); + if (required !== undefined) return required; + if (!ts.isIdentifier(assign.right)) return null; let value = this.checker.getSymbolAtLocation(assign.right); if (!value) return null; if (value.flags & ts.SymbolFlags.Alias) value = this.checker.getAliasedSymbol(value); @@ -3733,6 +3967,22 @@ export class Lowerer { return isClass ? value : null; } + /** For an inline program `require("./c")`: the class symbol its module's + * kept `module.exports = ` names, or null when the + * whole export is something else. Undefined when `expr` is not such a + * require. */ + memberExportRequiredClass(expr: ts.Expression): ts.Symbol | null | undefined { + const req = inlineProgramRequireOf(this, expr); + if (req === null) return undefined; + const whole = cjsWholeExportValueOf(req.dep); + if (whole === null || !ts.isIdentifier(whole.rhs)) return null; + let value = this.checker.getSymbolAtLocation(whole.rhs); + if (!value) return null; + if (value.flags & ts.SymbolFlags.Alias) value = this.checker.getAliasedSymbol(value); + const isClass = value.declarations.some((decl) => ts.isClassDeclaration(decl)); + return isClass ? value : null; + } + /** The CommonJS JS module a DEFAULT-import binding's declaration loads * (`import d from "./lib.cjs"`), or null: Node's ESM-CJS interop binds * the default to module.exports — exactly a require binding — so those @@ -3748,8 +3998,8 @@ export class Lowerer { // Project aliases/self-references resolve through the same entry point as // relative imports; an opted-in --npm-static package is the fallback. const dep = - resolveImport(this.program, importDecl.getSourceFile(), spec) ?? - npmStaticDepSf7(this.program, importDecl.getSourceFile(), spec); + resolveImport(this.program, ts.getSourceFileOfNode(importDecl), spec) ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(importDecl), spec); if (!dep || !isJsSourceFile(dep) || isNodeEsmFile(dep, this.program)) return null; return dep; } @@ -3789,8 +4039,8 @@ export class Lowerer { if (directSpec !== null) { if (isJsonRequireDecl(this, decl)) return null; return ( - resolveImport(this.program, decl.getSourceFile(), directSpec, "require") ?? - npmStaticDepSf7(this.program, decl.getSourceFile(), directSpec, "require") + resolveImport(this.program, ts.getSourceFileOfNode(decl), directSpec, "require") ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(decl), directSpec, "require") ); } return createRequireProgramModuleOf(this, decl.initializer)?.dep ?? null; @@ -4130,7 +4380,7 @@ export class Lowerer { * report on the next tick. Later loads are cache hits either way. */ punycodeRequireWarning(node: ts.Node): IrStmt { const loc = locOf(node); - const fromDependency = /[\\/]node_modules[\\/]/.test(node.getSourceFile().fileName); + const fromDependency = /[\\/]node_modules[\\/]/.test(ts.getSourceFileOfNode(node).fileName); return { kind: "exprStmt", expr: { @@ -4201,8 +4451,8 @@ export class Lowerer { // those globals stay uninitialized: the dep's module body would never // run. const dep = - resolveImport(this.program, node.getSourceFile(), spec, "require") ?? - npmStaticDepSf7(this.program, node.getSourceFile(), spec, "require"); + resolveImport(this.program, ts.getSourceFileOfNode(node), spec, "require") ?? + npmStaticDepSf7(this.program, ts.getSourceFileOfNode(node), spec, "require"); if (!dep || dep.fileName.endsWith(".json")) return null; if (this.asyncInitFiles.has(dep)) { this.unsupported( @@ -4219,7 +4469,7 @@ export class Lowerer { expr: { kind: "call", callee: initName, args: [], type: VOID, loc }, loc, }; - const parentModule = this.cjsModuleIdOf.get(node.getSourceFile()); + const parentModule = this.cjsModuleIdOf.get(ts.getSourceFileOfNode(node)); const childModule = this.cjsModuleIdOf.get(dep); if (parentModule === undefined || childModule === undefined) return init; return { @@ -4838,6 +5088,7 @@ export class Lowerer { if (isIndexedRead(e)) return true; if (ts.isPropertyAccessExpression(e) && isRecordIndexPropertyRead(e)) return true; if (ts.isPropertyAccessExpression(e) && isOptionalProcessStreamProperty(this, e)) return true; + if (isOptionalProcessVersionsRead(this, e)) return true; if (ts.isPropertyAccessExpression(e) && isSettledIteratorValueRead(this, e)) return true; if (ts.isPropertyAccessExpression(e) && isOsConstantsSymbol(symbolOf(e.expression), this)) return true; @@ -4980,7 +5231,7 @@ export class Lowerer { const decl = symbol.valueDeclaration; if (!decl || !ts.isPropertyDeclaration(decl) || !ts.isIdentifier(decl.name)) return false; if (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Static) return false; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return false; const declared = typeOrNull(decl.name); const mapped = declared ? this.mapTypeOf(declared) : null; @@ -5070,7 +5321,8 @@ export class Lowerer { const initSymbol = initType.getAliasSymbol() ?? initType.getSymbol(); const matchResult = initSymbol !== undefined && - (initSymbol.name === "RegExpExecArray" || initSymbol.name === "RegExpMatchArray"); + (ts.symbolName(initSymbol) === "RegExpExecArray" || + ts.symbolName(initSymbol) === "RegExpMatchArray"); if (arrayType?.kind !== "array" || matchResult) return scanRecordPattern(name, init); const positions = name.elements.nodes.filter((el) => { const key = el.propertyName; @@ -5246,7 +5498,7 @@ export class Lowerer { }; for (const [symbol, signature] of signatureBySymbol) { const declaration = functionDeclBySymbol.get(symbol); - if (!declaration || !isJsSourceFile(declaration.getSourceFile())) continue; + if (!declaration || !isJsSourceFile(ts.getSourceFileOfNode(declaration))) continue; const checked = this.checker.getSignatureFromDeclaration(declaration); const checkedReturn = checked ? this.checker.getReturnTypeOfSignature(checked) : null; // Checker-known optional results already narrow at each use. Only @@ -6088,7 +6340,7 @@ export class Lowerer { const callee = functionDeclBySymbol.get(symbol); if ( callee && - isJsSourceFile(callee.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(callee)) && !node.arguments.nodes.some(ts.isSpreadElement) ) { for (let i = 0; i < sig.params.length; i++) { @@ -6304,7 +6556,7 @@ export class Lowerer { for (const [symbol, decl] of functionDeclBySymbol) if (scanReturns(symbol, decl)) changed = true; const noteGenericReturn = (symbol: ts.Symbol, decl: ts.FunctionLikeDeclaration): void => { - if (optionalReturns.has(symbol) || isJsSourceFile(decl.getSourceFile())) return; + if (optionalReturns.has(symbol) || isJsSourceFile(ts.getSourceFileOfNode(decl))) return; if (!returnsOf(decl).some(genericMayBeOptional)) return; optionalReturns.add(symbol); this.runtimeOptionalGenericReturns.add(decl); @@ -6581,7 +6833,7 @@ export class Lowerer { if ( global.type.kind !== "func" || !ts.isBinaryExpression(declaration) || - !isJsSourceFile(declaration.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) ) continue; const value = peel(declaration.right); @@ -6710,6 +6962,10 @@ export class Lowerer { } this.collectProgram(parts); this.speculativeTypeQueries(() => { + extendAssertedRecordMembers( + this, + parts.map((fp) => fp.sf), + ); this.analyzeRuntimeOptionalArrayReads(parts); this.analyzeDateJsonResults(parts); this.analyzeGuardedRecordReadGlobals(parts); @@ -6856,7 +7112,9 @@ export class Lowerer { finishModule(functions: IrFunction[]): LowerResult { if (this.diags.length === 0) fuseArrayPipelines(this, functions); finalizeClassMethodValues(this, functions); + finalizePrivateBrands(this, functions); trackClassFieldCreation(this, functions); + guardClassAttributeWrites(this, functions); // Globals typed by a class that never REGISTERED (a JS class whose // collection fenced — Symbol-keyed fields, an unsupported base): the @@ -7051,7 +7309,7 @@ export class Lowerer { if (n === 0) return; this.stats.statementsTotal += n; this.stats.statementsFailed += n; - const file = decl.getSourceFile().fileName; + const file = ts.getSourceFileOfNode(decl).fileName; for (let i = 0; i < n; i++) { this.bumpFileStat(file, "total"); this.bumpFileStat(file, "failed"); @@ -7116,6 +7374,10 @@ export class Lowerer { }; this.collectProgram(parts); this.speculativeTypeQueries(() => { + extendAssertedRecordMembers( + this, + parts.map((fp) => fp.sf), + ); this.analyzeRuntimeOptionalArrayReads(parts); this.analyzeDateJsonResults(parts); this.analyzeGuardedRecordReadGlobals(parts); @@ -7666,8 +7928,8 @@ export class Lowerer { "this statement uses a construct with no static lowering"; const messageLoc = first?.loc ?? loc; const source = first - ? (this.program.getSourceFile(messageLoc.file) ?? node.getSourceFile()) - : node.getSourceFile(); + ? (this.program.getSourceFile(messageLoc.file) ?? ts.getSourceFileOfNode(node)) + : ts.getSourceFileOfNode(node); const pos = ts.getLineAndCharacterOfPosition(source, messageLoc.start); return { kind: "runtimeFence", @@ -7786,10 +8048,12 @@ export class Lowerer { // here; constructor objects such as Headers still can. if ( typeSym && - (FETCH_AMBIENT_TYPES as readonly string[]).includes(typeSym.name) && + (FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(typeSym)) && this.isStdlibSymbol(typeSym) ) { - this.pushDiag(unsupportedNativeApiDiag(`a value of type '${typeSym.name}'`, locOf(node))); + this.pushDiag( + unsupportedNativeApiDiag(`a value of type '${ts.symbolName(typeSym)}'`, locOf(node)), + ); throw new PoisonError(); } // ENUM OBJECTS (`typeof e` — a numeric enum's type carries the @@ -7803,7 +8067,7 @@ export class Lowerer { unsupportedDiag( "SC1090", locOf(node), - `enum objects as values ('${typeSym.name}' — member reads like '${typeSym.name}.X' compile to constants; the object itself has no runtime representation)`, + `enum objects as values ('${ts.symbolName(typeSym)}' — member reads like '${ts.symbolName(typeSym)}.X' compile to constants; the object itself has no runtime representation)`, ), ); throw new PoisonError(); @@ -7833,7 +8097,7 @@ export class Lowerer { ownSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - this.isStdlibFile(d.getSourceFile()), + this.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { this.pushDiag(noLoweringDiag(this.checker.typeToString(type), locOf(node))); @@ -7879,7 +8143,7 @@ export class Lowerer { stdlibOwnSym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - this.isStdlibFile(d.getSourceFile()), + this.isStdlibFile(ts.getSourceFileOfNode(d)), ); // OVERLOADED call signatures (SC2007) — after the generic branch, so a // generic overload set keeps the monomorphization story: a value of a @@ -7941,7 +8205,7 @@ export class Lowerer { noLoweringDiag( this.checker.typeToString(type), locOf(node), - typeHints[stdlibOwnSym?.name ?? ""], + typeHints[ts.symbolNameOf(stdlibOwnSym) ?? ""], ), ); throw new PoisonError(); @@ -8018,7 +8282,7 @@ export class Lowerer { } typeOf(node: ts.Node): ts.Type { - if (ts.isPropertyAccessExpression(node) && isJsSourceFile(node.getSourceFile())) { + if (ts.isPropertyAccessExpression(node) && isJsSourceFile(ts.getSourceFileOfNode(node))) { const receiver = node.expression.kind === ts.SyntaxKind.ThisKeyword && this.fnStack.length > 0 ? this.resolveThis()?.type @@ -8037,12 +8301,23 @@ export class Lowerer { return this.checker.getUnknownType(); } } + // A symbol-keyed field read through a CommonJS key-table key reads as + // the field's layout type (tableKeyFieldType). + if (ts.isElementAccessExpression(node) && isJsSourceFile(ts.getSourceFileOfNode(node))) { + const field = tableKeyFieldType(this, node); + if (field) { + if (field.kind === "f64") return this.checker.getNumberType(); + if (field.kind === "string") return this.checker.getStringType(); + if (field.kind === "bool") return this.checker.getBooleanType(); + return this.checker.getUnknownType(); + } + } // A checked JS method receives its actual call-site receiver. Its // declaration's inferred class type must not turn property reads back // into native field loads when the method is borrowed by another object. if ( node.kind === ts.SyntaxKind.ThisKeyword && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && this.fnStack.length > 0 && this.resolveThis()?.type.kind === "dyn" ) @@ -8316,7 +8591,7 @@ export class Lowerer { // locals at null/undefined despite later writes. Keep mutable native // storage instead of a slot restricted to the initializer's unit value. if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && (t.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0 ) return DYN; @@ -8355,6 +8630,12 @@ export class Lowerer { * validator ICEs on them as the usual backstop. */ requireExactShape(node: ts.Node, actual: IrType, expected: IrType): void { if (typeEquals(actual, expected)) return; + if (!this.generalNodeStreams && needsGeneralNodeStreams(this, actual, expected)) { + this.requiresGeneralNodeStreams = true; + // An untyped value into a stream slot also needs the checked-dynamic + // slots (dyn-streams.ts). + if (actual.kind === "dyn") this.requiresDynNodeStreams = true; + } // dyn mismatches first. A dyn ('unknown') value flowing into a typed // slot needs a CHECKED cast — the hint points at `as `; tsc // usually rejects this before we do, so the fence is mostly defensive. @@ -8553,6 +8834,17 @@ export class Lowerer { * else (including a DIFFERENT union) is left for requireExactShape, which * rejects union mismatches with SC2003. */ coerceToExpected(expr: IrExpr, expected: IrType): IrExpr { + // A comma expression ending in a unit literal (`return log(), null`): + // its effects run first, unchanged, and the unit value takes the + // destination's representation (a bare unit literal has none). + if ( + expr.kind === "seqExpr" && + expr.result.kind === "unitLit" && + !typeEquals(expr.type, expected) + ) { + const result = this.coerceToExpected(expr.result, expected); + return { ...expr, result, type: result.type }; + } // A destination that holds undefined takes an unassigned deferred-init // field's slot as is (Node passes the undefined along). if (expected.kind === "union" && this.armTag(expected.unionId, UNDEFINED_T) >= 0) { @@ -8561,6 +8853,9 @@ export class Lowerer { } if (expr.type.kind === "void" && expected.kind !== "void" && this.neverValued.has(expr)) return this.divergentValue(expr, expected); + // process.stdout/stderr into a general Writable slot (general-streams.ts). + const stdioStream = stdioIntoGeneralStream(this, expr, expected); + if (stdioStream) return this.coerceToExpected(stdioStream, expected); // Iterator-typed slots: native iterators and differently typed // generators step through an adapter generator. if (expected.kind === "generator" && !typeEquals(expr.type, expected)) { @@ -8732,6 +9027,7 @@ export class Lowerer { (id) => this.unions.get(id), ); const handleOk = DYN_HANDLE_KINDS.has(expected.kind); + noteUntypedStreamFlow(this, expected); if ( canDynCheckTo( expected, @@ -8927,6 +9223,10 @@ export class Lowerer { return this.applyWidthLift(lift, narrowed, expected, expr.loc); } } + if (expr.type.kind === "union" && expected.kind === "record") { + const lifted = unionArmsWidthLift(this, expr, expected); + if (lifted) return lifted; + } if (!typeEquals(expr.type, expected)) { const w = this.widthCoerce(expr, expected); if (w) return w; @@ -9264,6 +9564,7 @@ export class Lowerer { * lowering goes through here (via lowerExprExpecting) or calls this * directly when the expression was already lowered. */ coerceInto(node: ts.Node, expr: IrExpr, expected: IrType): IrExpr { + refuseInferredCallbackDrops(this, node, expr.type, expected); // A union destination can accept the binding's tagged storage directly. // The checker's single-arm flow type can be stale after a callback writes // the binding. Extracting that arm and wrapping it again would discard the @@ -9321,7 +9622,9 @@ export class Lowerer { : []; const errorOptions = parts.some((part) => { const sym = part.getAliasSymbol() ?? part.getSymbol(); - return sym?.name === "ErrorOptions" && this.isStdlibSymbol(sym); + return ( + sym !== undefined && ts.symbolName(sym) === "ErrorOptions" && this.isStdlibSymbol(sym) + ); }); if (errorOptions) { const actual = this.typeOf(node); @@ -9357,7 +9660,7 @@ export class Lowerer { let literal = node; while (ts.isParenthesizedExpression(literal)) literal = literal.expression; if ( - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && !ts.isArrayLiteralExpression(literal) && e.kind === "dynFrom" && e.value.type.kind === "array" @@ -9389,7 +9692,7 @@ export class Lowerer { if ( e.type.kind === "union" && !typeEquals(e.type, expected) && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && this.dynConvertible(e.type) ) { const sourceArms = this.unions.get(e.type.unionId)?.arms ?? []; @@ -9444,7 +9747,7 @@ export class Lowerer { !ts.isIdentifier(d.name) || !ts.isVariableDeclarationList(d.parent) || !ts.isVariableStatement(d.parent.parent) || - d.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(d).isDeclarationFile || (ts.getCombinedModifierFlags(d) & ts.ModifierFlags.Ambient) !== 0 ) { return null; @@ -9591,6 +9894,8 @@ export class Lowerer { node = node.expression; if (expected) { if (ts.isConditionalExpression(node)) return lowerTernary(this, node, expected); + const escaped = this.neverAssertionInto(node, expected); + if (escaped) return escaped; const fresh = this.emptyCollectionFor(node, expected); if (fresh) return this.coerceInto(node, fresh, expected); } @@ -9661,17 +9966,18 @@ export class Lowerer { const builtinOption = (name: string): boolean => parts.some((part) => { const sym = part.getAliasSymbol() ?? part.getSymbol(); - return sym?.name === name && this.isStdlibSymbol(sym); + return ts.symbolNameOf(sym) === name && this.isStdlibSymbol(sym); }); if (ts.isObjectLiteralExpression(x)) { if ( - isJsSourceFile(x.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(x)) || builtinOption("RequestInit") || builtinOption("ErrorOptions") || builtinOption("SpawnOptions") || builtinOption("UrlObject") || builtinOption("URLFormatOptions") || - !this.mapTypeOf(this.typeOf(x)) + !this.mapTypeOf(this.typeOf(x)) || + this.excessJsContextualProperty(x, parts) ) { return lowerDynObjectLiteral(this, x); } @@ -9747,7 +10053,7 @@ export class Lowerer { coerceExistingInto(node: ts.Expression, e: IrExpr, expected: IrType): IrExpr { if ( (expected.kind === "union" && this.armTag(expected.unionId, UNDEFINED_T) >= 0) || - (expected.kind === "dyn" && isJsSourceFile(node.getSourceFile())) + (expected.kind === "dyn" && isJsSourceFile(ts.getSourceFileOfNode(node))) ) { // A destination that accepts undefined must retain an unchecked // read's storage value even when the checker still calls it present. @@ -9770,7 +10076,8 @@ export class Lowerer { return null; const symbol = this.resolveValueSymbol(value.expression); if (!symbol || !this.isStdlibSymbol(symbol)) return null; - const kind = symbol.name === "Map" ? "map" : symbol.name === "Set" ? "set" : null; + const kind = + ts.symbolName(symbol) === "Map" ? "map" : ts.symbolName(symbol) === "Set" ? "set" : null; if (!kind) return null; const arms = expected.kind === "union" ? (this.unions.get(expected.unionId)?.arms ?? []) : [expected]; @@ -9895,7 +10202,7 @@ export class Lowerer { while (ts.isParenthesizedExpression(literal)) literal = literal.expression; return ( expected.kind === "dyn" && - isJsSourceFile(node.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(node)) && ts.isObjectLiteralExpression(literal) ); } @@ -10000,6 +10307,15 @@ export class Lowerer { e = this.returnedPromiseAwait(e); } if (e.kind === "unitLit") return { kind: "return", value: null, loc }; + // `return c ? f() : g()` over void calls: a void conditional has no + // value representation, so its branch selection becomes control flow. + if (e.kind === "ternary" && e.type.kind === "void") { + const branch = (x: IrExpr): IrStmt => + x.kind === "ternary" && x.type.kind === "void" + ? { kind: "if", cond: x.cond, then: [branch(x.then)], else_: [branch(x.else_)], loc } + : { kind: "exprStmt", expr: x, loc }; + return { kind: "block", body: [branch(e), { kind: "return", value: null, loc }], loc }; + } if (e.type.kind === "void") return { kind: "return", value: e, loc }; return { kind: "block", @@ -10352,7 +10668,7 @@ export class Lowerer { } } } - if (!isJsSourceFile(decl.getSourceFile())) continue; + if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) continue; for (const member of decl.members.nodes) { if (ts.isConstructorDeclaration(member)) { for (const param of member.parameters.nodes) { @@ -10385,7 +10701,7 @@ export class Lowerer { } // npm-static implicit-any classification resolves body parameter // references while collecting the method's shape. - if (implicitMonoFile(decl.getSourceFile()) && member.body) { + if (implicitMonoFile(ts.getSourceFileOfNode(decl)) && member.body) { symbolRoots.push(member.body); } } @@ -10494,6 +10810,14 @@ export class Lowerer { return findMethodOn(this, info, name); } + /** A class whose root is the runtime's %Error (builtin errors and their + * program subclasses). */ + isErrorRootedClass(className: string): boolean { + let info = this.classes.get(className) ?? null; + while (info && info.base) info = info.base; + return info?.def.name === "%Error"; + } + isSubclassOf(sub: string, sup: string): boolean { return isSubclassOf(this, sub, sup); } @@ -10780,6 +11104,108 @@ export class Lowerer { return declareContextLocal(this.ctx, name, type, false, undefined, undefined); } + /** The body binding of a fixed-tuple rest parameter: one native slot per + * position (the flattened ABI), rebuilt into the tuple the source names. + * The empty tuple rides the empty-array representation. */ + declareRestTupleParam( + decl: ts.ParameterDeclaration, + nameNode: ts.Identifier, + elemShapes: readonly ParamShape[], + params: IrParam[], + prologue: IrStmt[], + ): void { + const loc = locOf(decl); + const name = nameNode.text; + const fn = decl.parent; + const symbol = this.checker.getSymbolAtLocation(nameNode); + const isRead = (id: ts.Identifier): boolean => + id !== nameNode && this.checker.getSymbolAtLocation(id) === symbol; + const pure = fn !== undefined && symbol !== undefined && onlyForwarded(this, fn, isRead); + // A forwarded-only tuple is never built: a tuple with optional + // positions (`Parameters<(n?: T) => R>`, whose length varies per call) + // forwards its slots, an omitted position as undefined — which a + // callee's default treats exactly as an omitted argument. Its length + // is the one read that would observe the difference. + const tupleType = + pure && + elemShapes.length > 0 && + this.mapTypeOf(this.typeOf(nameNode)) === null && + !readsLength(fn!, isRead) + ? this.forwardedSlotsTuple(elemShapes) + : this.irTypeOf(nameNode); + const slots = elemShapes.map((shape, i) => { + const slot = this.declareHiddenLocal(`%${name}.${i}`, shape.type); + params.push({ localId: slot.id, name: `%${name}.${i}`, type: shape.type }); + return slot; + }); + const body = this.declareLocal(nameNode, name, tupleType, true); + if (pure) { + this.ctx.pureTuples.set(body.id, slots); + return; + } + const init = this.tupleOfSlots(slots, tupleType, nameNode, loc); + prologue.push({ kind: "varDecl", localId: body.id, init, loc }); + } + + /** `value as never` flowing into a typed slot: `never` is assignable to + * every type, so the assertion promises the slot's type. A dynamic value + * takes the checked extraction to it, as `value as T` would. Null for + * every other form, and for numeric, dynamic and union slots, which + * keep the existing lowering. */ + private neverAssertionInto(node: ts.Expression, expected: IrType): IrExpr | null { + if (!(ts.isAsExpression(node) || ts.isTypeAssertion(node))) return null; + if (expected.kind === "f64" || expected.kind === "dyn" || expected.kind === "union") + return null; + if (!(this.checker.getTypeFromTypeNode(node.type).flags & ts.TypeFlags.Never)) return null; + const innerTs = this.typeOf(node.expression); + if (!(innerTs.flags & ts.TypeFlags.Any) && this.mapTypeOf(innerTs)?.kind !== "dyn") return null; + if ( + !canDynCheckTo( + expected, + (id) => this.shapes.get(id), + (id) => this.unions.get(id), + ) + ) + return null; + const inner = this.lowerExpr(node.expression); + if (inner.type.kind !== "dyn") return null; + return { kind: "dynCheck", value: inner, type: expected, loc: locOf(node) }; + } + + /** The tuple record of a forwarded-only rest tuple's native slots. */ + private forwardedSlotsTuple(elemShapes: readonly ParamShape[]): IrType { + const fields = elemShapes.map((shape, i) => ({ name: String(i), type: shape.type })); + fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + return { kind: "record", shapeId: this.shapes.intern(fields, true) }; + } + + /** A fresh tuple holding the given positional slots, typed `tupleType` + * (a tuple record, or the empty-array form of `[]`). */ + tupleOfSlots(slots: readonly IrLocal[], tupleType: IrType, blame: ts.Node, loc: SrcLoc): IrExpr { + if (slots.length === 0) { + if (tupleType.kind !== "array") this.badType(blame, this.typeOf(blame)); + return { kind: "arrayLit", elems: [], type: tupleType, loc }; + } + const shape = tupleType.kind === "record" ? this.shapes.get(tupleType.shapeId) : undefined; + if (!shape?.tuple || shape.fields.length !== slots.length) + this.badType(blame, this.typeOf(blame)); + return { + kind: "recordLit", + fields: slots.map((slot, i) => { + const field = shape.fields.find((f) => f.name === String(i))!; + return { + name: field.name, + value: this.coerceToExpected( + { kind: "varRef", localId: slot.id, type: slot.type, loc }, + field.type, + ), + }; + }), + type: tupleType, + loc, + }; + } + /** Declares a callee's parameter locals from its ParamShapes and builds * the DEFAULT-PARAM PROLOGUE. Required/optional/rest params bind their * symbol directly (one local of the ABI type). A defaulted param `x: T = e` @@ -10816,6 +11242,17 @@ export class Lowerer { return defaultAfterUndefined(undefinedValue, this.coerceInto(node, absent, expected)); }; decls.forEach((decl, i) => { + // A fixed-tuple rest parameter owns every remaining (flattened) + // shape, possibly none for `[]` (restTupleShapes). + if ( + decl.dotDotDotToken !== undefined && + ts.isIdentifier(decl.name) && + i === decls.length - 1 && + (shapes[i] === undefined ? shapes.length === i : shapes[i]!.restTuple === true) + ) { + this.declareRestTupleParam(decl, decl.name, shapes.slice(i), params, prologue); + return; + } const shape = shapes[i]!; if (ts.isArrayBindingPattern(decl.name) || ts.isObjectBindingPattern(decl.name)) { // Pattern parameter: one hidden ABI slot carries the source value; @@ -10958,6 +11395,21 @@ export class Lowerer { return; } const name = (decl.name as ts.Identifier).text; + if (shape.entryNarrow) { + // A typed override parameter over an inherited checked-value slot: + // the body local narrows from the dyn argument at entry. + const loc = locOf(decl); + const slot = this.declareHiddenLocal(name, shape.type); + params.push({ localId: slot.id, name, type: shape.type }); + const narrowed = this.coerceInto( + decl.name, + { kind: "varRef", localId: slot.id, type: shape.type, loc }, + shape.entryNarrow, + ); + const body = this.declareLocal(decl.name, name, shape.entryNarrow, true); + prologue.push({ kind: "varDecl", localId: body.id, init: narrowed, loc }); + return; + } if (shape.mode === "omittable" && shape.bodyType && decl.initializer) { const abi = shape.type; if (abi.kind === "dyn") { @@ -11132,6 +11584,13 @@ export class Lowerer { } if (!symbol) return null; const direct = this.resolveKey(symbol, ident); + if (direct && this.ctx.pureTuples.has(direct.id)) { + // onlyForwarded proved every read forwards; a plain read here means + // the analysis and the lowering disagree. + throw new InternalCompilerError( + `lowerer bug: forwarded-only tuple '${ident.text}' read as a value`, + ); + } if (direct) return direct; // A PARAMETER PROPERTY declares two symbols: references resolve to the // PARAMETER symbol, while the declaration's name binds the PROPERTY @@ -11161,8 +11620,8 @@ export class Lowerer { /** Lexical `this` — the enclosing method's this-param, possibly captured * through arrows (function expressions/declarations reset `this` in JS; * their bodies never see an enclosing method's binding). */ - resolveThis(): IrLocal | null { - return this.resolveKey(undefined); + resolveThis(blame?: ts.Node): IrLocal | null { + return this.resolveKey(undefined, blame); } peekLocal(ident: ts.Identifier): IrLocal | null { @@ -11802,6 +12261,8 @@ export class Lowerer { // dispatch. The general identifier-call paths have no lowering for a // promise-returning ambient global, and `import` is a keyword callee no // identifier path matches. + const webAssembly = lowerWebAssemblyCall(this, expr); + if (webAssembly !== null) return webAssembly; refuseWebAssemblyUse(this, expr.expression); return ( lowerLifetimeRegionCall(this, expr) ?? @@ -12030,6 +12491,8 @@ export class Lowerer { } lowerNew(expr: ts.NewExpression): IrExpr { + const webAssembly = lowerWebAssemblyNew(this, expr); + if (webAssembly !== null) return webAssembly; refuseWebAssemblyUse(this, expr.expression, "new "); const builtin = lowerLifetimeRegionNew(this, expr) ?? @@ -12434,13 +12897,25 @@ export class Lowerer { const callable = this.lowerBuiltinCallableValue(bi, loc); if (callable) return callable; } + // node:stream/web's exports are the web stream globals themselves: in + // JavaScript sources the same identity token as the global spelling + // (`require("stream/web").ReadableStream === globalThis.ReadableStream`). + if (bi.module === "stream/web" && isJsSourceFile(ts.getSourceFileOfNode(expr))) { + return { kind: "strLit", value: `[builtin ${bi.member}]`, type: STRING, loc }; + } // A bare `fs.constants` read (not one of the baked bits above). if (bi.member === "constants" && bi.module === "fs") return fsConstantsObject(loc); + // `stream.Readable` as a JavaScript value: the runtime's untyped + // constructor, the base a constructor function inherits. + if (isJsSourceFile(ts.getSourceFileOfNode(expr))) { + const classKind = builtinMemberClassKind(bi.module, bi.member); + if (classKind !== null) return builtinClassValue(classKind, loc); + } if (builtinModuleFnOf(this, bi.module, bi.member)) { this.unsupported( "SC1090", expr, - `library functions as values (call '${expr.getText()}' directly)`, + `library functions as values (call '${ts.getTextOfNode(expr)}' directly)`, ); } this.noLowering( @@ -12984,3 +13459,205 @@ export class Lowerer { return lowerStdlibMethodCall(this, call, access); } } + +/** Whether some read of a binding (identified by `isRead`) in `fn` takes + * its `.length`. */ +function readsLength(fn: ts.Node, isRead: (id: ts.Identifier) => boolean): boolean { + let found = false; + ts.walkPreorder(fn, (n) => { + if (found) return "stop"; + if (!ts.isIdentifier(n) || !isRead(n)) return undefined; + let top: ts.Node = n; + while ( + top.parent && + (ts.isParenthesizedExpression(top.parent) || + ts.isAsExpression(top.parent) || + ts.isTypeAssertion(top.parent) || + ts.isSatisfiesExpression(top.parent) || + ts.isNonNullExpression(top.parent)) + ) + top = top.parent; + const p = top.parent; + if (p && ts.isPropertyAccessExpression(p) && p.expression === top && p.name.text === "length") + found = true; + return undefined; + }); + return found; +} + +/** A union of record layouts flowing into one record slot: a function + * returning differently-shaped object literals (`{ ok: false }` and + * `{ ok: true, size }`, whose union the checker normalizes to arms with + * `size?: undefined`) into its declared result type. Each record arm + * width-lifts into the slot on its own tag; unit arms take the stranded + * TypeError (the checker excluded them). Null when some record arm does + * not lift, or when there is a non-record, non-unit arm. */ +function unionArmsWidthLift( + lowerer: Lowerer, + expr: IrExpr, + expected: IrType & { kind: "record" }, +): IrExpr | null { + if (expr.type.kind !== "union") return null; + const unionId = expr.type.unionId; + const arms = lowerer.unions.get(unionId)?.arms ?? []; + const lifts: (WidthLift | null)[] = []; + let records = 0; + for (const arm of arms) { + if (isUnitType(arm)) { + lifts.push(null); + continue; + } + if (arm.kind !== "record") return null; + const lift = lowerer.widthLiftPlan(arm, expected); + if (!lift) return null; + lifts.push(lift); + records++; + } + if (records < 2) return null; + const loc = expr.loc; + const local = lowerer.declareHiddenLocal("%unionLift", expr.type); + const ref = (): IrExpr => ({ kind: "varRef", localId: local.id, type: expr.type, loc }); + let result: IrExpr | null = null; + for (let tag = arms.length - 1; tag >= 0; tag--) { + const lift = lifts[tag]; + const arm = arms[tag]!; + const branch: IrExpr = lift + ? lowerer.applyWidthLift( + lift, + { kind: "unionNarrow", unionId, tag, value: ref(), type: arm, loc }, + expected, + loc, + ) + : nodeThrowExpr( + 1, + "", + `${arm.kind === "nullT" ? "null" : "undefined"} is not representable in a '${lowerer.fmt(expected)}' slot (a value narrowed or asserted past the type still held it)`, + expected, + loc, + ); + result = + result === null + ? branch + : { + kind: "ternary", + cond: { + kind: "unionIsTag", + unionId, + tag, + negated: false, + value: ref(), + type: BOOL, + loc, + }, + then: branch, + else_: result, + type: expected, + loc, + }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: local.id, init: expr, loc }], + result: result!, + type: expected, + loc, + }; +} + +/** A callback passed to a generic call without a return annotation, whose + * returns the checker unified into a narrower result type: `trace(name, + * async () => (cond ? items : items.map((i) => ({ ...i, sum }))))` infers + * `T` from the narrower `Item[]`, though at run time the extended objects + * keep `sum`. Converting such a return into the inferred type copies the + * records and drops the members the narrower type lacks, which Node keeps + * and any later wider view (`Promise`) reads back. Refuse + * that conversion by name rather than lose the members silently; an + * annotation on the callback's return type or on the call's type argument + * names a type that keeps them. */ +function refuseInferredCallbackDrops( + lowerer: Lowerer, + node: ts.Node, + actual: IrType, + expected: IrType, +): void { + if (typeEquals(actual, expected)) return; + const structured = (t: IrType): boolean => + t.kind === "record" || t.kind === "array" || t.kind === "promise"; + if (!structured(actual) || !structured(expected)) return; + let site: ts.Node = node; + while (yieldsOperand(site)) site = site.parent!; + let fn: ts.Node | undefined; + if (ts.isReturnStatement(site.parent) && site.parent.expression === site) { + fn = site.parent.parent; + while (fn && !ts.isFunctionLike(fn)) fn = fn.parent; + } else if (ts.isArrowFunction(site.parent) && site.parent.body === site) { + fn = site.parent; + } + if (!fn || !(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn)) || fn.type) return; + let arg: ts.Node = fn; + while (ts.isParenthesizedExpression(arg.parent)) arg = arg.parent; + const call = arg.parent; + if (!(ts.isCallExpression(call) || ts.isNewExpression(call))) return; + if (!call.arguments?.nodes.includes(arg as ts.Expression)) return; + const dropped = droppedMembers(lowerer, actual, expected, new Set()); + if (dropped.length === 0) return; + lowerer.unsupported( + "SC1090", + node, + `returning '${lowerer.fmt(actual)}' from a callback whose result the checker infers as the narrower '${lowerer.fmt(expected)}' (the conversion would drop ${dropped.map((d) => `'${d}'`).join(", ")}, which the object keeps at run time — annotate the callback's return type or the call's type argument with a type that declares them)`, + ); +} + +/** True when the value of `node`'s parent can be `node`'s own value: + * parentheses, `await`, `satisfies`, a non-null assertion, a conditional's + * branch, either operand of `??`, `||` and `&&`, and the right operand of a + * comma. */ +function yieldsOperand(node: ts.Node): boolean { + const p = node.parent; + if (p === undefined) return false; + if ( + ts.isParenthesizedExpression(p) || + ts.isAwaitExpression(p) || + ts.isSatisfiesExpression(p) || + ts.isNonNullExpression(p) + ) + return true; + if (ts.isConditionalExpression(p)) return p.whenTrue === node || p.whenFalse === node; + if (ts.isBinaryExpression(p)) { + const op = p.operatorToken.kind; + if ( + op === ts.SyntaxKind.QuestionQuestionToken || + op === ts.SyntaxKind.BarBarToken || + op === ts.SyntaxKind.AmpersandAmpersandToken + ) + return true; + return op === ts.SyntaxKind.CommaToken && p.right === node; + } + return false; +} + +/** Member names a structural conversion from `from` into `to` drops: + * declared fields of a source record the target record lacks, through + * array elements and promise payloads. */ +function droppedMembers(lowerer: Lowerer, from: IrType, to: IrType, seen: Set): string[] { + if (typeEquals(from, to)) return []; + if (from.kind === "array" && to.kind === "array") + return droppedMembers(lowerer, from.elem, to.elem, seen); + if (from.kind === "promise" && to.kind === "promise") + return droppedMembers(lowerer, from.inner, to.inner, seen); + if (from.kind !== "record" || to.kind !== "record") return []; + const key = `${from.shapeId}>${to.shapeId}`; + if (seen.has(key)) return []; + seen.add(key); + const source = lowerer.shapes.get(from.shapeId); + const target = lowerer.shapes.get(to.shapeId); + if (!source || !target || source.tuple || target.tuple || target.indexValue) return []; + const out: string[] = []; + for (const f of source.fields) { + if (f.name.startsWith("%")) continue; + const t = target.fields.find((x) => x.name === f.name); + if (!t) out.push(f.name); + else out.push(...droppedMembers(lowerer, f.type, t.type, seen).map((n) => `${f.name}.${n}`)); + } + return out; +} diff --git a/packages/compiler/src/frontend/lowering/module-member-this.ts b/packages/compiler/src/frontend/lowering/module-member-this.ts index 205cc63e9..8d1592b47 100644 --- a/packages/compiler/src/frontend/lowering/module-member-this.ts +++ b/packages/compiler/src/frontend/lowering/module-member-this.ts @@ -25,24 +25,228 @@ function exportedFunction(decl: ts.Node): ts.Node | null { return null; } -/** Whether a function's own body reads `this` (arrows share it; other - * functions and classes have their own). */ -function readsOwnThis(fn: ts.Node): boolean { - let found = false; +/** Whether `node` is `f.apply(this, ...)` or `f.call(this, ...)`, which + * passes the receiver on to `f`. */ +function forwardsThis(node: ts.Node): node is ts.CallExpression & { + expression: ts.PropertyAccessExpression; +} { + if (!ts.isCallExpression(node)) return false; + const callee = node.expression; + if (!ts.isPropertyAccessExpression(callee) || !ts.isIdentifier(callee.name)) return false; + if (callee.name.text !== "apply" && callee.name.text !== "call") return false; + return node.arguments.nodes[0]?.kind === ts.SyntaxKind.ThisKeyword; +} + +/* The arguments a call gave a function's parameters, while the analysis + * follows the call into the function: each with the bindings of its own + * scope. */ +type Bindings = ReadonlyMap; +const noBindings: Bindings = new Map(); + +// How many calls and functions deep the analysis follows a forwarded +// `this`: enough for a wrapper over a function factory, as Effect's +// `dual(3, body).apply(this, arguments)`. +const maxDepth = 6; + +/** Whether `fn`'s own body uses `this` only to pass it on with `.apply` or + * `.call` to functions that ignore it (and so also when it doesn't use + * `this` at all). */ +function ignoresOwnThis(lowerer: Lowerer, fn: ts.Node, bindings: Bindings, depth: number): boolean { + let ignores = true; const visit = (node: ts.Node): void => { - if (found) return; + if (!ignores) return; if (node.kind === ts.SyntaxKind.ThisKeyword) { - found = true; + ignores = false; return; } if ((ts.isFunctionLike(node) && !ts.isArrowFunction(node)) || ts.isClassLikeDeclaration(node)) return; - node.forEachChild(visit); + if (forwardsThis(node)) { + if (!ignoresThis(lowerer, node.expression.expression, bindings, depth + 1)) { + ignores = false; + return; + } + visit(node.expression.expression); + for (const arg of node.arguments.nodes.slice(1)) visit(arg); + return; + } + ts.forEachChild(node, visit); }; - fn.forEachChild(visit); + ts.forEachChild(fn, visit); + return ignores; +} + +/** The function `name` provably holds: a function declaration, a const + * initialized with a function, or an import of one. */ +function functionOfName( + lowerer: Lowerer, + name: ts.Identifier, + bindings: Bindings, +): { fn: ts.Node | null; bound?: { expr: ts.Expression; bindings: Bindings } } { + let symbol = lowerer.checker.getSymbolAtLocation(name); + if (symbol === undefined) return { fn: null }; + const bound = bindings.get(symbol); + if (bound) return { fn: null, bound }; + if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) + symbol = lowerer.checker.getAliasedSymbol(symbol); + const decls = symbol.declarations; + if (decls.length !== 1) return { fn: null }; + const decl = decls[0]!; + if (ts.isFunctionDeclaration(decl)) return { fn: decl }; + if ( + ts.isVariableDeclaration(decl) && + decl.initializer && + ts.isVariableDeclarationList(decl.parent) && + (decl.parent.flags & ts.NodeFlags.Const) !== 0 + ) { + let init: ts.Expression = decl.initializer; + while (ts.isParenthesizedExpression(init)) init = init.expression; + if (ts.isFunctionExpression(init) || ts.isArrowFunction(init)) return { fn: init }; + } + return { fn: null }; +} + +/** + * Whether `fn` (with its nested functions) may change what its parameter + * `param` holds: an assignment, update, destructuring or for-in/for-of + * target that writes it, a declaration of the same name, a write to an + * element of `arguments` (which aliases parameters outside strict mode), or + * a call of `eval`. + */ +function mayRebind(lowerer: Lowerer, fn: ts.Node, param: ts.Identifier): boolean { + const symbol = lowerer.checker.getSymbolAtLocation(param); + const refersTo = (node: ts.Node): boolean => + ts.isIdentifier(node) && + (node.text === param.text || lowerer.checker.getSymbolAtLocation(node) === symbol); + // Whether writing `target` writes the parameter: the name itself, or the + // name inside a destructuring pattern. + const writes = (target: ts.Node): boolean => { + while (ts.isParenthesizedExpression(target)) target = target.expression; + if (refersTo(target)) return true; + if ( + ts.isElementAccessExpression(target) && + ts.isIdentifier(target.expression) && + target.expression.text === "arguments" + ) + return true; + if (ts.isArrayLiteralExpression(target)) return target.elements.nodes.some(writes); + if (ts.isObjectLiteralExpression(target)) + return target.properties.nodes.some((p) => + ts.isShorthandPropertyAssignment(p) + ? refersTo(p.name) + : ts.isPropertyAssignment(p) + ? writes(p.initializer) + : ts.isSpreadAssignment(p) && writes(p.expression), + ); + if (ts.isSpreadElement(target)) return writes(target.expression); + // A default in a pattern: `[body = other] = values`. + if (ts.isBinaryExpression(target) && target.operatorToken.kind === ts.SyntaxKind.EqualsToken) + return writes(target.left); + return false; + }; + const declaresName = (node: ts.Node): boolean => { + const name = (node as { name?: ts.Node }).name; + return name !== undefined && ts.isIdentifier(name) && name.text === param.text; + }; + let found = false; + const visit = (node: ts.Node): void => { + if (found) return; + if ( + (ts.isBinaryExpression(node) && + node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && + writes(node.left)) || + ((ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) && + (node.operator === ts.SyntaxKind.PlusPlusToken || + node.operator === ts.SyntaxKind.MinusMinusToken) && + writes(node.operand)) || + ((ts.isForOfStatement(node) || ts.isForInStatement(node)) && + !ts.isVariableDeclarationList(node.initializer) && + writes(node.initializer)) || + ((ts.isVariableDeclaration(node) || + ts.isBindingElement(node) || + ts.isParameter(node) || + ts.isFunctionDeclaration(node) || + ts.isClassDeclaration(node)) && + declaresName(node)) || + (ts.isCallExpression(node) && + ts.isIdentifier(node.expression) && + node.expression.text === "eval") + ) { + found = true; + return; + } + ts.forEachChild(node, visit); + }; + // The function's own parameters declare the name; their defaults can write it. + const params = (fn as { parameters?: ts.NodeArray }).parameters; + for (const p of params?.nodes ?? []) if (p.initializer) visit(p.initializer); + const body = (fn as { body?: ts.Node }).body; + if (body !== undefined) visit(body); return found; } +/** The expressions `fn` returns from its own body. */ +function returnedExpressions(fn: ts.Node): ts.Expression[] | null { + const body = (fn as { body?: ts.Node }).body; + if (body === undefined) return null; + if (!ts.isBlock(body)) return [body as ts.Expression]; + const result: ts.Expression[] = []; + const visit = (node: ts.Node): void => { + if (ts.isFunctionLike(node) || ts.isClassLikeDeclaration(node)) return; + if (ts.isReturnStatement(node) && node.expression) result.push(node.expression); + ts.forEachChild(node, visit); + }; + ts.forEachChild(body, visit); + return result; +} + +/** Whether the function `expr` evaluates to provably ignores the `this` + * it is called with. */ +function ignoresThis( + lowerer: Lowerer, + expr: ts.Expression, + bindings: Bindings, + depth: number, +): boolean { + if (depth > maxDepth) return false; + while (ts.isParenthesizedExpression(expr)) expr = expr.expression; + if (ts.isArrowFunction(expr)) return true; + if (ts.isFunctionExpression(expr)) return ignoresOwnThis(lowerer, expr, bindings, depth); + if (ts.isIdentifier(expr)) { + const { fn, bound } = functionOfName(lowerer, expr, bindings); + if (bound) return ignoresThis(lowerer, bound.expr, bound.bindings, depth + 1); + if (fn === null) return false; + return ts.isArrowFunction(fn) || ignoresOwnThis(lowerer, fn, noBindings, depth + 1); + } + // A call of a known function: what it returns, with its parameters bound + // to this call's arguments. + if (ts.isCallExpression(expr) && ts.isIdentifier(expr.expression)) { + const { fn } = functionOfName(lowerer, expr.expression, bindings); + if (fn === null) return false; + const params = (fn as { parameters?: ts.NodeArray }).parameters; + const inner = new Map(bindings); + for (const [i, param] of (params?.nodes ?? []).entries()) { + if (!ts.isIdentifier(param.name) || param.dotDotDotToken) return false; + const arg = expr.arguments.nodes[i]; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (arg === undefined || ts.isSpreadElement(arg) || symbol === undefined) return false; + // The parameter holds the argument for as long as the function runs. + if (mayRebind(lowerer, fn, param.name)) return false; + inner.set(symbol, { expr: arg, bindings }); + } + const returned = returnedExpressions(fn); + return returned !== null && returned.every((r) => ignoresThis(lowerer, r, inner, depth + 1)); + } + return false; +} + +/** Whether calling `fn` with another `this` than Node's can change what it + * does: it reads `this`, or passes it on to a function that may. */ +function readsOwnThis(lowerer: Lowerer, fn: ts.Node): boolean { + return !ignoresOwnThis(lowerer, fn, noBindings, 0); +} + export function refuseModuleMemberThis( lowerer: Lowerer, call: ts.CallExpression, @@ -54,7 +258,7 @@ export function refuseModuleMemberThis( if (symbol === undefined) return; for (const decl of symbol.declarations) { const fn = exportedFunction(decl); - if (fn === null || !readsOwnThis(fn)) continue; + if (fn === null || !readsOwnThis(lowerer, fn)) continue; lowerer.unsupported( "SC1090", call, diff --git a/packages/compiler/src/frontend/lowering/nan-coded-reads.ts b/packages/compiler/src/frontend/lowering/nan-coded-reads.ts index 4270457d5..65efe5b2a 100644 --- a/packages/compiler/src/frontend/lowering/nan-coded-reads.ts +++ b/packages/compiler/src/frontend/lowering/nan-coded-reads.ts @@ -365,20 +365,22 @@ export class NanCodedReads { private removalReason(slot: Slot): string { const badSource = slot.sources.find((s) => s === null || this.sourceInfo(s) === null); if (badSource !== undefined) - return `source ${badSource ? badSource.getText().slice(0, 60) : ""}`; + return `source ${badSource ? ts.getTextOfNode(badSource).slice(0, 60) : ""}`; const nan = this.genuineNaN.has(slot); const badUse = slot.uses.find((u) => !this.useSafe(u) && (nan || !this.reboxConsumer(u))); - if (badUse) - return `${nan ? "nan-use" : "use"} ${transparentParent(badUse).parent?.getText().slice(0, 80) ?? "?"}`; + if (badUse) { + const parent = transparentParent(badUse).parent; + return `${nan ? "nan-use" : "use"} ${parent === undefined ? "?" : ts.getTextOfNode(parent).slice(0, 80)}`; + } return "?"; } private debugReport(reached: Set): void { const name = (slot: Slot): string => { const decl = this.host.declarationOf(slot.symbol); - const sf = decl?.getSourceFile(); - const where = sf && decl ? `${sf.fileName}:${decl.getStart()}` : "?"; - return `${slot.kind} ${slot.symbol.name} @ ${where}`; + const sf = decl === undefined ? undefined : ts.getSourceFileOfNode(decl); + const where = sf && decl ? `${sf.fileName}:${ts.tokenStartOfNode(decl)}` : "?"; + return `${slot.kind} ${ts.symbolName(slot.symbol)} @ ${where}`; }; for (const slot of reached) { const ok = this.eligible.has(slot); @@ -483,7 +485,11 @@ export class NanCodedReads { return true; } const decl = this.host.declarationOf(symbol); - if (!decl || isJsSourceFile(decl.getSourceFile()) || decl.getSourceFile().isDeclarationFile) + if ( + !decl || + isJsSourceFile(ts.getSourceFileOfNode(decl)) || + ts.getSourceFileOfNode(decl).isDeclarationFile + ) return false; let name: ts.Identifier; if (ts.isParameter(decl)) { @@ -565,7 +571,7 @@ export class NanCodedReads { !nonNegativeIntegerLiteral(decl.initializer) || !ts.isVariableDeclarationList(decl.parent) || !(decl.parent.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) || - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return false; const name = decl.name; @@ -685,7 +691,7 @@ export class NanCodedReads { !(decl.parent.flags & ts.NodeFlags.Const) || // A module binding can escape through an export. (ts.isVariableStatement(decl.parent.parent) && ts.isSourceFile(decl.parent.parent.parent)) || - isJsSourceFile(decl.getSourceFile()) || + isJsSourceFile(ts.getSourceFileOfNode(decl)) || this.host.bytesElemOf(decl.name) === null ) return null; @@ -755,7 +761,7 @@ export class NanCodedReads { if (!decl || (!ts.isFunctionDeclaration(decl) && !ts.isMethodDeclaration(decl))) return null; if (!decl.body || decl.asteriskToken || decl.typeParameters?.nodes || !decl.name) return null; if (!ts.isIdentifier(decl.name)) return null; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return null; const flags = ts.getCombinedModifierFlags(decl); if (flags & (ts.ModifierFlags.Async | ts.ModifierFlags.Abstract | ts.ModifierFlags.Ambient)) @@ -831,7 +837,7 @@ export class NanCodedReads { private scanBindingSlot(symbol: ts.Symbol): BindingSlot | null { const decl = this.host.declarationOf(symbol); if (!decl) return null; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (isJsSourceFile(sf) || sf.isDeclarationFile) return null; let nameNode: ts.Identifier; const slot: BindingSlot = { @@ -1362,7 +1368,7 @@ export class NanCodedReads { decl.initializer && ts.isVariableDeclarationList(decl.parent) && decl.parent.flags & ts.NodeFlags.Const && - !isJsSourceFile(decl.getSourceFile()) + !isJsSourceFile(ts.getSourceFileOfNode(decl)) ) return this.plainNumber(decl.initializer, depth + 1); return null; diff --git a/packages/compiler/src/frontend/lowering/narrowing-stability.ts b/packages/compiler/src/frontend/lowering/narrowing-stability.ts index 01801470d..1c991c6ea 100644 --- a/packages/compiler/src/frontend/lowering/narrowing-stability.ts +++ b/packages/compiler/src/frontend/lowering/narrowing-stability.ts @@ -191,7 +191,8 @@ export class NarrowingStability { const checker = this.lowerer.checker; const signature = checker.getResolvedSignature(call); const declaration = signature ? signature.declaration : undefined; - if (!declaration || !this.lowerer.isStdlibFile(declaration.getSourceFile())) return true; + if (!declaration || !this.lowerer.isStdlibFile(ts.getSourceFileOfNode(declaration))) + return true; const callee = skipOuter(call.expression); if ( ts.isPropertyAccessExpression(callee) && @@ -287,11 +288,11 @@ export class NarrowingStability { const last = chain.links[chain.links.length - 1]; const refKey = last ? `.${last.name}` : (chain.root as ts.Identifier).text; const candidates = facts.refs.get(refKey) ?? []; - const useStart = node.getStart(); + const useStart = ts.tokenStartOfNode(node); let start = -1; for (const candidate of candidates) { if (candidate === node) continue; - const candidateStart = candidate.getStart(); + const candidateStart = ts.tokenStartOfNode(candidate); if (candidateStart >= useStart) break; if (referenceKey(candidate) !== chain.key) continue; start = candidateStart; @@ -313,7 +314,7 @@ export class NarrowingStability { // between. let end = useStart; for (const loop of facts.loops) { - const loopStart = loop.getStart(); + const loopStart = ts.tokenStartOfNode(loop); if (loopStart >= start && loopStart <= useStart && loop.end >= node.end) end = Math.max(end, loop.end); } @@ -376,7 +377,9 @@ export class NarrowingStability { if (decl !== undefined && ts.isVariableDeclaration(decl)) { if (decl.initializer !== undefined && this.mayBeStale(decl.initializer)) result = true; else { - for (const write of this.writesIn(decl.getSourceFile()).get(symbol.name) ?? []) { + for (const write of this.writesIn(ts.getSourceFileOfNode(decl)).get( + ts.symbolName(symbol), + ) ?? []) { const assignment = write.parent; if ( ts.isBinaryExpression(assignment) && @@ -430,7 +433,7 @@ export class NarrowingStability { ts.isParameter(decl)); if (decl && mutableBinding) { const home = flowBoundary(decl); - const writes = this.writesIn(decl.getSourceFile()).get(symbol.name) ?? []; + const writes = this.writesIn(ts.getSourceFileOfNode(decl)).get(ts.symbolName(symbol)) ?? []; for (const write of writes) { if (flowBoundary(write) === home) continue; if (this.lowerer.resolveValueSymbol(write) === symbol) { @@ -497,7 +500,7 @@ export class NarrowingStability { if (name === "toString" || name === "valueOf" || name === "toJSON") facts.conversion = true; if (ts.isComputedPropertyName(node.name)) { - const text = node.name.expression.getText(); + const text = ts.getTextOfNode(node.name.expression); if (text.includes("toPrimitive")) facts.conversion = true; if (text.includes("iterator") || text.includes("asyncIterator")) facts.iteration = true; } @@ -711,7 +714,7 @@ export class NarrowingStability { const symbol = type.getSymbol(); return ( symbol !== undefined && - BUILTIN_COLLECTIONS.has(symbol.name) && + BUILTIN_COLLECTIONS.has(ts.symbolName(symbol)) && this.lowerer.isStdlibSymbol(symbol) ); } @@ -1062,7 +1065,7 @@ function memberUse( function flowBoundary(node: ts.Node): ts.Node { let n: ts.Node | undefined = node.parent; while (n && !isFlowBoundary(n)) n = n.parent; - return n ?? node.getSourceFile(); + return n ?? ts.getSourceFileOfNode(node); } function isFlowBoundary(node: ts.Node): boolean { diff --git a/packages/compiler/src/frontend/lowering/native-ffi.ts b/packages/compiler/src/frontend/lowering/native-ffi.ts index c9ff3300d..0f932ba93 100644 --- a/packages/compiler/src/frontend/lowering/native-ffi.ts +++ b/packages/compiler/src/frontend/lowering/native-ffi.ts @@ -393,7 +393,7 @@ export function lowerFfiCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr (decl) => ts.isFunctionDeclaration(decl) && decl.body === undefined && - libraryCallbackOwnsFile(lowerer, decl.getSourceFile()), + libraryCallbackOwnsFile(lowerer, ts.getSourceFileOfNode(decl)), ); if (callbackShaped) { lowerer.pushDiag( @@ -429,13 +429,13 @@ export function lowerFfiCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr if (lowerer.libraryCallbacks) { const declarations = symbol.declarations; const programDeclarations = declarations.filter((decl) => - libraryCallbackOwnsFile(lowerer, decl.getSourceFile()), + libraryCallbackOwnsFile(lowerer, ts.getSourceFileOfNode(decl)), ); const programAmbient = programDeclarations.length > 0 && programDeclarations.every( (decl) => - decl.getSourceFile().isDeclarationFile || + ts.getSourceFileOfNode(decl).isDeclarationFile || (ts.getCombinedModifierFlags(decl as ts.Declaration) & ts.ModifierFlags.Ambient) !== 0, ); diff --git a/packages/compiler/src/frontend/lowering/object-factory-new.ts b/packages/compiler/src/frontend/lowering/object-factory-new.ts index ddd8d6d56..bcb675692 100644 --- a/packages/compiler/src/frontend/lowering/object-factory-new.ts +++ b/packages/compiler/src/frontend/lowering/object-factory-new.ts @@ -41,7 +41,7 @@ function isObjectFactoryNew(lowerer: Lowerer, expr: ts.NewExpression): boolean { !owner || !declaration?.body || !bindingNeverReassigned(lowerer, symbol, owner) || - !isJsSourceFile(declaration.getSourceFile()) || + !isJsSourceFile(ts.getSourceFileOfNode(declaration)) || declaration.asteriskToken || declaration.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ) diff --git a/packages/compiler/src/frontend/lowering/private-brands.ts b/packages/compiler/src/frontend/lowering/private-brands.ts new file mode 100644 index 000000000..8507cc81a --- /dev/null +++ b/packages/compiler/src/frontend/lowering/private-brands.ts @@ -0,0 +1,328 @@ +import * as ts from "../ts7/adapter.js"; +import { + BOOL, + DYN, + RUNTIME_ERROR_CLASSES, + STRING, + typeKey, + type IrExpr, + type IrFunction, + type IrStmt, + type IrType, + type SrcLoc, +} from "../../ir/ir.js"; +import { helperLoc, strLit, varRef } from "../../ir/build.js"; +import { isJsSourceFile } from "../program.js"; +import type { Lowerer } from "./lowerer.js"; +import type { ClassInfo } from "./lower-classes.js"; + +/** How a brand helper uses its receiver: `in` answers `#x in v`, `read` and + * `write` return the receiver as the declaring class for a field access. */ +type BrandUse = "in" | "read" | "write"; + +const PREFIX = "%private."; + +/** A function-local JavaScript class with private names. Its prototype + * methods and accessors exist once per evaluation and check their receiver's + * evaluation, so a method or accessor taken from an instance is the function + * of that instance's own evaluation. */ +export function perEvaluationPrivateNames(info: ClassInfo): boolean { + const declaration = info.localClass ? info.decl : null; + return ( + !!declaration && + isJsSourceFile(ts.getSourceFileOfNode(declaration)) && + declaration.members.nodes.some( + (member) => member.name !== undefined && ts.isPrivateIdentifier(member.name), + ) + ); +} + +/** The function-local class declaring a private name, when its private names + * need a per-evaluation brand check: the nearest enclosing class that + * declares the name, as JavaScript scoping resolves it. */ +export function localPrivateBrandClass( + lowerer: Lowerer, + priv: ts.PrivateIdentifier, +): { info: ClassInfo; decl: ts.ClassDeclaration & { name: ts.Identifier } } | null { + for (let node: ts.Node | undefined = priv.parent; node; node = node.parent) { + if (!ts.isClassDeclaration(node) && !ts.isClassExpression(node)) continue; + const declares = node.members.nodes.some((member) => { + const name = (member as { name?: ts.PropertyName }).name; + return name !== undefined && ts.isPrivateIdentifier(name) && name.text === priv.text; + }); + if (!declares) continue; + if (!ts.isClassDeclaration(node) || !node.name) return null; + const symbol = lowerer.checker.getSymbolAtLocation(node.name); + const info = + lowerer.currentClass?.decl === node + ? lowerer.currentClass + : symbol + ? lowerer.classBySymbol.get(symbol) + : undefined; + if (!info?.localClass) return null; + return { info, decl: node as ts.ClassDeclaration & { name: ts.Identifier } }; + } + return null; +} + +/** Each evaluation of a function-local class creates fresh private names, + * while one compiled class serves every evaluation. Its JavaScript prototype + * methods are closures per evaluation whose `this` is a checked value, so a + * private name on `this` is present exactly when the receiver is an instance + * of the class (or a subclass) whose `%classEnvironment` field holds the + * class value of the closure's own evaluation. The class's own binding names + * that value (lowerClassExpressionInfo accepts only named declarations, whose + * binding the checker keeps from being assigned). */ +export function checkedPrivateReceiver( + lowerer: Lowerer, + info: ClassInfo, + decl: ts.ClassDeclaration & { name: ts.Identifier }, + access: ts.PropertyAccessExpression, + receiver: IrExpr, + loc: SrcLoc, +): IrExpr { + const use: BrandUse = isPrivateWriteTarget(access) ? "write" : "read"; + return brandCall(lowerer, info, decl, use, access.name.text, receiver, loc); +} + +/** Whether a checked value is a primitive: `#x in v` throws for those + * before any brand lookup. */ +export function primitiveOperand(value: IrExpr, loc: SrcLoc): IrExpr { + return (["nullish", "number", "string", "boolean", "bigint", "symbol"] as const) + .map((test): IrExpr => ({ kind: "dynTest", test, value, type: BOOL, loc })) + .reduce((left, right) => ({ kind: "logical", op: "||", left, right, type: BOOL, loc })); +} + +/** Node's TypeError message for `#x in v` on a primitive, which spells the + * value as String(v) does. */ +export function privateInMessage(name: string, value: IrExpr, loc: SrcLoc): IrExpr { + return { + kind: "strConcat", + left: strLit(`Cannot use 'in' operator to search for '${name}' in `, loc), + right: { kind: "libCall", fn: "dyn.stringConstructor", args: [value], type: STRING, loc }, + type: STRING, + loc, + }; +} + +/** A receiver checkedPrivateReceiver built for a field write. */ +export function isPrivateWriteReceiver(expr: IrExpr): boolean { + return expr.kind === "call" && expr.callee.startsWith(`${PREFIX}write:`); +} + +/** `#x in v` for a checked value inside a function-local class: the brand + * test above, after the TypeError a primitive operand throws. */ +export function checkedPrivateIn( + lowerer: Lowerer, + info: ClassInfo, + decl: ts.ClassDeclaration & { name: ts.Identifier }, + name: string, + receiver: IrExpr, + loc: SrcLoc, +): IrExpr { + return brandCall(lowerer, info, decl, "in", name, receiver, loc); +} + +function brandCall( + lowerer: Lowerer, + info: ClassInfo, + decl: ts.ClassDeclaration & { name: ts.Identifier }, + use: BrandUse, + name: string, + receiver: IrExpr, + loc: SrcLoc, +): IrExpr { + const className = info.def.name; + const classType: IrType = { kind: "classval", className }; + const resultType: IrType = use === "in" ? BOOL : { kind: "object", className }; + const helper = `${PREFIX}${use}:${className}:${name}`; + if (!lowerer.valueHelpers.has(helper)) { + const at = helperLoc(loc); + lowerer.liftedFns.push({ + name: helper, + params: [ + { localId: "value.0", name: "value", type: DYN }, + { localId: "class.0", name: "class", type: classType }, + ], + returnType: resultType, + locals: [ + { id: "value.0", name: "value", type: DYN, mutable: false }, + { id: "class.0", name: "class", type: classType, mutable: false }, + ], + // finalizePrivateBrands fills the body once every class is collected, + // including subclasses declared after this use. + body: [], + loc: at, + }); + lowerer.valueHelpers.set(helper, helper); + } + const evaluation = lowerer.classValueRef(info, decl.name); + return { kind: "call", callee: helper, args: [receiver, evaluation], type: resultType, loc }; +} + +/** A plain assignment, a destructuring target, or a for-in/of target writes + * the field. Compound assignments and updates read it first, and Node + * reports the failed read. */ +function isPrivateWriteTarget(access: ts.PropertyAccessExpression): boolean { + let node: ts.Node = access; + for (;;) { + const parent = node.parent; + if (ts.isParenthesizedExpression(parent)) { + node = parent; + continue; + } + if (ts.isBinaryExpression(parent)) + return parent.left === node && parent.operatorToken.kind === ts.SyntaxKind.EqualsToken; + if (ts.isForOfStatement(parent) || ts.isForInStatement(parent)) + return parent.initializer === node; + // Inside a destructuring pattern: keep walking to the assignment. + if ( + ts.isArrayLiteralExpression(parent) || + ts.isSpreadElement(parent) || + (ts.isPropertyAssignment(parent) && parent.initializer === node) || + ts.isObjectLiteralExpression(parent) || + ts.isSpreadAssignment(parent) + ) { + node = parent; + continue; + } + return false; + } +} + +/** Fill the brand helpers: one branch per class a checked reference to an + * instance can be boxed under (the declarer, its subclasses, and its + * ancestors when the instance is at or below the declarer). */ +export function finalizePrivateBrands(lowerer: Lowerer, functions: IrFunction[]): void { + for (const fn of functions) { + if (!fn.name.startsWith(PREFIX) || fn.body.length > 0) continue; + // `%private.::<#name>`; class names may contain colons. + const spec = fn.name.slice(PREFIX.length); + const use = spec.slice(0, spec.indexOf(":")) as BrandUse; + const className = spec.slice(use.length + 1, spec.lastIndexOf(":")); + const name = spec.slice(spec.lastIndexOf(":") + 1); + const loc = fn.loc; + const value = varRef("value.0", DYN, loc); + const evaluation = varRef("class.0", { kind: "classval", className }, loc); + const ownerType: IrType = { kind: "object", className }; + const body: IrStmt[] = []; + let matches = 0; + for (const candidate of lowerer.classes.values()) { + const candidateName = candidate.def.name; + if (RUNTIME_ERROR_CLASSES.has(candidateName)) continue; + const below = candidateName === className || lowerer.isSubclassOf(candidateName, className); + const above = lowerer.isSubclassOf(className, candidateName); + if (!below && !above) continue; + const candidateType: IrType = { kind: "object", className: candidateName }; + const checked: IrExpr = { kind: "dynCheck", value, type: candidateType, loc }; + const local = `receiver.${matches++}`; + fn.locals.push({ id: local, name: "receiver", type: ownerType, mutable: false }); + const receiver = varRef(local, ownerType, loc); + const sameEvaluation: IrExpr = { + kind: "bin", + op: "===", + left: { + kind: "fieldGet", + obj: receiver, + className, + field: `%classEnvironment:${className}`, + type: evaluation.type, + loc, + }, + right: evaluation, + type: BOOL, + loc, + }; + const match = (typed: IrExpr): IrStmt[] => [ + { kind: "varDecl", localId: local, init: typed, loc }, + { + kind: "if", + cond: sameEvaluation, + then: [ + { + kind: "return", + value: use === "in" ? { kind: "boolLit", value: true, type: BOOL, loc } : receiver, + loc, + }, + ], + else_: null, + loc, + }, + ]; + const then: IrStmt[] = below + ? match( + candidateName === className + ? checked + : { kind: "upcast", value: checked, type: ownerType, loc }, + ) + : [ + { + kind: "if", + cond: { kind: "instanceOf", value: checked, className, type: BOOL, loc }, + then: match({ kind: "downcast", value: checked, type: ownerType, loc }), + else_: null, + loc, + }, + ]; + body.push({ + kind: "if", + cond: { + kind: "libCall", + fn: "dyn.typedRefIs", + args: [value, strLit(typeKey(candidateType), loc)], + type: BOOL, + loc, + }, + then, + else_: null, + loc, + }); + } + const typeError = (message: IrExpr): IrStmt => ({ + kind: "throw", + value: { + kind: "libCall", + fn: "error.new", + args: [message], + type: { kind: "object", className: "%TypeError" }, + loc, + }, + loc, + }); + if (use === "in") { + body.push( + { + kind: "if", + cond: primitiveOperand(value, loc), + then: [typeError(privateInMessage(name, value, loc))], + else_: null, + loc, + }, + { kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc }, + ); + } else { + const verb = use === "read" ? "reading" : "setting"; + const action = use === "read" ? "Cannot read properties" : "Cannot set properties"; + for (const unit of ["undefined", "null"] as const) { + body.push({ + kind: "if", + cond: { kind: "dynTest", test: unit, value, type: BOOL, loc }, + then: [typeError(strLit(`${action} of ${unit} (${verb} '${name}')`, loc))], + else_: null, + loc, + }); + } + body.push( + typeError( + strLit( + use === "read" + ? `Cannot read private member ${name} from an object whose class did not declare it` + : `Cannot write private member ${name} to an object whose class did not declare it`, + loc, + ), + ), + ); + } + fn.body = body; + } +} diff --git a/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts b/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts new file mode 100644 index 000000000..09d228b05 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/promise-executor-adapter.ts @@ -0,0 +1,102 @@ +import { varRef } from "../../ir/build.js"; +import { + type IrExpr, + type IrLocal, + type IrParam, + type IrType, + type SrcLoc, + VOID, + funcOf, + typeKey, +} from "../../ir/ir.js"; +import { newFnCtx, type Lowerer } from "./lowerer.js"; + +/** An executor VALUE whose parameters are checked-dynamic (a JavaScript + * function declaration: `function run(resolve, reject) {...}; return new + * Promise(run)`): the promise's native executor is an adapter closure that + * receives the native (resolve, reject) pair and calls the original with + * both boxed into the checked-dynamic tree, so the JavaScript body calls + * them like any function value. Null when the executor's shape is not + * adaptable (more than two parameters, a rest parameter, or a parameter + * that neither is dyn nor already the native closure). */ +export function adaptPromiseExecutor( + lowerer: Lowerer, + executor: IrExpr, + inner: IrType, + loc: SrcLoc, +): IrExpr | null { + const execT = executor.type; + if (execT.kind !== "func" || execT.rest || execT.params.length > 2) return null; + if (!execT.params.some((p) => p.kind === "dyn")) return null; + const resolveT = funcOf(inner.kind === "void" ? [] : [inner], VOID); + const rejectT = funcOf([{ kind: "object", className: "%Error" }], VOID); + const native = [resolveT, rejectT]; + if ( + !execT.params.every((p, i) => p.kind === "dyn" || typeKey(p) === typeKey(native[i]!)) || + !native.slice(0, execT.params.length).every((t) => lowerer.dynConvertible(t)) + ) + return null; + const adaptT = funcOf(native, VOID); + const key = `promise.executor:${typeKey(execT)}:${typeKey(inner)}`; + let name = lowerer.arrHofHelpers.get(key); + if (name === undefined) { + name = `%promise.executor.${lowerer.arrHofHelpers.size}`; + lowerer.arrHofHelpers.set(key, name); + const impl = `${name}.impl`; + const captured: IrLocal = { id: "f.0", name: "f", type: execT, mutable: false, boxed: true }; + const params: IrParam[] = [ + { localId: "resolve.0", name: "resolve", type: resolveT }, + { localId: "reject.0", name: "reject", type: rejectT }, + ]; + const ctx = newFnCtx(false, null, null, VOID); + lowerer.fnStack.push(ctx); + let args: IrExpr[]; + try { + args = execT.params.map((p, i) => + lowerer.coerceToExpected(varRef(params[i]!.localId, native[i]!, loc), p), + ); + } finally { + lowerer.fnStack.pop(); + } + lowerer.liftedFns.push({ + name: impl, + params, + returnType: VOID, + captures: [{ localId: captured.id, name: captured.name, type: execT }], + locals: [ + captured, + ...params.map((p) => ({ id: p.localId, name: p.name, type: p.type, mutable: false })), + ...ctx.locals, + ], + body: [ + { + kind: "exprStmt", + expr: { + kind: "callValue", + callee: varRef(captured.id, execT, loc), + args, + type: execT.ret, + loc, + }, + loc, + }, + ], + loc, + }); + lowerer.liftedFns.push({ + name, + params: [{ localId: captured.id, name: captured.name, type: execT }], + returnType: adaptT, + locals: [captured], + body: [ + { + kind: "return", + value: { kind: "closure", fnName: impl, captures: [captured.id], type: adaptT, loc }, + loc, + }, + ], + loc, + }); + } + return { kind: "call", callee: name, args: [executor], type: adaptT, loc }; +} diff --git a/packages/compiler/src/frontend/lowering/require-initialization.ts b/packages/compiler/src/frontend/lowering/require-initialization.ts index 2f84e988a..d979423b0 100644 --- a/packages/compiler/src/frontend/lowering/require-initialization.ts +++ b/packages/compiler/src/frontend/lowering/require-initialization.ts @@ -119,7 +119,7 @@ export function markRequireEarlyBindings(lowerer: Lowerer): void { const tag = rawTag === "" ? "e." : rawTag.replace(/^%/, ""); const bySymbol = new Map(); for (const { decl } of declared) { - const flagId = `%g.${tag}%required.${decl.getStart(sf)}`; + const flagId = `%g.${tag}%required.${ts.tokenStartOfNode(decl)}`; lowerer.globalsList.push({ id: flagId, name: "%initialized", type: BOOL, mutable: true }); const flag: RequireInitFlag = { flagId, names: new Map() }; for (const id of boundNames(decl.name)) { @@ -148,7 +148,7 @@ export function markRequireEarlyBindings(lowerer: Lowerer): void { if (owner === undefined) continue; if (boundNames(owner.decl.name).includes(id)) continue; // Code after the require runs after it, except inside functions. - if (id.getStart(sf) >= owner.decl.getEnd() && !inDeferredPosition(id)) continue; + if (ts.tokenStartOfNode(id) >= owner.decl.end && !inDeferredPosition(id)) continue; const body = enclosingBody(id); lowerer.requireEarlyReferences.push({ flag: owner.flag, diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts b/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts index d3acaff6b..f332d0f51 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-bounds.ts @@ -287,7 +287,7 @@ function assertsSlot(node: ts.Node, operands: Operands): boolean { return; } if ((n.flags & ts.NodeFlags.OptionalChain) !== 0) return; - n.forEachChild(visit); + ts.forEachChild(n, visit); }; if (ts.isVariableStatement(node)) { for (const declaration of node.declarationList.declarations.nodes) @@ -323,7 +323,7 @@ function unchanged(nodes: readonly ts.Node[], { array, index }: Operands): boole let hit = false; const walk = (n: ts.Node): void => { if (ts.isIdentifier(n) && roots.includes(n.text)) hit = true; - n.forEachChild(walk); + ts.forEachChild(n, walk); }; walk(e); return hit; @@ -372,7 +372,7 @@ function unchanged(nodes: readonly ts.Node[], { array, index }: Operands): boole ok = false; return; } - n.forEachChild(visit); + ts.forEachChild(n, visit); }; for (const node of nodes) visit(node); return ok; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts index 2fa8785c9..5ed011ce1 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-elements.ts @@ -611,7 +611,7 @@ export class ArrayElementStates { if (this.host.markIterationBinding(name, known)) changed = true; return; } - name.forEachChild(mark); + ts.forEachChild(name, mark); }; if (ts.isVariableDeclarationList(initializer)) { for (const declaration of initializer.declarations.nodes) mark(declaration.name); diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts b/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts index 768932ad3..3e4605f94 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-guards.ts @@ -21,7 +21,7 @@ export function reassignedBindings( else if (ts.isArrayLiteralExpression(e) || ts.isObjectLiteralExpression(e)) { const walk = (n: ts.Node): void => { if (ts.isIdentifier(n)) mark(n); - else n.forEachChild(walk); + else ts.forEachChild(n, walk); }; walk(e); } diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts b/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts index adecfa5a9..dd1dce04d 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-ownership.ts @@ -378,9 +378,9 @@ function usesArguments(fn: ts.FunctionExpression): boolean { if (found) return; if (ts.isIdentifier(node) && node.text === "arguments") found = true; else if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node)) - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - fn.body?.forEachChild(visit); + if (fn.body !== undefined) ts.forEachChild(fn.body, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts b/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts index f150e314b..3363f197d 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-provenance.ts @@ -33,8 +33,8 @@ export class RuntimeOptionalProvenance { function describe(node: ts.Node | undefined): string { if (!node) return "?"; - const sf = node.getSourceFile(); - const start = node.getStart(); + const sf = ts.getSourceFileOfNode(node); + const start = ts.tokenStartOfNode(node); const pos = ts.getLineAndCharacterOfPosition(sf, start); const text = sf.text.slice(start, node.end).replace(/\s+/g, " "); const short = text.length > 60 ? `${text.slice(0, 57)}...` : text; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts b/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts index f95694624..50b508a52 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-records.test.ts @@ -21,7 +21,7 @@ function classes(source: string): { const symbolFor = (name: string): ts.Symbol => { let symbol = symbols.get(name); if (!symbol) { - symbol = { name, declarations: [] } as unknown as ts.Symbol; + symbol = { escapedName: name, declarations: [] } as unknown as ts.Symbol; symbols.set(name, symbol); } return symbol; diff --git a/packages/compiler/src/frontend/lowering/runtime-optional-records.ts b/packages/compiler/src/frontend/lowering/runtime-optional-records.ts index bad2523c8..18e136d69 100644 --- a/packages/compiler/src/frontend/lowering/runtime-optional-records.ts +++ b/packages/compiler/src/frontend/lowering/runtime-optional-records.ts @@ -351,7 +351,7 @@ export class RecordFlowClasses { private referencesOf(symbol: ts.Symbol): readonly ts.Identifier[] { let list = this.references.get(symbol); if (!list) { - list = (this.names.get(symbol.name) ?? []).filter( + list = (this.names.get(ts.symbolName(symbol)) ?? []).filter( (node) => this.host.symbolOf(node) === symbol, ); this.references.set(symbol, list); @@ -412,7 +412,7 @@ export class RecordFlowClasses { const symbol = ts.isIdentifier(base) ? this.host.symbolOf(base) : null; const target = symbol ? this.host.declarationOf(symbol) : undefined; if (!target || !ts.isClassDeclaration(target)) return true; - const file = target.getSourceFile(); + const file = ts.getSourceFileOfNode(target); if (file.isDeclarationFile || isJsFile(file)) return true; current = target; } @@ -591,7 +591,7 @@ export class RecordFlowClasses { } else { this.markOpen(slot); } - if (this.shorthandNames.has(symbol.name)) this.markOpen(slot); + if (this.shorthandNames.has(ts.symbolName(symbol))) this.markOpen(slot); for (const reference of this.referencesOf(symbol)) { if (declaration && (declaration as { name?: ts.Node }).name === reference) continue; if (!this.use(reference, slot)) this.markOpen(slot); @@ -824,9 +824,9 @@ export function returnExpressions(fn: ts.FunctionLikeDeclaration): ts.Expression } if (ts.isFunctionLike(node) || ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - fn.body?.forEachChild(visit); + if (fn.body !== undefined) ts.forEachChild(fn.body, visit); return out; } @@ -865,9 +865,9 @@ function usesArguments(body: ts.Node): boolean { if (found) return; if (ts.isIdentifier(node) && node.text === "arguments") found = true; else if (!ts.isFunctionDeclaration(node) && !ts.isFunctionExpression(node)) - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - body.forEachChild(visit); + ts.forEachChild(body, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/split-consumers.ts b/packages/compiler/src/frontend/lowering/split-consumers.ts index 9624ac0aa..8195d2792 100644 --- a/packages/compiler/src/frontend/lowering/split-consumers.ts +++ b/packages/compiler/src/frontend/lowering/split-consumers.ts @@ -45,7 +45,7 @@ export function isPrivateSplitConsumer( ) return false; - for (const use of identifiersNamed(lowerer, decl.getSourceFile(), name.text)) { + for (const use of identifiersNamed(lowerer, ts.getSourceFileOfNode(decl), name.text)) { if (use === decl.name || use === name) continue; if (lowerer.resolveValueSymbol(use) === symbol) return false; } diff --git a/packages/compiler/src/frontend/lowering/static-function-names.ts b/packages/compiler/src/frontend/lowering/static-function-names.ts new file mode 100644 index 000000000..8868d6745 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/static-function-names.ts @@ -0,0 +1,219 @@ +import * as ts from "../ts7/adapter.js"; +import { isJsSourceFile } from "../program.js"; +import type { Lowerer } from "./lowerer.js"; +import type { GenericFnInfo } from "./generic-functions.js"; +import { stripTypeWrappers } from "./typed-arguments.js"; + +/** `fn.name` inside generic higher-order functions. + * + * Native closures carry no name, but a generic wrapper is instantiated per + * call site anyway: when its body reads `.name` of a function parameter, + * the instantiation key also carries the static name of that call's + * argument (the function declaration's name, a const binding's inferred + * name, the name of the function a wrapper call returns), and the read + * folds to that string. A call whose argument's name is only known at run + * time keeps a precise refusal at the read. */ + +/** Parameter index → symbol for the parameters whose `.name` the generic + * body reads (and never reassigns). Computed once per generic. */ +export function nameReadParams(lowerer: Lowerer, info: GenericFnInfo): Map { + if (info.nameReadParams) return info.nameReadParams; + const out = new Map(); + // JavaScript generics read function names through the checked-value + // box, which carries the runtime name already. + if (isJsSourceFile(ts.getSourceFileOfNode(info.decl))) { + info.nameReadParams = out; + return out; + } + const bySymbol = new Map(); + info.decl.parameters.nodes.forEach((param, i) => { + if (!ts.isIdentifier(param.name) || param.dotDotDotToken) return; + if (!functionTypedParameter(lowerer, param)) return; + const symbol = lowerer.checker.getSymbolAtLocation(param.name); + if (symbol) bySymbol.set(symbol, i); + }); + if (bySymbol.size > 0) { + const written = new Set(); + const read = new Set(); + ts.walkPreorder(info.decl, (node) => { + if (ts.isTypeNode(node)) return "skip"; + if ( + ts.isPropertyAccessExpression(node) && + node.name.text === "name" && + ts.isIdentifier(node.expression) + ) { + const symbol = lowerer.checker.getSymbolAtLocation(node.expression); + if (symbol && bySymbol.has(symbol)) read.add(symbol); + } + if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + node.operatorToken.kind <= ts.SyntaxKind.LastAssignment && + ts.isIdentifier(node.left) + ) { + const symbol = lowerer.checker.getSymbolAtLocation(node.left); + if (symbol) written.add(symbol); + } + return undefined; + }); + for (const symbol of read) { + if (!written.has(symbol)) out.set(bySymbol.get(symbol)!, symbol); + } + } + info.nameReadParams = out; + return out; +} + +/** A parameter declared with a function type, or with a type parameter + * constrained to one (`fn: F` where `F extends (...args: any) => any`). */ +function functionTypedParameter(lowerer: Lowerer, param: ts.ParameterDeclaration): boolean { + const t = lowerer.typeOf(param.name); + if (lowerer.checker.getCallSignatures(t).length > 0) return true; + if (!(t.flags & ts.TypeFlags.TypeParameter)) return false; + const symbol = t.getSymbol(); + const decl = symbol ? symbol.declarations.find(ts.isTypeParameterDeclaration) : undefined; + if (!decl?.constraint) return false; + const constraint = lowerer.checker.getTypeFromTypeNode(decl.constraint); + return lowerer.checker.getCallSignatures(constraint).length > 0; +} + +/** The instantiation key suffix and name bindings of one call of `info`, + * or null when the body reads no parameter's name. */ +export function staticNameBindings( + lowerer: Lowerer, + call: ts.CallExpression, + info: GenericFnInfo, +): { key: string; names: Map } | null { + const params = nameReadParams(lowerer, info); + if (params.size === 0) return null; + const names = new Map(); + const parts: string[] = []; + for (const [index, symbol] of params) { + const arg = call.arguments.nodes[index]; + const name = arg && !ts.isSpreadElement(arg) ? staticFunctionName(lowerer, arg) : null; + names.set(symbol, name); + parts.push(name === null ? "?" : JSON.stringify(name)); + } + return { key: `@name(${parts.join(",")})`, names }; +} + +/** The `name` JavaScript gives the function `expr` evaluates to, when it is + * statically known; null otherwise. */ +export function staticFunctionName(lowerer: Lowerer, expr: ts.Expression): string | null { + return staticNameOf(lowerer, expr, new Set()); +} + +function staticNameOf(lowerer: Lowerer, expr: ts.Expression, seen: Set): string | null { + const e = stripTypeWrappers(expr); + if (seen.has(e)) return null; + seen.add(e); + // An anonymous function as a call argument gets no inferred name. + if (ts.isFunctionExpression(e) || ts.isArrowFunction(e)) { + return !ts.isArrowFunction(e) && e.name ? e.name.text : ""; + } + if (ts.isIdentifier(e)) { + const symbol = lowerer.resolveValueSymbol(e); + if (!symbol) return null; + const bound = lowerer.staticFnNames?.get(symbol); + if (bound !== undefined) return bound; + const decl = symbol.valueDeclaration; + if (decl === undefined) return null; + if (ts.isFunctionDeclaration(decl)) return decl.name ? decl.name.text : "default"; + if ( + ts.isVariableDeclaration(decl) && + ts.isIdentifier(decl.name) && + decl.initializer !== undefined && + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0 + ) { + const init = stripTypeWrappers(decl.initializer); + // `const f = () => …` / `const f = function () {}`: the binding names + // an anonymous function definition. + if (ts.isArrowFunction(init)) return decl.name.text; + if (ts.isFunctionExpression(init)) return init.name ? init.name.text : decl.name.text; + return staticNameOf(lowerer, init, seen); + } + return null; + } + if (ts.isCallExpression(e) && ts.isIdentifier(e.expression)) { + // A call of a generic higher-order function returns the function its + // body returns: every `return` names the same anonymous definition or + // the same local const binding. + const symbol = lowerer.resolveValueSymbol(e.expression); + const info = symbol ? lowerer.genericFnsBySymbol.get(symbol) : undefined; + if (!info) return null; + return returnedFunctionName(lowerer, info.decl, seen); + } + if (ts.isPropertyAccessExpression(e)) { + const symbol = lowerer.checker.getSymbolAtLocation(e.name); + const decl = symbol ? symbol.valueDeclaration : undefined; + if (decl && ts.isMethodDeclaration(decl) && ts.isIdentifier(decl.name)) return decl.name.text; + return null; + } + return null; +} + +function returnedFunctionName( + lowerer: Lowerer, + decl: GenericFnInfo["decl"], + seen: Set, +): string | null { + const body = decl.body; + if (body === undefined) return null; + if (!ts.isBlock(body)) return functionValueName(lowerer, body, seen); + let name: string | null | undefined; + let ok = true; + ts.walkPreorder(body, (node) => { + if (!ok) return "stop"; + if (ts.isFunctionLike(node)) return "skip"; + if (!ts.isReturnStatement(node)) return undefined; + const here = node.expression ? functionValueName(lowerer, node.expression, seen) : null; + if (here === null || (name !== undefined && name !== here)) ok = false; + else name = here; + return undefined; + }); + return ok && name !== undefined ? name : null; +} + +/** The name of a returned function value: an anonymous definition returned + * directly is nameless; a local const binding names its definition. */ +function functionValueName( + lowerer: Lowerer, + expr: ts.Expression, + seen: Set, +): string | null { + const e = stripTypeWrappers(expr); + if (ts.isFunctionExpression(e) || ts.isArrowFunction(e)) { + return !ts.isArrowFunction(e) && e.name ? e.name.text : ""; + } + if (ts.isIdentifier(e)) return staticNameOf(lowerer, e, seen); + return null; +} + +/** `fn.name` where `fn` is a name-keyed parameter of the generic instance + * being lowered: the folded name, or a precise refusal when this call + * site's argument has no static name. Null for every other read. */ +export function staticFunctionNameRead( + lowerer: Lowerer, + expr: ts.PropertyAccessExpression, +): string | null { + const names = lowerer.staticFnNames; + if ( + names === null || + expr.name.text !== "name" || + expr.questionDotToken !== undefined || + !ts.isIdentifier(expr.expression) + ) { + return null; + } + const symbol = lowerer.checker.getSymbolAtLocation(expr.expression); + if (!symbol || !names.has(symbol)) return null; + const name = names.get(symbol); + if (name === null || name === undefined) { + lowerer.unsupported( + "SC1090", + expr, + `reading '${expr.expression.text}.name' when the call passes a function whose name is only known at run time (generic wrappers fold the name of a function declaration, a const-bound function, or a wrapper call's result)`, + ); + } + return name; +} diff --git a/packages/compiler/src/frontend/lowering/static-this.ts b/packages/compiler/src/frontend/lowering/static-this.ts index 004a72375..5c26ef0c9 100644 --- a/packages/compiler/src/frontend/lowering/static-this.ts +++ b/packages/compiler/src/frontend/lowering/static-this.ts @@ -31,10 +31,10 @@ export function rejectStaticThis( const keyword = node.kind === ts.SyntaxKind.ThisKeyword ? "this" : "super"; lowerer.unsupported("SC1090", node, message(keyword)); } - node.forEachChild(visit); + ts.forEachChild(node, visit); }; if (includeRoot) visit(root); - else root.forEachChild(visit); + else ts.forEachChild(root, visit); } export function hasStaticThis(root: ts.Node): boolean { @@ -47,9 +47,9 @@ export function hasStaticThis(root: ts.Node): boolean { ts.isClassExpression(node) ) return; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } @@ -60,8 +60,8 @@ export function hasLexicalPrivateReference(root: ts.Node): boolean { const visit = (node: ts.Node): void => { if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) return; if (ts.isPrivateIdentifier(node)) found = true; - node.forEachChild(visit); + ts.forEachChild(node, visit); }; - root.forEachChild(visit); + ts.forEachChild(root, visit); return found; } diff --git a/packages/compiler/src/frontend/lowering/strict-mode.ts b/packages/compiler/src/frontend/lowering/strict-mode.ts new file mode 100644 index 000000000..86653c1d3 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/strict-mode.ts @@ -0,0 +1,34 @@ +import * as ts from "../ts7/adapter.js"; +import { isCjsJsFile, isNodeEsmFile } from "../program.js"; + +function hasUseStrictDirective(statements: readonly ts.Statement[]): boolean { + for (const statement of statements) { + if (!ts.isExpressionStatement(statement) || !ts.isStringLiteral(statement.expression)) + return false; + if (statement.expression.text === "use strict") return true; + } + return false; +} + +/** Whether `node` runs in strict mode code: inside a class body, a function + * with a "use strict" directive, a module, or a script that starts with + * "use strict". CommonJS JavaScript is sloppy otherwise. */ +export function isStrictCode(node: ts.Node): boolean { + for (let parent: ts.Node | undefined = node.parent; parent; parent = parent.parent) { + if (ts.isClassDeclaration(parent) || ts.isClassExpression(parent)) return true; + if ( + ts.isFunctionLike(parent) && + parent.body && + ts.isBlock(parent.body) && + hasUseStrictDirective(parent.body.statements.nodes) + ) + return true; + if (ts.isSourceFile(parent)) { + return ( + hasUseStrictDirective(parent.statements.nodes) || + (!isCjsJsFile(parent) && (isNodeEsmFile(parent) || ts.isExternalModule(parent))) + ); + } + } + return false; +} diff --git a/packages/compiler/src/frontend/lowering/string-index-bounds.ts b/packages/compiler/src/frontend/lowering/string-index-bounds.ts index acb41514f..6e422b231 100644 --- a/packages/compiler/src/frontend/lowering/string-index-bounds.ts +++ b/packages/compiler/src/frontend/lowering/string-index-bounds.ts @@ -73,7 +73,7 @@ function isBoundary(node: ts.Node): boolean { function containerOf(node: ts.Node): ts.Node { let n = node.parent; while (n && !isBoundary(n)) n = n.parent; - return n ?? node.getSourceFile(); + return n ?? ts.getSourceFileOfNode(node); } function isLoop(node: ts.Node): boolean { @@ -130,7 +130,7 @@ function writeOf(id: ts.Identifier): Write | null { const k = p.operatorToken.kind; if (p.left !== n || k < ts.SyntaxKind.FirstAssignment || k > ts.SyntaxKind.LastAssignment) return null; - const at = id.getStart(); + const at = ts.tokenStartOfNode(id); if (destructured) return { at, step: null, value: null }; if (k === ts.SyntaxKind.EqualsToken) return { at, step: null, value: p.right }; const amount = integerLiteral(p.right); @@ -146,15 +146,15 @@ function writeOf(id: ts.Identifier): Write | null { (p.operator !== ts.SyntaxKind.PlusPlusToken && p.operator !== ts.SyntaxKind.MinusMinusToken) ) return null; - if (destructured) return { at: id.getStart(), step: null, value: null }; + if (destructured) return { at: ts.tokenStartOfNode(id), step: null, value: null }; return { - at: id.getStart(), + at: ts.tokenStartOfNode(id), step: p.operator === ts.SyntaxKind.PlusPlusToken ? 1 : -1, value: null, }; } if (ts.isForOfStatement(p) || ts.isForInStatement(p)) - return p.initializer === n ? { at: id.getStart(), step: null, value: null } : null; + return p.initializer === n ? { at: ts.tokenStartOfNode(id), step: null, value: null } : null; if (ts.isParenthesizedExpression(p)) { n = p; continue; @@ -199,7 +199,7 @@ export class StringIndexBounds { private prove(read: ts.ElementAccessExpression): boolean { // JavaScript declarations are not checked against callers. - if (read.questionDotToken || isJsSourceFile(read.getSourceFile())) return false; + if (read.questionDotToken || isJsSourceFile(ts.getSourceFileOfNode(read))) return false; const textNode = read.expression; const indexNode = read.argumentExpression; if (!ts.isIdentifier(textNode) || !ts.isIdentifier(indexNode)) return false; @@ -268,7 +268,7 @@ export class StringIndexBounds { if (!(list.flags & ts.NodeFlags.Const)) return null; } else if (declaration.initializer) { writes.push({ - at: declaration.name.getStart(), + at: ts.tokenStartOfNode(declaration.name), step: null, value: declaration.initializer, }); @@ -280,7 +280,7 @@ export class StringIndexBounds { const container = containerOf(declaration); let escapes = false; let assigned = 0; - for (const id of this.namesIn(container).get(symbol.name) ?? []) { + for (const id of this.namesIn(container).get(ts.symbolName(symbol)) ?? []) { if (id === nameNode) continue; const write = writeOf(id); if (!write) continue; diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 7ae1371c2..a884b39e1 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -24,6 +24,7 @@ import { } from "../../ir/ir.js"; import { isJsSourceFile, isNodeTypesPath, requireSpecOf } from "../program.js"; import { ZLIB_CONSTANTS } from "./zlib-constants.js"; +import { V8_MEMBER_REASON } from "../builtin-modules.js"; /** zlib stream instances: runtime Transform streams (scr_zlib_stream.c). */ const ZLIB_STREAM: IrType = { kind: "object", className: "%Transform" }; @@ -1908,6 +1909,18 @@ export const BUILTIN_MODULE_FNS: Record< // special-cased); the keys exist for the same reason net/http's do. dgram: {}, dns: {}, + // node:dns/promises rides the same spoke (lookup's promise form). + "dns/promises": {}, + // node:v8: getHeapStatistics answers allocator-derived figures in + // Node's shape (builtins/v8.ts — the entry carries the canonical + // shape); every other member fences with the by-design reason + // (builtinFenceHintOf). + v8: { + getHeapStatistics: { fn: "process.memoryUsageTake", params: [], result: VOID }, + }, + // node:stream/web re-exports the web stream globals (the same objects); + // its members lower through the globals' own paths. + "stream/web": {}, // node:assert lowers ENTIRELY through the assert spoke (lower-assert.ts // — every call shape is special-cased: optional messages complete, the // comparisons pick per-type libCalls, deep equality synthesizes @@ -2221,14 +2234,22 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ kind: "stdlib", name: "Date.parse", fns: ["date.parse"], - note: "one date string, using the bounded parser shared with new Date(dateString).getTime()", + note: "one date string, using V8's date parser shared with new Date(dateString)", }, { id: "stdlib.date.constructor", kind: "stdlib", name: "Date constructor", - fns: ["date.newNow", "date.newMs", "date.newString", "date.nativeNew"], - note: "zero arguments, or one milliseconds/date-string argument; values are the read-only TimeClip scalar slice", + fns: [ + "date.newNow", + "date.newMs", + "date.newString", + "date.nativeNew", + "date.newLocal", + "date.nativeNewFields", + "date.nativeNewValue", + ], + note: "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice", }, { id: "stdlib.date.UTC", @@ -2431,6 +2452,27 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ name: "perf_hooks.performance.clearMeasures", fns: ["perf.clearMeasures"], }, + { + id: "node-builtin.perf_hooks.performance.eventLoopUtilization", + kind: "node-builtin", + name: "perf_hooks.performance.eventLoopUtilization", + fns: ["perf.loopStart", "perf.idleTime"], + note: "active time is the main thread's CPU time since the loop started; the one- and two-argument difference forms are supported", + }, + { + id: "node-builtin.perf_hooks.monitorEventLoopDelay", + kind: "node-builtin", + name: "perf_hooks.monitorEventLoopDelay", + fns: [ + "perf.eldNew", + "perf.eldEnable", + "perf.eldDisable", + "perf.eldReset", + "perf.eldPercentile", + "perf.eldStat", + ], + note: "an unref'd sampling interval records the delay histogram; enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings are supported", + }, // ── the process global's ambient reads and authority calls // (lowerProcessProperty/lowerProcessMethodCall — process is a // provenance-checked global here, not an importable module). @@ -2656,8 +2698,8 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [ id: "node-builtin.process.exitCode", kind: "node-builtin", name: "process.exitCode", - fns: ["process.setExitCode"], - note: "numeric writes in statement position set the implicit exit status; process.exit() reads it", + fns: ["process.setExitCode", "process.setExitCodeTracked", "process.hasExitCode"], + note: "numeric writes in statement position set the implicit exit status; reads answer the assigned code or undefined; process.exit() reads it", }, // ── the tls CA store (lowerTlsCaCall / lowerTlsRootCertificates): the // host's trust anchors, read and replaced. Dedicated paths, and @@ -2803,7 +2845,11 @@ export const FS_CALLBACK_MEMBERS = new Set([ * inherited function into the diagnostic text). */ export function builtinFenceHintOf(module: string, member: string): string | undefined { const mod = ownEntry(BUILTIN_MODULE_FENCE_HINTS, module); - return mod ? ownEntry(mod, member) : undefined; + const hint = mod ? ownEntry(mod, member) : undefined; + // node:v8's unlowered members observe the engine itself: the by-design + // reason, not a pending-surface hint (kept out of the hint table so the + // compatibility rows stay by-design rather than refused). + return hint === undefined && module === "v8" ? V8_MEMBER_REASON : hint; } /** BUILTIN_MODULE_CONSTS with the win32-target overrides applied — the @@ -2969,7 +3015,7 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record< }, util: { promisify: - "the lowered shapes are const bindings over child_process.execFile, child_process.exec, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions, dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart; call the bound function directly", + "the lowered shapes are const bindings over child_process.execFile, child_process.exec, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions, dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart, plus typed callback-last function values whose callback takes (error?: Error | null, value?); call the bound function directly", }, "stream/consumers": { blob: @@ -3266,7 +3312,7 @@ export function isChildSurfaceMember( const decl = sym ? sym.declarations[0] : undefined; const iface = decl?.parent; if (!decl || !iface || !ts.isInterfaceDeclaration(iface)) return false; - if (lowerer.isStdlibFile(decl.getSourceFile())) return false; + if (lowerer.isStdlibFile(ts.getSourceFileOfNode(decl))) return false; return lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(iface.name))?.kind === "child"; } @@ -3276,7 +3322,9 @@ export function isChildSurfaceMember( * with lib interfaces, so a member can carry declarations from both. A * user's own declaration is in neither, so shadowing never matches. */ export function isStdlibSymbol(lowerer: Lowerer, symbol: ts.Symbol | undefined): boolean { - return !!symbol && symbol.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile())); + return ( + !!symbol && symbol.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d))) + ); } /** The canonical stdlib-global name `expr` denotes, or null. Three @@ -3334,20 +3382,20 @@ export function stdlibGlobalNameOf(lowerer: Lowerer, expr: ts.Expression): strin if ( decl !== undefined && (ts.isVariableDeclaration(decl) || ts.isBindingElement(decl)) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) { const imported = lowerer.builtinImportOf(expr); if (imported?.module === "buffer" && imported.member === "Buffer") return "Buffer"; } return null; } - return resolved.name === "global" ? "globalThis" : resolved.name; + return ts.symbolName(resolved) === "global" ? "globalThis" : ts.symbolName(resolved); } if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) { if (stdlibGlobalNameOf(lowerer, expr.expression) !== "globalThis") return null; const symbol = lowerer.checker.getSymbolAtLocation(expr.name); if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null; - return symbol.name === "global" ? "globalThis" : symbol.name; + return ts.symbolName(symbol) === "global" ? "globalThis" : ts.symbolName(symbol); } return null; } @@ -3433,6 +3481,10 @@ export function stdlibGlobalAliasNameOf( "Number", "String", "Boolean", + // Code generation from strings throws Node's EvalError at every call + // (code-generation.ts); zod 4 probes `const F = Function; new F("")`. + "Function", + "eval", ].includes(name) ? name : null; @@ -3477,7 +3529,7 @@ export function nodeTypesOnlySymbol(lowerer: Lowerer, sym: ts.Symbol | null | un if (!decls || decls.length === 0) return false; let viaNode = false; for (const d of decls) { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); if (sf.isDeclarationFile && isNodeTypesPath(sf.fileName)) viaNode = true; else if (lowerer.isStdlibFile(sf)) return false; } diff --git a/packages/compiler/src/frontend/lowering/symbol-fields.ts b/packages/compiler/src/frontend/lowering/symbol-fields.ts index 5d7a20c06..56718615e 100644 --- a/packages/compiler/src/frontend/lowering/symbol-fields.ts +++ b/packages/compiler/src/frontend/lowering/symbol-fields.ts @@ -1,5 +1,10 @@ import * as ts from "../ts7/adapter.js"; -import { bindingEarlyUse7 } from "../program.js"; +import { + bindingEarlyUse7, + cjsExportDiscardReason, + isCjsExportTableLiteral, + isModuleExportsAccess, +} from "../program.js"; import type { Lowerer } from "./lowerer.js"; import type { IrType } from "../../ir/ir.js"; @@ -39,7 +44,7 @@ function constantSymbolDescription( } export function symbolFieldDisplayName(field: string): string { - return field.slice(FIELD_PREFIX.length); + return field.slice(FIELD_PREFIX.length).replace(/^\d+:/, ""); } /** CopyDataProperties includes symbols, unlike the string-keyed view used @@ -96,6 +101,11 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS cache.set(sym, null); const decls = sym.declarations; const decl = sym.valueDeclaration; + if (decls.length === 1 && decl && ts.isPropertyAssignment(decl)) { + const key = cjsTableSymbolKeyOf(lowerer, sym, decl); + if (key) cache.set(sym, key); + return key; + } if (decls.length !== 1 || !decl || !ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name)) return null; const list = decl.parent; @@ -108,13 +118,13 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS const init = decl.initializer; if (init && (ts.isIdentifier(init) || ts.isPropertyAccessExpression(init))) { const target = classSymbolKeyOf(lowerer, init); - if (!target || bindingWritten(lowerer, sym, decl.getSourceFile())) return null; + if (!target || bindingWritten(lowerer, sym, ts.getSourceFileOfNode(decl))) return null; const preceding = list.declarations.nodes.slice(0, list.declarations.nodes.indexOf(decl)); if ( bindingEarlyUse7( lowerer.program, - decl.getSourceFile(), - decl.getSourceFile().statements.nodes.indexOf(list.parent), + ts.getSourceFileOfNode(decl), + ts.getSourceFileOfNode(decl).statements.nodes.indexOf(list.parent), decl, preceding, ) !== null @@ -124,24 +134,12 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS cache.set(sym, alias); return alias; } - if (!init || !ts.isCallExpression(init) || init.questionDotToken) return null; - const callee = init.expression; - const registered = - ts.isPropertyAccessExpression(callee) && !callee.questionDotToken && callee.name.text === "for"; - const root = registered ? callee.expression : callee; - if (!ts.isIdentifier(root) || !lowerer.isStdlibGlobal(root, "Symbol")) return null; - const arg = - init.arguments.nodes.length === 0 - ? null - : init.arguments.nodes.length === 1 - ? init.arguments.nodes[0]! - : undefined; - if (arg === undefined || (registered && arg === null)) return null; - const description = arg === null ? "" : constantSymbolDescription(lowerer, arg); - if (description === null) return null; + const call = init ? literalSymbolCallOf(lowerer, init) : null; + if (!call) return null; + const { registered, description } = call; // Ordinary Symbol() is a fresh identity per evaluation, so only the // module-scope declaration above can name one fixed layout slot. - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (bindingWritten(lowerer, sym, sf)) return null; const preceding = list.declarations.nodes.slice(0, list.declarations.nodes.indexOf(decl)); // Creating a function value does not execute its body. The general @@ -184,6 +182,18 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS ) !== null ) return null; + const key = symbolKeyOf(lowerer, sym, registered, description); + cache.set(sym, key); + return key; +} + +function symbolKeyOf( + lowerer: Lowerer, + sym: ts.Symbol, + registered: boolean, + description: string, + table = false, +): ClassSymbolKey { // A registry name denotes one symbol even when several source bindings // initialize it. Intern those names into a representative checker symbol; // the layout table then has one identity-keyed domain for both forms. @@ -193,16 +203,175 @@ export function classSymbolKeyOfSymbol(lowerer: Lowerer, sym: ts.Symbol): ClassS if (existing) identity = existing; else lowerer.registeredClassSymbols.set(description, sym); } - const key: ClassSymbolKey = { + // Distinct identities can share a description (undici's core and mock + // key tables both hold a `Symbol('close')`, and MockClient inherits one + // and declares the other). A key-table key whose description an earlier + // identity already holds takes an ordinal that keeps its slot apart; the + // printable name drops it. Const keys keep their plain names. + let identities = lowerer.symbolFieldIdentities.get(description); + if (!identities) lowerer.symbolFieldIdentities.set(description, (identities = [])); + let ordinal = identities.indexOf(identity); + if (ordinal < 0) ordinal = identities.push(identity) - 1; + return { sym, identity, // Symbol slots are not string properties. The reserved prefix keeps // them out of the native class's string-keyed dynamic view, including // Object.keys/JSON serialization and string-property writeback. - fieldName: `${FIELD_PREFIX}Symbol(${description})`, + fieldName: `${FIELD_PREFIX}${table && ordinal > 0 ? `${ordinal}:` : ""}Symbol(${description})`, }; - cache.set(sym, key); - return key; +} + +/** `Symbol()` or `Symbol.for()` on the standard global. */ +function literalSymbolCallOf( + lowerer: Lowerer, + init: ts.Expression, +): { registered: boolean; description: string } | null { + if (!ts.isCallExpression(init) || init.questionDotToken) return null; + const callee = init.expression; + const registered = + ts.isPropertyAccessExpression(callee) && !callee.questionDotToken && callee.name.text === "for"; + const root = registered ? callee.expression : callee; + if (!ts.isIdentifier(root) || !lowerer.isStdlibGlobal(root, "Symbol")) return null; + const arg = + init.arguments.nodes.length === 0 + ? null + : init.arguments.nodes.length === 1 + ? init.arguments.nodes[0]! + : undefined; + if (arg === undefined || (registered && arg === null)) return null; + const description = arg === null ? "" : constantSymbolDescription(lowerer, arg); + return description === null ? null : { registered, description }; +} + +/** A key table property: `kX: Symbol('x')` in a JS module's effective + * top-level `module.exports = { ... }` literal (undici's core/symbols.js). + * The module evaluates once, so the property names one symbol identity for + * every requirer, as a module-scope const does. Requirers can observe the + * table only after its assignment completes, provided nothing before it + * can run code (a require there could close a cycle and publish the + * initial empty exports object instead). Any write to the property, or + * replacement through `exports`/`module.exports`, keeps it unstable. */ +function cjsTableSymbolKeyOf( + lowerer: Lowerer, + sym: ts.Symbol, + decl: ts.PropertyAssignment, +): ClassSymbolKey | null { + const table = decl.parent; + if (!ts.isObjectLiteralExpression(table) || !ts.isIdentifier(decl.name)) return null; + const assign = table.parent; + if ( + !ts.isBinaryExpression(assign) || + assign.right !== table || + !isCjsExportTableLiteral(table) || + !ts.isExpressionStatement(assign.parent) + ) + return null; + const stmt = assign.parent; + const sf = ts.getSourceFileOfNode(stmt); + if (cjsExportDiscardReason(stmt) !== null) return null; + const call = literalSymbolCallOf(lowerer, decl.initializer); + if (!call) return null; + const inert = (expr: ts.Expression | undefined): boolean => { + if (!expr) return true; + while (ts.isParenthesizedExpression(expr)) expr = expr.expression; + return ( + ts.isIdentifier(expr) || + ts.isArrowFunction(expr) || + ts.isFunctionExpression(expr) || + ts.isStringLiteralLike(expr) || + ts.isNumericLiteral(expr) || + expr.kind === ts.SyntaxKind.TrueKeyword || + expr.kind === ts.SyntaxKind.FalseKeyword || + expr.kind === ts.SyntaxKind.NullKeyword || + literalSymbolCallOf(lowerer, expr) !== null + ); + }; + const statements = sf.statements.nodes; + const prefixInert = statements + .slice(0, statements.indexOf(stmt)) + .every( + (statement) => + (ts.isExpressionStatement(statement) && ts.isStringLiteral(statement.expression)) || + ts.isFunctionDeclaration(statement) || + (ts.isVariableStatement(statement) && + statement.declarationList.declarations.nodes.every( + (d) => ts.isIdentifier(d.name) && inert(d.initializer), + )), + ); + if (!prefixInert) return null; + for (const property of table.properties.nodes) { + if (property === decl) break; + if (ts.isShorthandPropertyAssignment(property)) continue; + if ( + !ts.isPropertyAssignment(property) || + ts.isComputedPropertyName(property.name) || + !inert(property.initializer) + ) + return null; + } + // Requirers can reach the property through a checker-synthesized symbol + // (the export= type's member); the declaration's own symbol is the one + // identity every requirer and the table's export global share. + const canonical = lowerer.checker.getSymbolAtLocation(decl.name) ?? sym; + if (tablePropertyWritten(lowerer, new Set([sym, canonical]), decl.name.text, sf)) return null; + return { ...symbolKeyOf(lowerer, canonical, call.registered, call.description, true), sym }; +} + +function tablePropertyWritten( + lowerer: Lowerer, + symbols: ReadonlySet, + name: string, + home: ts.SourceFile, +): boolean { + let written = false; + const targets = (node: ts.Node): boolean => { + if (ts.isPropertyAccessExpression(node)) { + if (node.name.text !== name) return false; + const recv = node.expression; + if ( + ts.getSourceFileOfNode(node) === home && + ((ts.isIdentifier(recv) && recv.text === "exports") || isModuleExportsAccess(recv)) + ) + return true; + const target = lowerer.checker.getSymbolAtLocation(node.name); + return target !== undefined && symbols.has(target); + } + if (ts.isElementAccessExpression(node)) { + const key = node.argumentExpression; + if (!ts.isStringLiteralLike(key) || key.text !== name) return false; + const target = lowerer.checker.getSymbolAtLocation(node); + return target !== undefined && symbols.has(target); + } + return false; + }; + for (const sf of lowerer.program.getSourceFiles()) { + if (written) break; + if (sf.isDeclarationFile || !sf.text.includes(name)) continue; + ts.walkPreorder(sf, (node) => { + if (written) return "stop"; + let target: ts.Node | undefined; + if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + node.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) + target = node.left; + else if (ts.isDeleteExpression(node)) target = node.expression; + else if ( + (ts.isPrefixUnaryExpression(node) || ts.isPostfixUnaryExpression(node)) && + (node.operator === ts.SyntaxKind.PlusPlusToken || + node.operator === ts.SyntaxKind.MinusMinusToken) + ) + target = node.operand; + if (target && targets(target)) { + written = true; + return "stop"; + } + return undefined; + }); + } + return written; } function bindingWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): boolean { @@ -212,7 +381,7 @@ function bindingWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): bo // Assignment to obj[key] changes the property, never the key's // binding. The same applies to these leaves in destructuring. if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) return "skip"; - if (!ts.isIdentifier(node) || node.text !== sym.name) return; + if (!ts.isIdentifier(node) || node.text !== ts.symbolName(sym)) return; const targetSym = ts.isShorthandPropertyAssignment(node.parent) ? lowerer.checker.getShorthandAssignmentValueSymbol(node.parent) : lowerer.checker.getSymbolAtLocation(node); @@ -245,3 +414,29 @@ function bindingWritten(lowerer: Lowerer, sym: ts.Symbol, sf: ts.SourceFile): bo }); return written; } + +/** The layout type of a symbol-keyed field read through a CommonJS + * key-table key (`this[kCount]` with `kCount` from a `module.exports = { + * kCount: Symbol('count') }` table), or null. The checker types such keys + * as plain `symbol`, so it answers the read with the union of every + * symbol-keyed property of the class; the field's own type is the read's + * type. Keys declared as consts keep their unique-symbol checker types. */ +export function tableKeyFieldType( + lowerer: Lowerer, + node: ts.ElementAccessExpression, +): IrType | null { + const key = node.argumentExpression; + if (!ts.isIdentifier(key) && !ts.isPropertyAccessExpression(key)) return null; + const keyType = lowerer.checker.getTypeAtLocation(key); + if (!(keyType.flags & ts.TypeFlags.ESSymbol) || keyType.flags & ts.TypeFlags.UniqueESSymbol) + return null; + const resolved = classSymbolKeyOf(lowerer, key); + if (!resolved) return null; + const decl = resolved.identity.valueDeclaration; + if (!decl || !ts.isPropertyAssignment(decl)) return null; + const receiver = lowerer.mapTypeOf(lowerer.checker.getTypeAtLocation(node.expression)); + if (receiver?.kind !== "object") return null; + const info = lowerer.classes.get(receiver.className); + const field = info?.symbolFields?.get(resolved.identity); + return field === undefined ? null : (info!.fields.get(field) ?? null); +} diff --git a/packages/compiler/src/frontend/lowering/symbol-methods.ts b/packages/compiler/src/frontend/lowering/symbol-methods.ts index f6a8e68e4..b02e95641 100644 --- a/packages/compiler/src/frontend/lowering/symbol-methods.ts +++ b/packages/compiler/src/frontend/lowering/symbol-methods.ts @@ -2,10 +2,11 @@ import * as ts from "../ts7/adapter.js"; import { locOf } from "../program.js"; import type { IrExpr } from "../../ir/ir.js"; import type { Lowerer } from "./lowerer.js"; -import { accessorCall } from "./lower-classes.js"; +import { accessorCall, type ClassInfo } from "./lower-classes.js"; import { classSymbolKeyOf } from "./symbol-fields.js"; -/** A stable symbol method uses the same completed arguments and virtual +/** A stable symbol method (a module-scope symbol, or a well-known symbol + * such as `Symbol.iterator`) uses the same completed arguments and virtual * dispatch as a string-named class method. Runtime-selected keys stay on * the checked-property path. */ export function lowerClassSymbolMethodCall( @@ -15,12 +16,13 @@ export function lowerClassSymbolMethodCall( const access = call.expression; if (!ts.isElementAccessExpression(access) || access.questionDotToken || call.questionDotToken) return null; - const key = classSymbolKeyOf(lowerer, access.argumentExpression); - if (!key) return null; const type = lowerer.mapTypeOf(lowerer.typeOf(access.expression)); if (type?.kind !== "object") return null; const info = lowerer.classes.get(type.className); - const name = info?.symbolMethods?.get(key.identity); + const key = classSymbolKeyOf(lowerer, access.argumentExpression); + const name = key + ? info?.symbolMethods?.get(key.identity) + : wellKnownSymbolMethod(lowerer, info, access.argumentExpression); if (!name) return null; const method = lowerer.findMethodOn(info ?? null, name); if (!method) return null; @@ -29,3 +31,21 @@ export function lowerClassSymbolMethodCall( const args = lowerer.completeArgs(call.arguments.nodes, method.sig.params, loc, call); return accessorCall(lowerer, type.className, name, receiver, args, method.sig.ret, loc); } + +/** The `sym:` method a well-known symbol key such as `Symbol.iterator` + * names, when the class declares it as a method and the program cannot + * replace it at run time (an instance property of that name, a prototype + * write). */ +function wellKnownSymbolMethod( + lowerer: Lowerer, + info: ClassInfo | undefined, + key: ts.Expression, +): string | null { + while (ts.isParenthesizedExpression(key)) key = key.expression; + if (!info || info.def.runtime || !ts.isPropertyAccessExpression(key)) return null; + const member = lowerer.stdlibGlobalMember(key, "Symbol"); + if (member === null) return null; + const name = `sym:${member}`; + if (info.fields.has(name) || lowerer.replacedSymbolMethods.has(name)) return null; + return name; +} diff --git a/packages/compiler/src/frontend/lowering/thread-slots.ts b/packages/compiler/src/frontend/lowering/thread-slots.ts index 50b5ecf09..8227c6bd0 100644 --- a/packages/compiler/src/frontend/lowering/thread-slots.ts +++ b/packages/compiler/src/frontend/lowering/thread-slots.ts @@ -22,6 +22,7 @@ import { threadSlotsMemberOf, } from "../experimental-import.js"; import * as ts from "../ts7/adapter.js"; +import { withUndefinedArm } from "../type-mapper.js"; import type { ClassInfo } from "./lower-classes.js"; import type { Lowerer } from "./lowerer.js"; import { locOf } from "../program.js"; @@ -238,10 +239,16 @@ export function lowerThreadSlotsCall(lowerer: Lowerer, expr: ts.CallExpression): const args = [name, receiver, key]; switch (member) { case "get": { + // The slots hold values in the layout of the type the constructor registered (`family.value`, + // which `set` stores with). The receiver's annotation can name a structurally wider value + // type, so the value read is converted to the call's type like any value flowing into it. const resultTs = lowerer.typeOf(expr); const result = lowerer.mapTypeOf(resultTs); if (result === null) lowerer.badType(expr, resultTs); - return { kind: "intrinsic", name: "slots.get", args, type: result!, loc }; + const stored = withUndefinedArm(family.value, lowerer.unions); + if (stored === null) lowerer.badType(expr, resultTs); + const read: IrExpr = { kind: "intrinsic", name: "slots.get", args, type: stored!, loc }; + return typeKey(stored!) === typeKey(result!) ? read : lowerer.coerceInto(expr, read, result!); } case "has": case "delete": diff --git a/packages/compiler/src/frontend/lowering/typed-arguments.ts b/packages/compiler/src/frontend/lowering/typed-arguments.ts new file mode 100644 index 000000000..2fbd83482 --- /dev/null +++ b/packages/compiler/src/frontend/lowering/typed-arguments.ts @@ -0,0 +1,516 @@ +import * as ts from "../ts7/adapter.js"; +import { bodyReadsArguments } from "../arguments-usage.js"; +import { isJsSourceFile, locOf } from "../program.js"; +import { restTupleElementTypes, unitOnlyUnion } from "../type-mapper.js"; +import { DYN, type IrExpr, type IrLocal, type IrStmt, type IrType, arrayOf } from "../../ir/ir.js"; +import type { Lowerer } from "./lowerer.js"; +import { captureContextArguments } from "./function-context.js"; +import { isThisParameter, type ParamShape } from "./call-signatures.js"; +import { tryLowerExpression } from "./expressions/try-lower-expression.js"; +import { omittedArgFor } from "./call-arguments.js"; + +/** Variadic forwarding through a FIXED signature. + * + * A TypeScript function expression that declares no parameters and reads + * `arguments` (`function (this: unknown) { return fn.apply(this, arguments) } + * as F`) is lowered against the fixed function type its value is asserted + * to: one native slot per position of that type, and `arguments` is the + * tuple of those slots. Inside a generic instantiation F is the call + * site's concrete signature, so a higher-order wrapper specializes to the + * wrapped function's exact ABI. Forwarding (`f.apply(thisArg, args)`, + * `f(...args)`) then calls with the tuple's positions directly. */ + +/** `e`, with the type-only wrappers that never change its value removed. */ +export function stripTypeWrappers(e: ts.Expression): ts.Expression { + let cur = e; + for (;;) { + if (ts.isParenthesizedExpression(cur)) cur = cur.expression; + else if (ts.isAsExpression(cur) || ts.isTypeAssertion(cur) || ts.isSatisfiesExpression(cur)) + cur = cur.expression; + else if (ts.isNonNullExpression(cur)) cur = cur.expression; + else return cur; + } +} + +/** The outermost type assertion around `node` (`node as unknown as F` → F), + * looking through parentheses; null when the value is not asserted. */ +function assertedTypeNode(node: ts.Node): ts.TypeNode | null { + let cur: ts.Node = node; + let found: ts.TypeNode | null = null; + for (let parent = cur.parent; parent !== undefined; parent = cur.parent) { + if (ts.isParenthesizedExpression(parent)) { + cur = parent; + continue; + } + if ((ts.isAsExpression(parent) || ts.isTypeAssertion(parent)) && parent.expression === cur) { + found = parent.type; + cur = parent; + continue; + } + break; + } + return found; +} + +/** The fixed function type a value is asserted to, or null. */ +function assertedFuncType(lowerer: Lowerer, node: ts.Node): (IrType & { kind: "func" }) | null { + const typeNode = assertedTypeNode(node); + if (!typeNode) return null; + const mapped = lowerer.mapTypeOf(lowerer.checker.getTypeFromTypeNode(typeNode)); + return mapped?.kind === "func" ? mapped : null; +} + +function sameFuncType(a: IrType & { kind: "func" }, b: IrType & { kind: "func" }): boolean { + return JSON.stringify(a) === JSON.stringify(b); +} + +/** A function expression whose `arguments` can be typed: TypeScript source, + * no runtime parameters (a `this` parameter is type-only), an ordinary + * (non-generator) function that reads its own `arguments`. */ +export function typedArgumentsCandidate(node: ts.Node): node is ts.FunctionExpression { + return ( + ts.isFunctionExpression(node) && + node.asteriskToken === undefined && + !isJsSourceFile(ts.getSourceFileOfNode(node)) && + node.parameters.nodes.every(isThisParameter) && + bodyReadsArguments(node) + ); +} + +/** The fixed signature a typed-arguments function expression is lowered + * against: the type its value is asserted to (`function () {…} as F`), the + * one type every use of its const binding asserts it to (`const w = + * function () {…}; return w as unknown as F`), or its contextual function + * type. Null when none names one fixed-arity signature. */ +export function typedArgumentsTarget( + lowerer: Lowerer, + node: ts.FunctionExpression, +): (IrType & { kind: "func" }) | null { + const fixed = (t: (IrType & { kind: "func" }) | null) => (t && t.rest !== true ? t : null); + const direct = assertedFuncType(lowerer, node); + if (direct) return fixed(direct); + let top: ts.Node = node; + while (top.parent && ts.isParenthesizedExpression(top.parent)) top = top.parent; + const decl = top.parent; + if ( + decl && + ts.isVariableDeclaration(decl) && + decl.initializer === top && + decl.type === undefined && + ts.isIdentifier(decl.name) && + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) !== 0 + ) { + const symbol = lowerer.checker.getSymbolAtLocation(decl.name); + const scope = enclosingScope(decl); + if (symbol && scope) { + let target: (IrType & { kind: "func" }) | null = null; + let ok = true; + let uses = 0; + ts.walkPreorder(scope, (n) => { + if (!ok) return "stop"; + if (n === decl.name || !ts.isIdentifier(n) || n.text !== decl.name.text) return undefined; + if (lowerer.checker.getSymbolAtLocation(n) !== symbol) return undefined; + uses++; + const asserted = assertedFuncType(lowerer, n); + if (!asserted || (target && !sameFuncType(target, asserted))) ok = false; + else target = asserted; + return undefined; + }); + if (ok && uses > 0) return fixed(target); + } + } + const ctxType = lowerer.checker.getContextualType(node); + if (!ctxType) return null; + const sigs = lowerer.checker.getCallSignatures(lowerer.checker.getNonNullableType(ctxType)); + if (sigs.length !== 1) return null; + const mapped = lowerer.mapTypeOf(lowerer.checker.getNonNullableType(ctxType)); + return mapped?.kind === "func" ? fixed(mapped) : null; +} + +function enclosingScope(node: ts.Node): ts.Node | null { + for (let cur = node.parent; cur !== undefined; cur = cur.parent) { + if (ts.isSourceFile(cur) || (ts.isFunctionLike(cur) && cur.body !== undefined)) return cur; + } + return null; +} + +/** The tuple type of a fixed parameter list, interned exactly as the + * spelled tuple (and `Parameters`) map: positional fields, or the + * empty-array form for no parameters. */ +export function tupleTypeOfParams(lowerer: Lowerer, params: readonly IrType[]): IrType { + if (params.length === 0) return arrayOf(unitOnlyUnion(lowerer.unions)); + const fields = params.map((type, i) => ({ name: String(i), type })); + fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + return { kind: "record", shapeId: lowerer.shapes.intern(fields, true) }; +} + +/** The ABI shapes of a typed-arguments function: one required slot per + * position of its target signature. */ +export function typedArgumentsShapes(target: IrType & { kind: "func" }): ParamShape[] { + return target.params.map((type) => ({ type, mode: "required" })); +} + +/** Declares the hidden positional slots of a typed-arguments function and + * binds its `arguments` to their tuple. */ +export function declareTypedArguments( + lowerer: Lowerer, + node: ts.FunctionExpression, + shapes: readonly ParamShape[], + params: { localId: string; name: string; type: IrType }[], + prologue: IrStmt[], +): void { + const loc = locOf(node); + const slots = shapes.map((shape, i) => { + const slot = lowerer.declareHiddenLocal(`%arguments.${i}`, shape.type); + params.push({ localId: slot.id, name: `%arguments.${i}`, type: shape.type }); + return slot; + }); + const tupleType = tupleTypeOfParams( + lowerer, + shapes.map((s) => s.type), + ); + const local = lowerer.declareHiddenLocal("%arguments", tupleType); + lowerer.ctx.argumentsLocal = local; + // Pure forwarding (every read is forwarded, counted, or aliased by a + // binding that is itself only forwarded): no tuple exists at all. + if ( + onlyForwarded(lowerer, node, (id) => id.text === "arguments" && argumentsOwner(id) === node) + ) { + lowerer.ctx.pureTuples.set(local.id, slots); + return; + } + prologue.push({ + kind: "varDecl", + localId: local.id, + init: lowerer.tupleOfSlots(slots, tupleType, node, loc), + loc, + }); +} + +/** Whether every read of a tuple binding in `fn` (identified by `isRoot`) + * only forwards it: `f.apply(t, x)`, `g(...x)` into a program function, + * `x.length`, or a const alias whose own reads only forward. Reads inside + * nested functions (arrows included) capture the binding and need the + * tuple. Type assertions around a read never matter. */ +export function onlyForwarded( + lowerer: Lowerer, + fn: ts.Node, + isRoot: (id: ts.Identifier) => boolean, +): boolean { + const body = (fn as ts.Node & { body?: ts.Node }).body; + if (body === undefined) return false; + const roots: ((id: ts.Identifier) => boolean)[] = [isRoot]; + let ok = true; + const visit = (scope: ts.Node, test: (id: ts.Identifier) => boolean): void => { + ts.walkPreorder(scope, (n) => { + if (!ok) return "stop"; + if (ts.isTypeNode(n)) return "skip"; + if (!ts.isIdentifier(n) || !test(n)) return undefined; + for (let cur = n.parent; cur !== undefined && cur !== fn; cur = cur.parent) { + if (ts.isFunctionLike(cur)) { + ok = false; + return "stop"; + } + } + let top: ts.Node = n; + while ( + top.parent && + (ts.isParenthesizedExpression(top.parent) || + ts.isAsExpression(top.parent) || + ts.isTypeAssertion(top.parent) || + ts.isSatisfiesExpression(top.parent) || + ts.isNonNullExpression(top.parent)) + ) + top = top.parent; + const p = top.parent; + if (!p) { + ok = false; + return "stop"; + } + if (ts.isPropertyAccessExpression(p) && p.expression === top && p.name.text === "length") + return undefined; + if ( + ts.isCallExpression(p) && + p.arguments.nodes.length === 2 && + p.arguments.nodes[1] === top && + ts.isPropertyAccessExpression(p.expression) && + p.expression.name.text === "apply" && + p.expression.questionDotToken === undefined + ) + return undefined; + if (ts.isSpreadElement(p) && p.parent && ts.isCallExpression(p.parent)) { + const sigs = lowerer.checker.getCallSignatures( + lowerer.checker.getNonNullableType(lowerer.typeOf(p.parent.expression)), + ); + const decl = sigs.length === 1 ? sigs[0]!.declaration : undefined; + if (decl && !ts.getSourceFileOfNode(decl).isDeclarationFile) return undefined; + } + if ( + ts.isVariableDeclaration(p) && + p.initializer === top && + ts.isIdentifier(p.name) && + (ts.getCombinedNodeFlags(p) & ts.NodeFlags.Const) !== 0 + ) { + const alias = lowerer.checker.getSymbolAtLocation(p.name); + const aliasName = p.name; + if (alias) { + roots.push((id) => id !== aliasName && lowerer.checker.getSymbolAtLocation(id) === alias); + return undefined; + } + } + ok = false; + return "stop"; + }); + }; + for (let i = 0; i < roots.length && ok; i++) visit(body, roots[i]!); + return ok; +} + +/** The native slots of a pure-forwarded tuple binding read at `node` in + * the current function, or null when the tuple is materialized. */ +function pureSlotsOf(lowerer: Lowerer, node: ts.Expression): IrLocal[] | null { + const operand = stripTypeWrappers(node); + if (!ts.isIdentifier(operand)) return null; + const local = + operand.text === "arguments" ? lowerer.ctx.argumentsLocal : lowerer.peekLocal(operand); + return local ? (lowerer.ctx.pureTuples.get(local.id) ?? null) : null; +} + +/** The positional values `node` forwards when it is a pure-forwarded tuple + * (slot reads, in order), or null. */ +export function pureForwardedValues(lowerer: Lowerer, node: ts.Expression): IrExpr[] | null { + const slots = pureSlotsOf(lowerer, node); + if (!slots) return null; + const loc = locOf(node); + return slots.map((slot) => ({ kind: "varRef", localId: slot.id, type: slot.type, loc })); +} + +/** Whether `node` reads a tuple-typed `arguments` binding: the nearest + * ordinary function (arrows inherit it) is a typed-arguments function. + * Looks the binding up without allocating captures. */ +function isTypedArgumentsRead(lowerer: Lowerer, node: ts.Expression): boolean { + if (!ts.isIdentifier(node) || node.text !== "arguments") return false; + const stack = lowerer.fnStack; + let owner = stack.length - 1; + while (owner > 0 && stack[owner]!.inheritsArguments) owner--; + const own = owner >= 0 ? stack[owner]!.argumentsLocal : undefined; + return own !== undefined && own !== null && own.type.kind !== "dyn"; +} + +/** The fixed tuple `node` forwards, or null: the typed `arguments`, a + * fixed-tuple rest parameter, or a const alias of either — seen through + * type assertions, which never change the value. */ +export function forwardedTupleType(lowerer: Lowerer, node: ts.Expression): IrType | null { + const operand = stripTypeWrappers(node); + if (!ts.isIdentifier(operand)) return null; + if (operand.text === "arguments") { + if (!isTypedArgumentsRead(lowerer, operand)) return null; + return captureContextArguments(lowerer.fnStack)!.type; + } + const symbol = lowerer.resolveValueSymbol(operand); + if (!symbol) return null; + const alias = lowerer.tupleAliasSymbols.get(symbol); + if (alias) return alias; + const decl = symbol.valueDeclaration; + if ( + decl !== undefined && + ts.isParameter(decl) && + decl.dotDotDotToken !== undefined && + ts.isIdentifier(decl.name) && + !isJsSourceFile(ts.getSourceFileOfNode(decl)) && + // Checker-only probe first: an ordinary array rest never maps here. + restTupleElementTypes(lowerer.typeOf(operand), lowerer.typeCtx) !== null + ) { + // A tuple with optional positions has no value type of its own; its + // forwarded-only binding carries the slots' tuple (declareRestTupleParam). + const t = lowerer.mapTypeOf(lowerer.typeOf(operand)) ?? lowerer.peekLocal(operand)?.type; + if (t && isTupleValueType(lowerer, t)) return t; + } + return null; +} + +function isTupleValueType(lowerer: Lowerer, t: IrType): boolean { + if (t.kind === "record") return lowerer.shapes.get(t.shapeId)?.tuple === true; + // The empty tuple's representation (mapType's `[]` rule): an array over + // the unit-only union. Looked up, never interned, so probing a value + // leaves the registries untouched. + if (t.kind !== "array" || t.elem.kind !== "union") return false; + const arms = lowerer.unions.get(t.elem.unionId)?.arms; + return ( + arms !== undefined && + arms.length === 2 && + arms.some((a) => a.kind === "nullT") && + arms.some((a) => a.kind === "undefinedT") + ); +} + +/** The positional reads of a forwarded tuple value (already saved in a + * local), in order. */ +function tupleReads( + lowerer: Lowerer, + ref: () => IrExpr, + tupleType: IrType, + loc: IrExpr["loc"], +): IrExpr[] { + if (tupleType.kind !== "record") return []; + const shape = lowerer.shapes.get(tupleType.shapeId)!; + return [...shape.fields] + .sort((a, b) => Number(a.name) - Number(b.name)) + .map((field) => ({ + kind: "recordGet" as const, + obj: ref(), + shapeId: tupleType.shapeId, + field: field.name, + type: field.type, + loc, + })); +} + +/** `f.apply(thisArg, args)` where `args` forwards a fixed tuple and `f` is a + * fixed-arity function value: a direct call of `f` with the tuple's + * positions and `thisArg` as the call-time receiver. Null for every other + * form (the existing Function.prototype paths keep them). */ +export function lowerForwardingApply( + lowerer: Lowerer, + call: ts.CallExpression, + access: ts.PropertyAccessExpression, +): IrExpr | null { + if (access.name.text !== "apply" || access.questionDotToken !== undefined) return null; + const argNodes = call.arguments.nodes; + if (argNodes.length !== 2 || argNodes.some(ts.isSpreadElement)) return null; + const tupleType = forwardedTupleType(lowerer, argNodes[1]!); + if (!tupleType) return null; + const callee = tryLowerExpression(lowerer, access.expression); + if (callee?.type.kind !== "func") return null; + const fnType = callee.type; + const loc = locOf(call); + if (fnType.rest === true) { + lowerer.unsupported( + "SC1090", + call, + "forwarding a fixed argument tuple through 'apply' to a variadic function value", + ); + } + const savedFn = lowerer.declareHiddenLocal("%applyFn", fnType); + const savedThis = lowerer.declareHiddenLocal("%applyThis", DYN); + const stmts: IrStmt[] = [ + { kind: "varDecl", localId: savedFn.id, init: callee, loc }, + { + kind: "varDecl", + localId: savedThis.id, + init: lowerer.lowerExprExpecting(argNodes[0]!, DYN), + loc, + }, + ]; + let reads = pureForwardedValues(lowerer, argNodes[1]!); + if (!reads) { + const savedArgs = lowerer.declareHiddenLocal("%applyArgs", tupleType); + stmts.push({ + kind: "varDecl", + localId: savedArgs.id, + init: lowerer.lowerExpr(stripTypeWrappers(argNodes[1]!)), + loc, + }); + reads = tupleReads( + lowerer, + () => ({ kind: "varRef", localId: savedArgs.id, type: tupleType, loc }), + tupleType, + loc, + ); + } + // JS drops surplus positions and fills missing ones with undefined. + const args = fnType.params.map((param, i): IrExpr => { + const read = reads[i]; + if (read) return lowerer.coerceInto(call, read, param); + const absent = omittedArgFor(lowerer, param, loc); + if (!absent) { + lowerer.unsupported( + "SC1090", + call, + "forwarding fewer arguments through 'apply' than the callee's required parameters", + ); + } + return absent; + }); + const invoked: IrExpr = { + kind: "callValue", + callee: { kind: "varRef", localId: savedFn.id, type: fnType, loc }, + receiver: { kind: "varRef", localId: savedThis.id, type: DYN, loc }, + args, + type: fnType.ret, + loc, + }; + return { kind: "seqExpr", stmts, result: invoked, type: fnType.ret, loc }; +} + +/** `const args = arguments as …` (or an alias of a rest tuple) inside a + * typed-arguments function: the binding holds the tuple itself — the + * assertion only re-describes the value — and stays a forwarding source. */ +export function lowerTupleAliasDecl(lowerer: Lowerer, decl: ts.VariableDeclaration): IrStmt | null { + if ( + !decl.initializer || + !ts.isIdentifier(decl.name) || + (ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) === 0 || + lowerer.fnStack.length === 0 + ) { + return null; + } + const tupleType = forwardedTupleType(lowerer, decl.initializer); + if (!tupleType) return null; + const symbol = lowerer.checker.getSymbolAtLocation(decl.name); + if (!symbol) return null; + const declared = lowerer.mapTypeOf(lowerer.typeOf(decl.name)); + if (declared && declared.kind !== "dyn" && JSON.stringify(declared) !== JSON.stringify(tupleType)) + return null; + lowerer.tupleAliasSymbols.set(symbol, tupleType); + const slots = pureSlotsOf(lowerer, decl.initializer); + if (slots) { + // An alias of a pure-forwarded tuple is itself only forwarded + // (onlyForwarded followed it): it names the same slots. + const local = lowerer.declareLocal(decl.name, decl.name.text, tupleType, false); + lowerer.ctx.pureTuples.set(local.id, slots); + return { kind: "block", body: [], loc: locOf(decl) }; + } + const init = lowerer.lowerExpr(stripTypeWrappers(decl.initializer)); + const local = lowerer.declareLocal(decl.name, decl.name.text, tupleType, false); + return { kind: "varDecl", localId: local.id, init, loc: locOf(decl) }; +} + +/** A spread operand forwarding a fixed tuple (forwardedTupleType), + * including one that resolved to no positions at all. */ +export function isForwardedTupleSpread(lowerer: Lowerer, node: ts.Expression): boolean { + return forwardedTupleType(lowerer, node) !== null; +} + +/** `arguments` read inside a typed-arguments function: its fixed arity. */ +export function typedArgumentsArity(lowerer: Lowerer, node: ts.Expression): number | null { + if (!ts.isIdentifier(node)) return null; + const t = forwardedTupleType(lowerer, node); + if (!t) return null; + if (t.kind !== "record") return 0; + const fields = lowerer.shapes.get(t.shapeId)?.fields ?? []; + // A position that may be undefined may also be absent at the call: the + // native signature always passes it, so the count is not static. + if ( + fields.some( + (f) => f.type.kind === "union" && lowerer.armTag(f.type.unionId, { kind: "undefinedT" }) >= 0, + ) + ) { + lowerer.unsupported( + "SC1090", + node.parent ?? node, + "'length' of an argument tuple whose positions may be omitted (the native signature always passes every position, so the count is not static)", + ); + } + return fields.length; +} + +/** The function whose `arguments` an identifier reads: the nearest + * enclosing non-arrow function. */ +export function argumentsOwner(node: ts.Node): ts.Node | null { + for (let cur = node.parent; cur !== undefined; cur = cur.parent) { + if (ts.isFunctionLike(cur) && !ts.isArrowFunction(cur)) return cur; + if (ts.isSourceFile(cur)) return null; + } + return null; +} diff --git a/packages/compiler/src/frontend/lowering/var-hoisting.ts b/packages/compiler/src/frontend/lowering/var-hoisting.ts index c445f1751..6276ab671 100644 --- a/packages/compiler/src/frontend/lowering/var-hoisting.ts +++ b/packages/compiler/src/frontend/lowering/var-hoisting.ts @@ -97,7 +97,7 @@ function hoistingScopeOf(node: ts.Node): Scope { ) return p; } - return node.getSourceFile(); + return ts.getSourceFileOfNode(node); } function statementsOf(scope: Scope): readonly ts.Statement[] { @@ -221,7 +221,7 @@ class ScopeAnalysis { * the statement at index `settle`. `read`: `ref` reads the binding * itself, so tsc vouches for one in the scope's own code. */ settles(scope: Scope, settle: number, ref: ts.Identifier, read = true): boolean { - if (ref.getSourceFile() !== this.sf) return true; + if (ts.getSourceFileOfNode(ref) !== this.sf) return true; const statements = statementsOf(scope); let deferred: ts.Node | null = null; let top: ts.Node | null = null; diff --git a/packages/compiler/src/frontend/lowering/width-lift.ts b/packages/compiler/src/frontend/lowering/width-lift.ts index d90f9ed35..e3722bc32 100644 --- a/packages/compiler/src/frontend/lowering/width-lift.ts +++ b/packages/compiler/src/frontend/lowering/width-lift.ts @@ -9,6 +9,7 @@ export type WidthLift = | { how: "discriminantWrap" } | { how: "discriminantRoute" } | { how: "retag" } + | { how: "unitRetag" } | { how: "liftWrap"; tag: number; arm: IrType } | { how: "width" } | { how: "unionWidth" } diff --git a/packages/compiler/src/frontend/mock-shapes.ts b/packages/compiler/src/frontend/mock-shapes.ts index 1baf2cd04..50781c272 100644 --- a/packages/compiler/src/frontend/mock-shapes.ts +++ b/packages/compiler/src/frontend/mock-shapes.ts @@ -12,10 +12,11 @@ * object; empty for mock.fn * * A MockFunctionCall is { arguments, error, result, this }: the - * arguments as a tuple of F's parameters, the result as F's return type or - * undefined (a checked-dynamic value for functions returning void or - * undefined, since a record field cannot have a unit type), and error and - * this as checked-dynamic values. */ + * arguments as a tuple of F's parameters (a checked-dynamic array for + * mock.fn()'s `(...args: any[]) => undefined`), the result as F's return + * type or undefined (a checked-dynamic value for functions returning void + * or undefined, since a record field cannot have a unit type), and error + * and this as checked-dynamic values. */ import { DYN, F64, NULL_T, UNDEFINED_T, VOID, typeKey, type IrType } from "../ir/ir.js"; import type { TypeMapperCtx } from "./type-mapper.js"; @@ -34,11 +35,17 @@ function orUndefined(t: IrType, reg: MockShapeRegistries): IrType { } /** The arguments type of a call record: a tuple of the parameters, or the - * rest array itself for a function whose only parameter is a rest array. - * Null for other rest shapes. A function without parameters records the + * rest array itself for a function whose only parameter is a rest array, + * or a checked-dynamic array for a function returning nothing whose only + * parameter is a checked-dynamic rest (mock.fn()'s default, `(...args: + * any[]) => undefined`). Null for other rest shapes. A function without parameters records the * empty tuple, which maps to an array of the unit-only union, as * type-mapper.ts maps `[]`. */ function mockArgumentsType(fn: IrType & { kind: "func" }, reg: MockShapeRegistries): IrType | null { + if (fn.rest && fn.restAbi === undefined) { + const returnsNothing = fn.ret.kind === "void" || fn.ret.kind === "undefinedT"; + return fn.params.length === 0 && returnsNothing ? DYN : null; + } if (fn.rest) { if (fn.params.length !== 1 || fn.params[0]!.kind !== "array") return null; return fn.params[0]!; diff --git a/packages/compiler/src/frontend/module-source-candidates.test.ts b/packages/compiler/src/frontend/module-source-candidates.test.ts index 4ff20decc..8fccef000 100644 --- a/packages/compiler/src/frontend/module-source-candidates.test.ts +++ b/packages/compiler/src/frontend/module-source-candidates.test.ts @@ -6,6 +6,7 @@ import * as ast from "./ts7/ast.js"; import { Ts7Api } from "./ts7/rpc-api.js"; import { Ts7Host } from "./ts7/program-host.js"; import { moduleSourceCandidates } from "./module-source-candidates.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; test("module candidates preserve nested call order and belong to one program snapshot", () => { const directory = mkdtempSync(join(tmpdir(), "scriptc-module-candidates-")); @@ -24,7 +25,7 @@ test("module candidates preserve nested call order and belong to one program sna const source = first.getSourceFile(entry)!; const candidates = moduleSourceCandidates(first, source); expect(candidates.specifiers).toEqual(["first", "second", "third", "fourth"]); - expect(candidates.calls.map((call) => call.expression.getText())).toEqual([ + expect(candidates.calls.map((call) => getTextOfNode(call.expression))).toEqual([ "outer", "require", "import", diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts new file mode 100644 index 000000000..e6dfe9a56 --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.test.ts @@ -0,0 +1,318 @@ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { basename, join } from "node:path"; +import { afterAll, expect, test } from "vitest"; +import { nodeFrontendServices } from "./services-node.js"; +import { rewriteBundlerCjsExports } from "./npm-static-rewrite.js"; +import type { BundledChunkSplit } from "./npm-static-bundled-chunks.js"; + +const keyedHelper = `var __getOwnPropNames = Object.getOwnPropertyNames; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +`; + +const dirs: string[] = []; +afterAll(() => { + for (const dir of dirs) rmSync(dir, { recursive: true, force: true }); +}); + +function split(source: string, file = "/pkg/dist/index.js") { + return nodeFrontendServices().splitCjsChunks(source, file); +} + +function run(file: string) { + const result = spawnSync(process.execPath, [file], { encoding: "utf8" }); + return { status: result.status, stdout: result.stdout, stderr: result.stderr }; +} + +/** Runs the original bundle and its split modules side by side. */ +function expectSameBehavior(source: string): BundledChunkSplit { + const dir = mkdtempSync(join(tmpdir(), "scriptc-chunks-")); + dirs.push(dir); + const original = join(dir, "original.js"); + writeFileSync(original, source); + const bundle = join(dir, "index.js"); + const result = split(source, bundle); + expect(result).not.toBeNull(); + expect(result).not.toHaveProperty("degrade"); + const answer = result as BundledChunkSplit; + writeFileSync(bundle, answer.text); + for (const chunk of answer.chunks) writeFileSync(chunk.path, chunk.source); + // Respelled calls and blanked declarations keep the line structure. + expect(answer.text.split("\n")).toHaveLength(source.split("\n").length); + const oracle = run(original); + expect(oracle.status).toBe(0); + expect(run(bundle)).toEqual(oracle); + return answer; +} + +test("chunks run once, at their first require, with shared module instances", () => { + const answer = expectSameBehavior(`"use strict"; +${keyedHelper} +var require_b = __commonJS({ + "b.js"(exports2, module2) { + console.log("b init"); + module2.exports = { n: 1 }; + } +}); +var require_a = __commonJS({ + "a.js"(exports2) { + console.log("a init"); + var b = require_b(); + exports2.value = b.n + 1; + exports2.keys = Object.keys({ exports2 }); + } +}); +console.log("start"); +console.log(require_a().value, require_a().value, require_b().n, require_a().keys); +`); + expect(answer.chunks.map((c) => basename(c.path))).toEqual([ + "index.js.require_b.cjs", + "index.js.require_a.cjs", + ]); + expect(answer.text).not.toContain("require_a()"); +}); + +test("the arrow helper form and minified spellings split the same way", () => { + expectSameBehavior( + `var __commonJS=(cb,mod)=>()=>(mod||cb((mod={exports:{}}).exports,mod),mod.exports);` + + `var require_x=__commonJS((exports,module)=>{module.exports={v:"x"}});` + + `console.log(require_x().v,require_x()===require_x());`, + ); +}); + +test("esbuild 0.11's Object.keys spelling is recognized", () => { + expectSameBehavior(`var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[Object.keys(cb)[0]])((mod = {exports: {}}).exports, mod), mod.exports; +}; +var require_y = __commonJS({ + "y.js"(exports) { + exports.y = 3; + } +}); +console.log(require_y().y); +`); +}); + +test("pure helpers a chunk reads travel with it", () => { + const answer = expectSameBehavior(`${keyedHelper}var __defProp = Object.defineProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); +var require_named = __commonJS({ + "named.js"(exports2, module2) { + module2.exports = __name(function () {}, "renamed"); + } +}); +console.log(require_named().name); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("var __name ="); + expect(chunk).toContain("var __defProp = Object.defineProperty;"); +}); + +test("a helper travels with a chunk whose nested scopes declare its name", () => { + const answer = expectSameBehavior(`${keyedHelper}function label(n) { + return "label " + n; +} +function wrap(n) { + function inner() { + var label = "inner"; + return label; + } + return label(n) + " " + inner(); +} +var require_e = __commonJS({ + "e.js"(exports2, module2) { + function nested() { + var label = "nested"; + return label; + } + var seen = []; + for (let label of ["loop"]) seen.push(label); + try { + throw "caught"; + } catch (label) { + seen.push(label); + } + { + const label = "block"; + seen.push(label); + } + module2.exports = { outer: label(1), indirect: (0, label)(3), wrapped: wrap(2), nested: nested(), seen }; + } +}); +var e = require_e(); +console.log(e.outer, e.indirect, e.wrapped, e.nested, e.seen.join(",")); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("function label(n)"); + expect(chunk).toContain("function wrap(n)"); +}); + +test("a named function expression that only calls itself travels with a chunk", () => { + const answer = expectSameBehavior(`${keyedHelper}var fact = function self(n) { + return n <= 1 ? 1 : n * self(n - 1); +}; +var kind = function self(self) { + return typeof self; +}; +var outer = function self() { + var self = 2; + return self; +}; +var require_g = __commonJS({ + "g.js"(exports2, module2) { + module2.exports = [fact(5), kind(1), outer()]; + } +}); +console.log(require_g().join(",")); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("var fact = function self(n)"); +}); + +test("a helper a parameter default reads travels with a chunk whose body declares its name", () => { + const answer = expectSameBehavior(`${keyedHelper}var ten = function () { + return 10; +}; +var add = function (n, base = ten()) { + var ten = 1; + return n + base + ten; +}; +var require_g = __commonJS({ + "g.js"(exports2, module2) { + module2.exports = add(1); + } +}); +console.log(require_g()); +`); + const chunk = answer.chunks[0]!.source; + expect(chunk).toContain("var ten = function ()"); +}); + +test.each([ + ["exports it", `function tally() {}`, `module2.exports = { tally };`], + ["reads its properties", `function tally() {}`, `exports2.calls = tally.calls;`], + ["constructs it", `function tally() {}`, `exports2.made = new tally();`], + ["stores an arrow helper", `var tally = () => 1;`, `exports2.fn = tally;`], + [ + "calls a function that reads its own name as a value", + `var tally = function self() { self.n = (self.n || 0) + 1; return self.n; };`, + `exports2.count = tally();`, + ], + [ + "calls a function whose parameter default reads its own name", + `var tally = function self(box = self) { box.n = (box.n || 0) + 1; return box.n; };`, + `exports2.count = tally();`, + ], + [ + "calls a function whose parameter default reads its own name past a body var of that name", + `var tally = function self(box = self) { var self = 0; box.n = (box.n || 0) + 1; return box.n + self; };`, + `exports2.count = tally();`, + ], +])("a chunk that %s keeps the package refused", (_, declaration, statement) => { + const result = split(`${keyedHelper}${declaration} +var require_f = __commonJS({ + "f.js"(exports2, module2) { + ${statement} + } +}); +require_f(); +`); + expect(result).toEqual({ + degrade: expect.stringContaining( + "uses the bundle's top-level function 'tally' as a value", + ) as string, + }); +}); + +test.each([ + ["reads its factory's receiver", `console.log(this);`], + ["reads its factory's arguments object", `console.log(arguments.length);`], + ["returns from its top level", `if (exports2) return;`], + ["uses its module object beyond module.exports", `console.log(module2.id);`], + ["reads __filename", `console.log(__filename);`], + ["rebinds its module parameters", `function f(module2) { return module2; }`], + ["reads the bundle's top-level binding 'state'", `console.log(state);`], +])("a chunk that %s keeps the package refused", (reason, statement) => { + const result = split(`${keyedHelper}var state = { n: 1 }; +var require_c = __commonJS({ + "c.js"(exports2, module2) { + ${statement} + } +}); +require_c(); +`); + expect(result).toEqual({ degrade: expect.stringContaining(reason) as string }); +}); + +test("a factory used other than as a direct call keeps the package refused", () => { + const result = + split(`${keyedHelper}var require_d = __commonJS({ "d.js"(exports2) { exports2.d = 1; } }); +var load = require_d; +load(); +`); + expect(result).toEqual({ + degrade: expect.stringContaining("used other than as a direct call") as string, + }); +}); + +test("an unrecognized helper body keeps the package refused", () => { + const result = + split(`var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod); +var require_e = __commonJS((exports) => { exports.e = 1; }); +require_e(); +`); + expect(result).toEqual({ degrade: expect.stringContaining("__commonJS helper") as string }); +}); + +test("files without chunks and ES-module bundles are left alone", () => { + expect(split("exports.x = 1;\n")).toBeNull(); + expect( + split(`${keyedHelper}var require_f = __commonJS({ "f.js"(e) { e.f = 1; } }); +export default require_f(); +`), + ).toBeNull(); +}); + +test("an identifier-minified bundle names its lost helper names", () => { + // esbuild --minify output: the helper shapes survive, their names do not. + const result = split( + `var c=(r,e)=>()=>(e||r((e={exports:{}}).exports,e),e.exports);` + + `var x=c((t,m)=>{m.exports={v:1}});console.log(x().v);`, + ); + expect(result).toEqual({ degrade: expect.stringContaining("identifier-minified") as string }); +}); + +test("an ES-module bundle of CommonJS chunks names the chunk interop", () => { + const result = rewriteBundlerCjsExports( + `${keyedHelper}var __toESM = (mod) => mod; +var require_g = __commonJS({ "g.js"(exports) { exports.g = 1; } }); +var import_g = __toESM(require_g()); +export const g = import_g.g; +`, + "/pkg/dist/index.js", + ); + expect(result).toEqual({ + degrade: expect.stringContaining("bundled CommonJS modules") as string, + }); +}); + +test("a chunk that only re-exports a builtin module becomes that module", () => { + const answer = expectSameBehavior(`${keyedHelper}var require_stream = __commonJS({ + "lib/stream.js"(exports2, module2) { + module2.exports = require("stream"); + } +}); +var require_user = __commonJS({ + "lib/user.js"(exports2) { + var Stream = require_stream(); + exports2.same = Stream === require("stream"); + } +}); +console.log(typeof require_stream().PassThrough, require_user().same); +`); + expect(answer.chunks.map((c) => basename(c.path))).toEqual(["index.js.require_user.cjs"]); + expect(answer.text).toContain('typeof require("stream").PassThrough'); +}); diff --git a/packages/compiler/src/frontend/npm-static-bundled-chunks.ts b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts new file mode 100644 index 000000000..b625c456d --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-bundled-chunks.ts @@ -0,0 +1,910 @@ +/* Bundled CommonJS chunks: esbuild wraps every CommonJS module it bundles + * in a lazily evaluated factory, + * + * var require_x = __commonJS({ "src/x.js"(exports, module) { BODY } }); + * + * and each `require_x()` runs BODY once (later calls answer the cached + * module.exports, a call during BODY answers the partial one). That is + * exactly a CommonJS module that the bundle requires at the call's + * position, so each recognized chunk becomes its own module: a virtual + * sibling file `..cjs` holding BODY, its module + * parameters respelled as the CommonJS globals, and every `require_x()` + * call becomes `require("./..cjs")`. The program then + * compiles chunks with the ordinary CommonJS machinery: evaluation at the + * require's position, the run-once module cache, export tables, and the + * __toESM interop erasure. + * + * Only shapes whose meaning is verified move: the helper must be one of + * the recognized __commonJS spellings, every reference to a factory + * binding must be a direct call, and BODY must not observe the + * difference between a factory and a module (its `this`, `arguments`, + * `__filename`, the module object beyond `module.exports`, a top-level + * return, or a binding of the bundle other than the pure helpers copied + * beside it). Anything else answers a degrade reason. */ +import * as ts from "./ts7/syntax.js"; +import { helperTokens } from "./helper-tokens.js"; +import { canonicalBuiltinModule } from "./builtin-modules.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; + +export interface BundledChunkSplit { + /** The bundle with chunk declarations blanked and calls respelled. */ + text: string; + /** Virtual sibling modules, in declaration order. */ + chunks: { path: string; source: string }[]; +} + +export interface Edit { + start: number; + end: number; + text: string; +} + +const CHUNK_SHAPE_DEGRADE = + "its shipped JS bundles CommonJS modules through the __commonJS helper in a shape the " + + "recognizer cannot verify"; + +/** The helper a counter-suffixed bundler name spells (`__commonJS2`). */ +export function helperBase(name: string): string { + return /^__[A-Za-z]+\d+$/.test(name) ? name.replace(/\d+$/, "") : name; +} + +export function unparen(e: ts.Expression): ts.Expression { + while (ts.isParenthesizedExpression(e)) e = e.expression; + return e; +} + +/** True for an identifier occurrence that reads or writes a binding (as + * opposed to a property name, a label, or a declaration's own name). + * Shorthand property values count: `{ x }` reads x. */ +export function isReference(id: ts.Identifier): boolean { + const p = id.parent; + if (p === undefined) return false; + if (ts.isPropertyAccessExpression(p)) return p.expression === id; + if (ts.isPropertyAssignment(p)) return p.initializer === id; + if ( + ts.isMethodDeclaration(p) || + ts.isPropertyDeclaration(p) || + ts.isGetAccessorDeclaration(p) || + ts.isSetAccessorDeclaration(p) + ) + return p.name !== id; + if (ts.isBindingElement(p)) return p.initializer === id; + if (ts.isVariableDeclaration(p) || ts.isParameter(p)) return p.initializer === id; + if ( + ts.isFunctionDeclaration(p) || + ts.isFunctionExpression(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p) + ) + return p.name !== id; + if (ts.isLabeledStatement(p)) return p.label !== id; + if (ts.isBreakStatement(p) || ts.isContinueStatement(p)) return false; + if (ts.isMetaProperty(p)) return false; + return true; +} + +/** Names a subtree declares anywhere inside it: variables (destructuring + * included), parameters, function and class names, catch bindings. */ +export function declaredNames(root: ts.Node): Set { + const names = new Set(); + // Binding patterns nest BindingElements, which the walk visits too. + ts.walkPreorder(root, (n) => { + if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) + return "skip"; + if (ts.isVariableDeclaration(n) || ts.isParameter(n) || ts.isBindingElement(n)) { + const name = n.name; + if (name !== undefined && ts.isIdentifier(name)) names.add(name.text); + } else if ( + (ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isClassDeclaration(n) || + ts.isClassExpression(n)) && + n.name !== undefined + ) + names.add(n.name.text); + return undefined; + }); + return names; +} + +/** Adds the identifiers a binding name (an identifier or a destructuring + * pattern) declares. */ +function addBindingNames(name: ts.BindingName, out: Set): void { + if (ts.isIdentifier(name)) { + out.add(name.text); + return; + } + for (const element of name.elements.nodes) { + if (!ts.isOmittedExpression(element) && element.name !== undefined) + addBindingNames(element.name, out); + } +} + +function isFunctionScope(n: ts.Node): n is ts.SignatureDeclaration { + return ( + ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isArrowFunction(n) || + ts.isMethodDeclaration(n) || + ts.isGetAccessorDeclaration(n) || + ts.isSetAccessorDeclaration(n) || + ts.isConstructorDeclaration(n) + ); +} + +/** The `var` names a function body declares, without entering nested + * functions. Function declarations in nested blocks count as block-scoped + * here; treating one as free only adds an unused helper copy. */ +function varScopedNames(body: ts.Node): Set { + const out = new Set(); + ts.walkPreorder(body, (n) => { + if (n !== body && (isFunctionScope(n) || ts.isClassDeclaration(n) || ts.isClassExpression(n))) + return "skip"; + if (ts.isVariableDeclarationList(n) && (n.flags & ts.NodeFlags.BlockScoped) === 0) { + for (const d of n.declarations.nodes) addBindingNames(d.name, out); + } + return undefined; + }); + return out; +} + +/** The names a function binds for references in its parameter list + * (default values, computed keys of destructuring patterns): its + * parameters and a function expression's own name. Parameter initializers + * run in their own scope, so the body's `var`s are not visible there. */ +function parameterScopeNames(scope: ts.SignatureDeclaration): Set { + const out = new Set(); + for (const param of scope.parameters.nodes) addBindingNames(param.name, out); + if (ts.isFunctionExpression(scope) && scope.name !== undefined) out.add(scope.name.text); + return out; +} + +const NO_NAMES: ReadonlySet = new Set(); + +/** True when `node` lies inside `ancestor` (or is it). */ +function within(node: ts.Node, ancestor: ts.Node): boolean { + for (let n: ts.Node | undefined = node; n !== undefined; n = n.parent) + if (n === ancestor) return true; + return false; +} + +/** The names one scope node binds for the references inside it. */ +function scopeNames(scope: ts.Node): Set { + const out = new Set(); + if (isFunctionScope(scope)) { + for (const param of scope.parameters.nodes) addBindingNames(param.name, out); + if (ts.isFunctionExpression(scope) && scope.name !== undefined) out.add(scope.name.text); + if (scope.body !== undefined) for (const name of varScopedNames(scope.body)) out.add(name); + } else if (ts.isBlock(scope) || ts.isSourceFile(scope) || ts.isCaseBlock(scope)) { + const statements = ts.isCaseBlock(scope) + ? scope.clauses.nodes.flatMap((clause) => clause.statements.nodes) + : scope.statements.nodes; + for (const stmt of statements) { + if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && stmt.name) + out.add(stmt.name.text); + else if ( + ts.isVariableStatement(stmt) && + (stmt.declarationList.flags & ts.NodeFlags.BlockScoped) !== 0 + ) { + for (const d of stmt.declarationList.declarations.nodes) addBindingNames(d.name, out); + } + } + } else if (ts.isCatchClause(scope) && scope.variableDeclaration !== undefined) { + addBindingNames(scope.variableDeclaration.name, out); + } else if ( + (ts.isForStatement(scope) || ts.isForInStatement(scope) || ts.isForOfStatement(scope)) && + scope.initializer !== undefined && + ts.isVariableDeclarationList(scope.initializer) && + (scope.initializer.flags & ts.NodeFlags.BlockScoped) !== 0 + ) { + for (const d of scope.initializer.declarations.nodes) addBindingNames(d.name, out); + } else if ( + (ts.isClassDeclaration(scope) || ts.isClassExpression(scope)) && + scope.name !== undefined + ) { + out.add(scope.name.text); + } + return out; +} + +/** Resolves references against one bundle's scopes, keeping each scope's + * names for the bundle's split. */ +class BundleScopes { + private readonly names = new Map>(); + private readonly parameterNames = new Map>(); + + private of(scope: ts.Node): Set { + let names = this.names.get(scope); + if (names === undefined) { + names = scopeNames(scope); + this.names.set(scope, names); + } + return names; + } + + /** The names `scope` binds for a reference reached through its child + * `from`: a function's parameter list sees its parameters and own name + * but not its body's `var`s, and a computed method name sees none of + * them. */ + private visible(scope: ts.Node, from: ts.Node): ReadonlySet { + if (!isFunctionScope(scope) || from === scope.body) return this.of(scope); + if (from === scope.name) return NO_NAMES; + let names = this.parameterNames.get(scope); + if (names === undefined) { + names = parameterScopeNames(scope); + this.parameterNames.set(scope, names); + } + return names; + } + + /** True when a scope between the reference and `root` (inclusive) + * binds its name. */ + boundWithin(id: ts.Identifier, root: ts.Node): boolean { + for (let from: ts.Node = id, n = id.parent; n !== undefined; from = n, n = n.parent) { + if (this.visible(n, from).has(id.text)) return true; + if (n === root) return false; + } + return false; + } + + /** True when a scope inside `root` (exclusive) binds the reference's + * name. */ + boundInside(id: ts.Identifier, root: ts.Node): boolean { + for ( + let from: ts.Node = id, n = id.parent; + n !== undefined && n !== root; + from = n, n = n.parent + ) { + if (this.visible(n, from).has(id.text)) return true; + } + return false; + } + + /** True when the reference resolves to a top-level binding of `sf`. */ + topLevel(id: ts.Identifier, sf: ts.SourceFile): boolean { + for ( + let from: ts.Node = id, n = id.parent; + n !== undefined && n !== sf; + from = n, n = n.parent + ) { + if (this.visible(n, from).has(id.text)) return false; + } + return true; + } +} + +/** Every binding reference inside a subtree. */ +export function references(root: ts.Node): ts.Identifier[] { + const out: ts.Identifier[] = []; + ts.walkPreorder(root, (n) => { + if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) + return "skip"; + if (ts.isIdentifier(n) && isReference(n)) out.push(n); + return undefined; + }); + return out; +} + +/** True when the reference is written (assignment target, update operand, + * loop target, delete operand). */ +export function isWritten(id: ts.Identifier): boolean { + let child: ts.Node = id; + for (let p = child.parent; p !== undefined; child = p, p = p.parent) { + if (ts.isBinaryExpression(p)) { + return ( + p.left === child && + p.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + p.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ); + } + if (ts.isPrefixUnaryExpression(p) || ts.isPostfixUnaryExpression(p)) { + return ( + p.operator === ts.SyntaxKind.PlusPlusToken || p.operator === ts.SyntaxKind.MinusMinusToken + ); + } + if (ts.isForInStatement(p) || ts.isForOfStatement(p)) return p.initializer === child; + if (ts.isDeleteExpression(p)) return true; + if ( + !( + ts.isParenthesizedExpression(p) || + ts.isArrayLiteralExpression(p) || + ts.isObjectLiteralExpression(p) || + ts.isShorthandPropertyAssignment(p) || + ts.isPropertyAssignment(p) || + ts.isSpreadElement(p) + ) + ) + return false; + } + return false; +} + +/** The recognized __commonJS helper forms, as token templates over the + * helper's own parameter names (CB, MOD) and the own-key reader (KEYS: + * esbuild's `__getOwnPropNames` alias, or `Object.keys` in esbuild 0.11): + * keyed: `(CB, MOD) => function [name]() { return ; }` — the + * factory is the only method of an object literal; + * arrow: `(CB, MOD) => () => ()` — the factory itself. */ +const KEYED_RETURN = + "MOD || (0, CB[KEYS(CB)[0]])((MOD = { exports: {} }).exports, MOD), MOD.exports"; +const ARROW_BODY = "(MOD || CB((MOD = { exports: {} }).exports, MOD), MOD.exports)"; + +function template(text: string, cb: string, mod: string, keys: string): string | null { + return helperTokens( + text + .replace(/\bCB\b/g, cb) + .replace(/\bMOD\b/g, mod) + .replace(/\bKEYS\b/g, keys), + ); +} + +type HelperForm = "keyed" | "arrow"; + +/** The helper's form, or null when its initializer is not recognized. + * `ownNames` answers whether an identifier names the verified + * Object.getOwnPropertyNames alias. */ +function helperForm( + init: ts.Expression, + sf: ts.SourceFile, + ownNames: (name: string) => boolean, +): HelperForm | null { + const fn = unparen(init); + if (!ts.isArrowFunction(fn) || fn.parameters.nodes.length !== 2) return null; + const params = fn.parameters.nodes.map((p) => + ts.isIdentifier(p.name) && p.initializer === undefined && p.dotDotDotToken === undefined + ? p.name.text + : null, + ); + const [cb, mod] = params; + if (cb === null || mod === null || cb === undefined || mod === undefined || cb === mod) + return null; + if (ts.isBlock(fn.body)) return null; + const body = unparen(fn.body); + if (ts.isFunctionExpression(body)) { + if (body.parameters.nodes.length !== 0 || body.asteriskToken || body.modifiers?.nodes.length) + return null; + const stmts = body.body.statements.nodes; + const ret = stmts[0]; + if (stmts.length !== 1 || ret === undefined || !ts.isReturnStatement(ret) || !ret.expression) + return null; + const actual = helperTokens(getTextOfNode(ret.expression)); + if (actual === null) return null; + // The own-key reader: esbuild's alias or the 0.11 spelling. + const keysRef = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(getTextOfNode(ret.expression)); + const keys = keysRef?.[1]; + if (keys === undefined || (keys !== "Object.keys" && !ownNames(keys))) return null; + return actual === template(KEYED_RETURN, cb, mod, keys) ? "keyed" : null; + } + if (ts.isArrowFunction(body) && body.parameters.nodes.length === 0 && !ts.isBlock(body.body)) { + const actual = helperTokens(getTextOfNode(body.body)); + return actual !== null && actual === template(ARROW_BODY, cb, mod, "") ? "arrow" : null; + } + return null; +} + +/** A top-level declaration the virtual chunk can copy verbatim: a pure + * helper whose evaluation runs nothing (a function, or a member read of + * Object such as `Object.create` / `Object.prototype.hasOwnProperty`). */ +function copyableInitializer(init: ts.Expression | undefined): boolean { + if (init === undefined) return false; + const e = unparen(init); + if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return true; + let walk: ts.Expression = e; + let depth = 0; + while (ts.isPropertyAccessExpression(walk) && ts.isIdentifier(walk.name)) { + walk = walk.expression; + depth++; + } + return depth > 0 && ts.isIdentifier(walk) && walk.text === "Object"; +} + +/** A top-level declaration whose value is a function. */ +function isFunctionValued(node: ts.VariableDeclaration | ts.FunctionDeclaration): boolean { + if (ts.isFunctionDeclaration(node)) return true; + const init = node.initializer && unparen(node.initializer); + return init !== undefined && (ts.isArrowFunction(init) || ts.isFunctionExpression(init)); +} + +/** True when a named function expression initializer (`var f = function + * self() {}`) uses its own name only as a callee: `self` is the copied + * function, so it is as observable as the top-level binding. */ +function selfCalledOnly( + node: ts.VariableDeclaration | ts.FunctionDeclaration, + scopes: BundleScopes, +): boolean { + if (!ts.isVariableDeclaration(node) || node.initializer === undefined) return true; + const fn = unparen(node.initializer); + if (!ts.isFunctionExpression(fn) || fn.name === undefined || fn.body === undefined) return true; + const self = fn.name.text; + // A parameter of the same name hides the function's own name everywhere + // in it; a `var` of the same name hides it only in the body, since the + // parameter initializers do not see the body's `var`s. + if (fn.parameters.nodes.some((p) => bindsName(p.name, self))) return true; + const bodyVar = varScopedNames(fn.body).has(self); + const body = fn.body; + return references(fn).every( + (r) => + r.text !== self || scopes.boundInside(r, fn) || (bodyVar && within(r, body)) || isCallee(r), + ); +} + +/** True when a binding name (an identifier or a destructuring pattern) + * declares `name`. */ +function bindsName(binding: ts.BindingName, name: string): boolean { + const names = new Set(); + addBindingNames(binding, names); + return names.has(name); +} + +/** True when a reference is the function a call invokes: `f(...)`, or the + * `(0, f)(...)` form that drops the receiver. */ +function isCallee(id: ts.Identifier): boolean { + let callee: ts.Node = id; + const p = id.parent; + if ( + p !== undefined && + ts.isBinaryExpression(p) && + p.operatorToken.kind === ts.SyntaxKind.CommaToken && + p.right === id && + p.parent !== undefined && + ts.isParenthesizedExpression(p.parent) + ) + callee = p.parent; + while (callee.parent !== undefined && ts.isParenthesizedExpression(callee.parent)) + callee = callee.parent; + const call = callee.parent; + return call !== undefined && ts.isCallExpression(call) && call.expression === callee; +} + +/** One recognized chunk declaration. */ +interface Chunk { + name: string; + stmt: ts.VariableStatement; + params: string[]; + body: ts.Block; + path: string; + spec: string; +} + +interface TopLevel { + stmt: ts.Statement; + /** The declarator (or function declaration) itself. */ + node: ts.VariableDeclaration | ts.FunctionDeclaration; +} + +/** True when the file opens with a "use strict" directive prologue. */ +export function strictPrologue(stmts: readonly ts.Statement[]): boolean { + for (const s of stmts) { + if (!ts.isExpressionStatement(s) || !ts.isStringLiteral(s.expression)) return false; + if (s.expression.text === "use strict") return true; + } + return false; +} + +/** A cheap text probe: the helper's name, or its minified body's + * `(m={exports:{}}).exports,m` fragment. */ +export function mayBundleChunks(source: string): boolean { + return ( + source.includes("__commonJS") || + /\{\s*exports\s*:\s*\{\s*\}\s*\}\s*\)\s*\.exports\s*,/.test(source) + ); +} + +/** Splits a CommonJS bundle's __commonJS chunks into virtual modules (see + * the header). Null: no chunks (or not a CommonJS bundle) — serve the file + * unchanged. */ +export function splitBundledChunks( + sf: ts.SourceFile, + filePath: string, +): BundledChunkSplit | { degrade: string } | null { + const source = sf.text; + if (!mayBundleChunks(source)) return null; + // ES-module bundles have no require: their chunks keep the existing + // treatment (the Bun factory recognizer, or the ESM interop refusal). + if (!filePath.endsWith(".cjs") && ts.isExternalModule(sf)) return null; + + const top = new Map(); + const duplicate = new Set(); + for (const stmt of sf.statements.nodes) { + const add = (name: string, node: TopLevel["node"]): void => { + if (top.has(name)) duplicate.add(name); + else top.set(name, { stmt, node }); + }; + if (ts.isFunctionDeclaration(stmt) && stmt.name !== undefined) add(stmt.name.text, stmt); + else if (ts.isVariableStatement(stmt)) { + for (const d of stmt.declarationList.declarations.nodes) { + if (ts.isIdentifier(d.name)) add(d.name.text, d); + } + } + } + const helperNames = [...top.keys()].filter((n) => helperBase(n) === "__commonJS"); + + // Every name a recognizer relies on is written nowhere and declared + // once at the top level; `Object` itself is never rebound. + const allRefs = references(sf); + const written = new Set(); + for (const r of allRefs) if (isWritten(r)) written.add(r.text); + const declaredAnywhere = declaredNames(sf); + if (declaredAnywhere.has("Object") || written.has("Object")) + return { degrade: CHUNK_SHAPE_DEGRADE }; + const stable = (name: string): boolean => !duplicate.has(name) && !written.has(name); + // Names whose every reference in the bundle is a callee: `f(...)` or the + // `(0, f)(...)` form. + const scopes = new BundleScopes(); + const valueUsed = new Set(); + for (const r of allRefs) if (!isCallee(r) && scopes.topLevel(r, sf)) valueUsed.add(r.text); + const calledOnly = (name: string): boolean => !valueUsed.has(name); + const ownNames = (name: string): boolean => { + const t = top.get(name); + if (t === undefined || !stable(name) || !ts.isVariableDeclaration(t.node)) return false; + const init = t.node.initializer; + return ( + init !== undefined && + helperTokens(getTextOfNode(init)) === helperTokens("Object.getOwnPropertyNames") + ); + }; + if (helperNames.length === 0) { + // The helper's shape under another name: an identifier-minified bundle, + // whose other interop helpers lost their names as well. + for (const [name, t] of top) { + const init = ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + if (init !== undefined && stable(name) && helperForm(init, sf, ownNames) !== null) + return { + degrade: + "its shipped JS is an identifier-minified bundle whose bundler-interop helpers " + + "(__commonJS and the rest) have lost the names the recognizers verify", + }; + } + return null; + } + const forms = new Map(); + for (const name of helperNames) { + const t = top.get(name)!; + const init = ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + const form = init !== undefined && stable(name) ? helperForm(init, sf, ownNames) : null; + if (form === null) return { degrade: CHUNK_SHAPE_DEGRADE }; + forms.set(name, form); + } + + // The chunk declarations: `var NAME = HELPER(factory);`, one declarator. + const chunks = new Map(); + const base = filePath.replace(/^.*[\\/]/, ""); + for (const [name, t] of top) { + if (!ts.isVariableDeclaration(t.node) || t.node.initializer === undefined) continue; + const call = unparen(t.node.initializer); + if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression)) continue; + const form = forms.get(call.expression.text); + if (form === undefined) continue; + const stmt = t.stmt; + if ( + !ts.isVariableStatement(stmt) || + stmt.declarationList.declarations.nodes.length !== 1 || + call.arguments.nodes.length !== 1 || + !stable(name) + ) + return { degrade: CHUNK_SHAPE_DEGRADE }; + let factory: ts.Node = call.arguments.nodes[0]!; + if (form === "keyed") { + if (!ts.isObjectLiteralExpression(factory) || factory.properties.nodes.length !== 1) + return { degrade: CHUNK_SHAPE_DEGRADE }; + factory = factory.properties.nodes[0]!; + if (!ts.isMethodDeclaration(factory) || ts.isComputedPropertyName(factory.name)) + return { degrade: CHUNK_SHAPE_DEGRADE }; + } else { + factory = unparen(factory as ts.Expression); + if (!ts.isArrowFunction(factory) && !ts.isFunctionExpression(factory)) + return { degrade: CHUNK_SHAPE_DEGRADE }; + } + const fn = factory as ts.MethodDeclaration | ts.ArrowFunction | ts.FunctionExpression; + if ( + fn.body === undefined || + !ts.isBlock(fn.body) || + fn.asteriskToken !== undefined || + (fn.modifiers?.nodes.length ?? 0) > 0 || + fn.parameters.nodes.length > 2 + ) + return { degrade: CHUNK_SHAPE_DEGRADE }; + const params: string[] = []; + for (const p of fn.parameters.nodes) { + if (!ts.isIdentifier(p.name) || p.initializer !== undefined || p.dotDotDotToken) + return { degrade: CHUNK_SHAPE_DEGRADE }; + params.push(p.name.text); + } + const file = `${base}.${name}.cjs`; + chunks.set(name, { + name, + stmt, + params, + body: fn.body, + path: filePath.slice(0, filePath.length - base.length) + file, + spec: `./${file}`, + }); + } + if (chunks.size === 0) return null; + + // Every factory reference is a direct zero-argument call. Calls inside a + // chunk body belong to that chunk's text, the rest to the bundle's. + const callEdits: Edit[] = []; + for (const r of allRefs) { + const chunk = chunks.get(r.text); + if (chunk === undefined) continue; + const call = r.parent; + if ( + call === undefined || + !ts.isCallExpression(call) || + call.expression !== r || + call.arguments.nodes.length !== 0 || + call.questionDotToken !== undefined + ) + return { + degrade: `its bundled CommonJS module factory '${r.text}' is used other than as a direct call`, + }; + const forwarded = forwarderSpec(chunk); + callEdits.push({ + start: tokenStartOfNode(call), + end: call.end, + text: `require(${JSON.stringify(forwarded ?? chunk.spec)})`, + }); + } + for (const name of chunks.keys()) { + if (declaredAnywhere.has(name) && countDeclarations(sf, name) > 1) + return { degrade: CHUNK_SHAPE_DEGRADE }; + } + + const bundleStrict = strictPrologue(sf.statements.nodes); + const out: BundledChunkSplit["chunks"] = []; + const bundleEdits: Edit[] = [...callEdits]; + for (const chunk of chunks.values()) { + if (forwarderSpec(chunk) !== null) { + // A chunk that only re-exports a builtin module is that module. + bundleEdits.push({ start: tokenStartOfNode(chunk.stmt), end: chunk.stmt.end, text: "" }); + continue; + } + const result = chunkSource( + sf, + chunk, + chunks, + top, + stable, + calledOnly, + scopes, + callEdits, + bundleStrict, + ); + if (typeof result !== "string") return result; + out.push({ path: chunk.path, source: result }); + bundleEdits.push({ start: tokenStartOfNode(chunk.stmt), end: chunk.stmt.end, text: "" }); + } + return { text: applyEdits(source, 0, source.length, bundleEdits), chunks: out }; +} + +export function countDeclarations(sf: ts.SourceFile, name: string): number { + let n = 0; + ts.walkPreorder(sf, (node) => { + if ( + (ts.isVariableDeclaration(node) || + ts.isParameter(node) || + ts.isBindingElement(node) || + ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isClassDeclaration(node) || + ts.isClassExpression(node)) && + node.name !== undefined && + ts.isIdentifier(node.name) && + node.name.text === name + ) + n++; + return undefined; + }); + return n; +} + +/** Applies non-overlapping edits inside [from, to): replaced spans keep + * their newlines (blanked text stays line-aligned); edits nested in a + * blanked span vanish with it. */ +export function applyEdits(source: string, from: number, to: number, edits: Edit[]): string { + const inside = edits + .filter((e) => e.start >= from && e.end <= to) + .sort((a, b) => a.start - b.start || b.end - a.end); + let text = ""; + let at = from; + for (const e of inside) { + if (e.start < at) continue; // nested in an earlier edit + text += source.slice(at, e.start); + const replaced = source.slice(e.start, e.end); + // A respelled span keeps its line breaks after the replacement. + text += + e.text === "" ? replaced.replace(/[^\r\n]/g, " ") : e.text + replaced.replace(/[^\n]/g, ""); + at = e.end; + } + return text + source.slice(at, to); +} + +/** The builtin module a chunk forwards, or null: a body that is exactly + * `module.exports = require("")` (readable-stream's + * internal/streams/stream.js). Requiring the chunk is then requiring the + * builtin itself (same object, and loading a builtin runs no code). */ +function forwarderSpec(chunk: Chunk): string | null { + const moduleParam = chunk.params[1]; + if (moduleParam === undefined) return null; + const stmts = chunk.body.statements.nodes.filter( + (st) => !(ts.isExpressionStatement(st) && ts.isStringLiteral(st.expression)), + ); + const only = stmts[0]; + if (stmts.length !== 1 || only === undefined || !ts.isExpressionStatement(only)) return null; + const e = unparen(only.expression); + if ( + !ts.isBinaryExpression(e) || + e.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isPropertyAccessExpression(e.left) || + !ts.isIdentifier(e.left.expression) || + e.left.expression.text !== moduleParam || + e.left.name.text !== "exports" + ) + return null; + const call = unparen(e.right); + if ( + !ts.isCallExpression(call) || + !ts.isIdentifier(call.expression) || + call.expression.text !== "require" || + call.arguments.nodes.length !== 1 || + !ts.isStringLiteral(call.arguments.nodes[0]!) + ) + return null; + const spec = (call.arguments.nodes[0] as ts.StringLiteral).text; + return canonicalBuiltinModule(spec) !== null ? spec : null; +} + +/** How code in a factory body (a chunk or a lazy ES-module initializer) + * observes the factory's own frame, or null: its receiver, its arguments + * object, `super`, a meta property, or (when `returns`) a top-level + * return have no module-scope equivalent. */ +export function factoryFrameUse(body: ts.Block, returns: boolean): string | null { + let frameUse: string | null = null; + /** The innermost frame boundary between a node and the factory body. */ + const frameOf = (n: ts.Node): "factory" | "arrow" | "function" => { + let arrow = false; + for (let p = n.parent; p !== undefined && p !== body; p = p.parent) { + if ( + ts.isFunctionDeclaration(p) || + ts.isFunctionExpression(p) || + ts.isMethodDeclaration(p) || + ts.isGetAccessorDeclaration(p) || + ts.isSetAccessorDeclaration(p) || + ts.isConstructorDeclaration(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p) + ) + return "function"; + if (ts.isArrowFunction(p)) arrow = true; + } + return arrow ? "arrow" : "factory"; + }; + ts.walkPreorder(body, (n) => { + if (frameUse !== null) return "stop"; + if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) + return "skip"; + let use: string | null = null; + if (n.kind === ts.SyntaxKind.ThisKeyword) use = "reads its factory's receiver (`this`)"; + else if (n.kind === ts.SyntaxKind.SuperKeyword) use = "reads `super` at its top level"; + else if (ts.isIdentifier(n) && n.text === "arguments" && isReference(n)) + use = "reads its factory's arguments object"; + else if (ts.isMetaProperty(n)) use = "reads a meta property at its top level"; + else if (ts.isReturnStatement(n)) { + if (returns && frameOf(n) === "factory") frameUse = "returns from its top level"; + return undefined; + } + if (use !== null && frameOf(n) !== "function") frameUse = use; + return undefined; + }); + return frameUse; +} + +/** The virtual module text for one chunk, or a degrade reason. */ +function chunkSource( + sf: ts.SourceFile, + chunk: Chunk, + chunks: ReadonlyMap, + top: ReadonlyMap, + stable: (name: string) => boolean, + calledOnly: (name: string) => boolean, + scopes: BundleScopes, + callEdits: readonly Edit[], + bundleStrict: boolean, +): string | { degrade: string } { + const why = (reason: string): { degrade: string } => ({ + degrade: `its bundled CommonJS module '${chunk.name}' ${reason}`, + }); + const [exportsParam, moduleParam] = chunk.params; + const body = chunk.body; + const local = declaredNames(body); + for (const p of chunk.params) { + if ( + local.has(p) || + (p !== "exports" && local.has("exports")) || + (p !== "module" && local.has("module")) + ) + return why( + "rebinds its module parameters inside its body (an identifier-minified bundle the recognizer cannot respell)", + ); + } + + const frameUse = factoryFrameUse(body, true); + if (frameUse !== null) return why(frameUse); + + const edits: Edit[] = callEdits.filter((e) => e.start >= body.pos && e.end <= body.end); + const copied = new Set(); + const copyQueue: string[] = []; + for (const r of references(body)) { + const name = r.text; + if (chunks.has(name)) continue; // a call edit already respells it + if (name === exportsParam || name === moduleParam) { + if (name === moduleParam) { + const p = r.parent; + if (!ts.isPropertyAccessExpression(p) || p.expression !== r || p.name.text !== "exports") + return why("uses its module object beyond module.exports"); + } + const global = name === exportsParam ? "exports" : "module"; + if (name !== global) { + const p = r.parent; + edits.push( + ts.isShorthandPropertyAssignment(p) + ? { start: tokenStartOfNode(r), end: r.end, text: `${name}: ${global}` } + : { start: tokenStartOfNode(r), end: r.end, text: global }, + ); + } + continue; + } + // The factory function itself binds the parameters and `var`s. + if (scopes.boundWithin(r, body.parent ?? body)) continue; + if (name === "exports" || name === "module") + return why(`reads the bundle's own '${name}' binding`); + if (name === "__filename") return why("reads __filename, which names the bundle file"); + if (top.has(name)) { + if (!copied.has(name)) { + copied.add(name); + copyQueue.push(name); + } + } + } + + // The pure helpers BODY reads, closed over their own top-level reads. + const header: string[] = []; + for (let i = 0; i < copyQueue.length; i++) { + const name = copyQueue[i]!; + const t = top.get(name)!; + const node = t.node; + const pure = + stable(name) && + (ts.isFunctionDeclaration(node) || + (ts.isVariableDeclaration(node) && copyableInitializer(node.initializer))); + if (!pure || chunks.has(name)) + return why(`reads the bundle's top-level binding '${name}', which it cannot carry along`); + // Each chunk gets its own copy, so a function's identity and its + // properties must stay unobservable: the bundle only ever calls it. + if (isFunctionValued(node) && (!calledOnly(name) || !selfCalledOnly(node, scopes))) + return why( + `uses the bundle's top-level function '${name}' as a value, which its chunks cannot share`, + ); + for (const r of references(node)) { + if (scopes.boundWithin(r, node) || !top.has(r.text) || copied.has(r.text)) continue; + copied.add(r.text); + copyQueue.push(r.text); + } + } + // Declaration order of the bundle: a helper reads only earlier ones. + const ordered = [...copied].sort( + (a, b) => tokenStartOfNode(top.get(a)!.node) - tokenStartOfNode(top.get(b)!.node), + ); + for (const name of ordered) { + const node = top.get(name)!.node; + header.push( + ts.isFunctionDeclaration(node) ? getTextOfNode(node) : `var ${getTextOfNode(node)};`, + ); + } + + const bodyStrict = strictPrologue(body.statements.nodes); + const text = applyEdits(sf.text, tokenStartOfNode(body) + 1, body.end - 1, edits); + const prologue = bundleStrict || bodyStrict ? '"use strict";\n' : ""; + return prologue + (header.length > 0 ? header.join("\n") + "\n" : "") + text + "\n"; +} diff --git a/packages/compiler/src/frontend/npm-static-bundled-cjs.ts b/packages/compiler/src/frontend/npm-static-bundled-cjs.ts index 89cd0fda1..be43284a6 100644 --- a/packages/compiler/src/frontend/npm-static-bundled-cjs.ts +++ b/packages/compiler/src/frontend/npm-static-bundled-cjs.ts @@ -1,5 +1,6 @@ import * as ts from "./ts7/syntax.js"; import { helperTokens } from "./helper-tokens.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; // Bun's cached node-mode interop helper. Match the complete token stream, // including its dependencies: helper names alone are not a semantic contract. @@ -117,14 +118,14 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) } for (const [name, shape] of helperShapes) { const actual = declarations.get(name); - if (!actual || helperTokens(actual.stmt.getText(sf)) !== shape) return null; + if (!actual || helperTokens(getTextOfNode(actual.stmt)) !== shape) return null; } // These initializers must execute before any factory or namespace. The // original bundle would throw if a var helper were still undefined. let helperEnd = 0; for (const name of helperShapes.keys()) { const stmt = declarations.get(name)!.stmt; - if (stmt.getStart(sf) < helperEnd) return null; + if (tokenStartOfNode(stmt) < helperEnd) return null; helperEnd = stmt.end; } const edits: Edit[] = []; @@ -140,7 +141,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) const call = decl.initializer; if (!ts.isIdentifier(call.expression) || call.expression.text !== "__commonJS") continue; if ( - stmt.getStart(sf) < helperEnd || + tokenStartOfNode(stmt) < helperEnd || (ts.canHaveModifiers(stmt) && ts.getModifiers(stmt)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) ) @@ -193,7 +194,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) if (unsafe) return null; // The comma expression prevents inferred naming of an anonymous export. edits.push( - { start: call.getStart(sf), end: value.getStart(sf), text: "(0," }, + { start: tokenStartOfNode(call), end: tokenStartOfNode(value), text: "(0," }, { start: value.end, end: call.end, text: ")" }, ); factories.set(name, stmt); @@ -212,7 +213,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) const call = decl.initializer; if (!ts.isIdentifier(call.expression) || call.expression.text !== "__toESM") continue; if ( - stmt.getStart(sf) < helperEnd || + tokenStartOfNode(stmt) < helperEnd || (ts.canHaveModifiers(stmt) && ts.getModifiers(stmt)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)) ) @@ -230,9 +231,9 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) ) return null; const factory = factories.get(requireCall.expression.text); - if (!factory || factory.end > stmt.getStart(sf)) return null; + if (!factory || factory.end > tokenStartOfNode(stmt)) return null; edits.push({ - start: call.getStart(sf), + start: tokenStartOfNode(call), end: call.end, text: `{default:${requireCall.expression.text}}`, }); @@ -286,7 +287,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) // Remove only helpers that became dead. Other bundle features may share // __defProp, so a recognized declaration alone does not justify erasure. const dead = (n: ts.Node): boolean => - edits.some((e) => n.getStart(sf) >= e.start && n.end <= e.end); + edits.some((e) => tokenStartOfNode(n) >= e.start && n.end <= e.end); const removed = new Set(); for (let changed = true; changed;) { changed = false; @@ -304,7 +305,7 @@ export function rewriteBundledFunctionImports(source: string, sf: ts.SourceFile) live = true; }); if (!live) { - edits.push({ start: stmt.getStart(sf), end: stmt.end, text: "" }); + edits.push({ start: tokenStartOfNode(stmt), end: stmt.end, text: "" }); removed.add(name); changed = true; } diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts new file mode 100644 index 000000000..381538087 --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.test.ts @@ -0,0 +1,230 @@ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, expect, test } from "vitest"; +import { nodeFrontendServices } from "./services-node.js"; + +const helpers = `var __getOwnPropNames = Object.getOwnPropertyNames; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __create = Object.create; +var __getProtoOf = Object.getPrototypeOf; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +`; + +const dirs: string[] = []; +afterAll(() => { + for (const dir of dirs) rmSync(dir, { recursive: true, force: true }); +}); + +function lower(source: string, file = "/pkg/dist/index.js") { + return nodeFrontendServices().lowerEsmInits(source, file); +} + +function run(file: string) { + const result = spawnSync(process.execPath, [file], { encoding: "utf8" }); + return { status: result.status, stdout: result.stdout, stderr: result.stderr }; +} + +function expectSameBehavior(source: string): string { + const dir = mkdtempSync(join(tmpdir(), "scriptc-esm-inits-")); + dirs.push(dir); + const original = join(dir, "original.js"); + const lowered = join(dir, "index.js"); + writeFileSync(original, source); + const result = lower(source, lowered); + expect(typeof result).toBe("string"); + writeFileSync(lowered, result as string); + const oracle = run(original); + expect(oracle.status).toBe(0); + expect(run(lowered)).toEqual(oracle); + return result as string; +} + +const lazyModule = ` +function helper(x) { COUNT++; return "[" + x + "]"; } +var COUNT; +var init_helper = __esm({ + "src/helper.ts"() { + console.log("helper init"); + COUNT = 0; + } +}); +var lazy_exports = {}; +__export(lazy_exports, { + VALUE: () => VALUE, + value: () => value +}); +function value() { + return VALUE + ":" + COUNT + ":" + import_os.default.EOL.length; +} +var path, import_os, VALUE; +var init_lazy = __esm({ + "src/lazy.ts"() { + path = __toESM(require("path")); + import_os = __toESM(require("os")); + init_helper(); + console.log("lazy init"); + VALUE = helper(path.basename("/a/b.txt")); + } +}); +`; + +test("initializers run once, at their first call, and namespaces read live bindings", () => { + const lowered = expectSameBehavior(`"use strict"; +${helpers}${lazyModule} +console.log("start"); +function load() { + const lazy = (init_lazy(), __toCommonJS(lazy_exports)); + return lazy.value() + " " + lazy.VALUE; +} +console.log(load()); +COUNT = 5; +console.log(load(), (init_lazy(), __toCommonJS(lazy_exports)).value()); +`); + expect(lowered).not.toContain("__esm({"); + expect(lowered).not.toContain("import_os"); + expect(lowered).toContain('(require("os")).EOL'); +}); + +test("calls of exported functions that never read their own `this` still lower", () => { + const lowered = expectSameBehavior(`${helpers}var t_exports = {}; +__export(t_exports, { plain: () => plain, inner: () => inner }); +function plain(x) { return "plain " + x; } +function inner() { + function own() { return typeof this; } + return "inner " + own.call(1); +} +var init_t = __esm({ "t.ts"() {} }); +const t = (init_t(), __toCommonJS(t_exports)); +console.log(t.plain(1), (t.plain)(2), t.inner()); +`); + expect(lowered).not.toContain("__toCommonJS(t_exports)"); +}); + +test.each([ + ["a direct call", `NS.who()`], + ["a parenthesized callee", `(NS.who)()`], + ["a tagged template", "NS.who`x`"], + ["a call through a local", `(() => { const m = NS; return m.who(); })()`], + ["an arrow inside the function", `NS.arrow()()`], +])("%s that passes the namespace as `this` keeps the package refused", (_, use) => { + const result = lower(`${helpers}var w_exports = {}; +__export(w_exports, { who: () => who, arrow: () => arrow }); +function who() { return this.__esModule; } +function arrow() { return () => this; } +var init_w = __esm({ "w.ts"() {} }); +console.log(${use.replaceAll("NS", "(init_w(), __toCommonJS(w_exports))")}); +`); + expect(result).toEqual({ + degrade: expect.stringContaining( + "calls only of exported functions that do not read 'this'", + ) as string, + }); +}); + +test("an export that reads `this` in a parameter's default value keeps the package refused", () => { + const result = lower(`${helpers}var p_exports = {}; +__export(p_exports, { who: () => who }); +function who(value = this.__esModule) { return value; } +var init_p = __esm({ "p.ts"() {} }); +console.log((init_p(), __toCommonJS(p_exports)).who()); +`); + expect(result).toEqual({ + degrade: expect.stringContaining( + "calls only of exported functions that do not read 'this'", + ) as string, + }); +}); + +test("minified initializers sharing a var statement lower the same way", () => { + expectSameBehavior( + helpers + + `var x_exports={};__export(x_exports,{v:()=>v});var v,import_path,init_x=__esm({"x.ts"(){"use strict";import_path=__toESM(require("path")),v=import_path.default.sep}});` + + `console.log((init_x(),__toCommonJS(x_exports)).v);`, + ); +}); + +test("memoized lazy loaders sharing a var statement lower together", () => { + const lowered = expectSameBehavior(`${helpers}var a_exports = {}; +__export(a_exports, { v: () => v }); +var v, init_a = __esm({ "a.ts"() { console.log("a init"); v = 1; } }); +var b_exports = {}; +__export(b_exports, { default: () => w }); +var w, init_b = __esm({ "b.ts"() { console.log("b init"); w = 2; } }); +var nsA, loadA = () => nsA ??= (init_a(), __toCommonJS(a_exports)), nsB, loadB = () => nsB ??= __toESM((init_b(), __toCommonJS(b_exports))); +console.log(loadA().v, loadB().default, loadA().v); +`); + expect(lowered).not.toContain("loadA"); + expect(lowered).not.toContain("loadB"); +}); + +test("calls through a memoized lazy loader keep the receiver rule", () => { + const source = (use: string): string => `${helpers}var w_exports = {}; +__export(w_exports, { who: () => who, plain: () => plain }); +function who() { return this.__esModule; } +function plain(x) { return "plain " + x; } +var init_w = __esm({ "w.ts"() {} }); +var ns, load = () => ns ??= (init_w(), __toCommonJS(w_exports)); +console.log(${use}); +`; + expect(lower(source("load().who()"))).toEqual({ + degrade: expect.stringContaining( + "calls only of exported functions that do not read 'this'", + ) as string, + }); + const lowered = expectSameBehavior(source("load().plain(1), (load().plain)(2)")); + expect(lowered).not.toContain("load()"); +}); + +test.each([ + ["is asynchronous", `var init_a = __esm({ async "a.ts"() { await 0; } });\ninit_a();`], + [ + "is used other than as a statement call", + `var init_a = __esm({ "a.ts"() {} });\nvar r = init_a();`, + ], + [ + "reads its factory's receiver", + `var init_a = __esm({ "a.ts"() { console.log(this); } });\ninit_a();`, + ], + [ + "memoized lazy loader 'load' returns the namespace of a bundled ES module as a value", + `var a_exports = {};\n__export(a_exports, { v: () => v });\nvar v, init_a = __esm({ "a.ts"() { v = 1; } });\nvar ns, load = () => ns ??= __toESM((init_a(), __toCommonJS(a_exports)));\nload();`, + ], + [ + "reads the namespace of a bundled ES module ('a_exports')", + `var a_exports = {};\n__export(a_exports, { v: () => v });\nvar v, init_a = __esm({ "a.ts"() { v = 1; } });\nconsole.log(Object.keys((init_a(), __toCommonJS(a_exports))));`, + ], +])("an initializer that %s keeps the package refused", (reason, code) => { + expect(lower(`${helpers}${code}\n`)).toEqual({ + degrade: expect.stringContaining(reason) as string, + }); +}); + +test("files without __esm initializers and ES-module bundles are left alone", () => { + expect(lower("exports.x = 1;\n")).toBeNull(); + expect( + lower(`${helpers}var init_a = __esm({ "a.ts"() {} });\ninit_a();\nexport {};\n`), + ).toBeNull(); +}); diff --git a/packages/compiler/src/frontend/npm-static-bundled-esm.ts b/packages/compiler/src/frontend/npm-static-bundled-esm.ts new file mode 100644 index 000000000..ac572000d --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-bundled-esm.ts @@ -0,0 +1,826 @@ +/* Lazily initialized ES modules in a CommonJS bundle. esbuild scope-hoists + * every ES module it bundles into the bundle's own scope; one that some + * code require()s instead of importing runs from a lazy initializer, + * + * var COUNT, import_fs; + * var init_x = __esm({ "src/x.ts"() { import_fs = __toESM(require("fs")); COUNT = 0; } }); + * + * and the requiring site reads its namespace as + * `(init_x(), __toCommonJS(x_exports))`. `init_x()` runs the body once (a + * call during the body answers immediately). The pass below keeps that + * shape in the bundle's scope and respells only what has no static + * representation: + * + * - the initializer becomes a run-once closure over the same body; + * - an interop binding the body assigns once from require() (`import_fs` + * above) becomes that require at the assignment's position, and its + * member reads become member reads of the required module, exactly + * like the top-level interop erasure (npm-static-rewrite-syntax.ts); + * - a namespace read only through member reads becomes an object + * literal of getters over the module's live bindings (what + * __toCommonJS copies, minus the non-enumerable __esModule mark no + * member read can reach). + * + * The respelled interop reads resolve the required module even if they + * ran before the initializer, where Node would read a member of + * undefined and throw; esbuild only reaches them through the module's + * own code, which runs after its initializer. Shapes outside the + * recognized forms answer a degrade reason. */ +import * as ts from "./ts7/syntax.js"; +import { bareRequireSpecOf } from "./cjs-syntax.js"; +import { helperTokens } from "./helper-tokens.js"; +import { + applyEdits, + countDeclarations, + declaredNames, + factoryFrameUse, + helperBase, + isWritten, + references, + unparen, + type Edit, +} from "./npm-static-bundled-chunks.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; + +export interface EsmInitHost { + requireTargetEsModuleStamped(fromFile: string, specifier: string): boolean; +} + +const ESM_SHAPE_DEGRADE = + "its shipped JS initializes bundled ES modules through the __esm helper in a shape the " + + "recognizer cannot verify"; + +/* The recognized __esm forms over its parameter names (FN, RES) and the + * own-key reader (KEYS): keyed `(FN, RES) => function [name]() { return + * ; }` with a one-method object argument, and arrow + * `(FN, RES) => () => ()` with a function argument. */ +const KEYED_RETURN = "FN && (RES = (0, FN[KEYS(FN)[0]])(FN = 0)), RES"; +const ARROW_BODY = "(FN && (RES = FN(FN = 0)), RES)"; + +function template(text: string, fn: string, res: string, keys: string): string | null { + return helperTokens( + text + .replace(/\bFN\b/g, fn) + .replace(/\bRES\b/g, res) + .replace(/\bKEYS\b/g, keys), + ); +} + +function esmHelperForm( + init: ts.Expression, + sf: ts.SourceFile, + ownNames: (name: string) => boolean, +): "keyed" | "arrow" | null { + const fn = unparen(init); + if (!ts.isArrowFunction(fn) || fn.parameters.nodes.length !== 2 || ts.isBlock(fn.body)) + return null; + const [p0, p1] = fn.parameters.nodes; + if ( + !p0 || + !p1 || + !ts.isIdentifier(p0.name) || + !ts.isIdentifier(p1.name) || + p0.initializer || + p1.initializer || + p0.name.text === p1.name.text + ) + return null; + const [f, r] = [p0.name.text, p1.name.text]; + const body = unparen(fn.body); + if (ts.isFunctionExpression(body)) { + if (body.parameters.nodes.length !== 0 || body.asteriskToken || body.modifiers?.nodes.length) + return null; + const stmts = body.body.statements.nodes; + const ret = stmts[0]; + if (stmts.length !== 1 || !ret || !ts.isReturnStatement(ret) || !ret.expression) return null; + const text = getTextOfNode(ret.expression); + const keys = /\[\s*([A-Za-z_$][\w$]*(?:\.keys)?)\s*\(/.exec(text)?.[1]; + if (keys === undefined || (keys !== "Object.keys" && !ownNames(keys))) return null; + return helperTokens(text) === template(KEYED_RETURN, f, r, keys) ? "keyed" : null; + } + if (ts.isArrowFunction(body) && body.parameters.nodes.length === 0 && !ts.isBlock(body.body)) { + const actual = helperTokens(getTextOfNode(body.body)); + return actual !== null && actual === template(ARROW_BODY, f, r, "") ? "arrow" : null; + } + return null; +} + +/** Pads the given declarators out of their statements (separating commas + * included; a statement whose declarators all go pads whole). */ +function removeDeclarators(sf: ts.SourceFile, dead: ReadonlySet): Edit[] { + const edits: Edit[] = []; + const stmts = new Set(); + for (const d of dead) { + const stmt = d.parent?.parent; + if (stmt !== undefined && ts.isVariableStatement(stmt)) stmts.add(stmt); + } + for (const stmt of stmts) { + const decls = stmt.declarationList.declarations.nodes; + const deadAt = decls.map((d) => dead.has(d)); + if (deadAt.every((x) => x)) { + edits.push({ start: tokenStartOfNode(stmt), end: stmt.end, text: "" }); + continue; + } + const lastLive = deadAt.lastIndexOf(false); + for (let i = 0; i < decls.length; i++) { + if (!deadAt[i]) continue; + edits.push( + i < lastLive + ? { start: tokenStartOfNode(decls[i]!), end: tokenStartOfNode(decls[i + 1]!), text: "" } + : { start: decls[lastLive]!.end, end: decls[i]!.end, text: "" }, + ); + } + } + return edits; +} + +/** The elements of an expression statement's comma chain, in order. */ +function commaElements(e: ts.Expression): ts.Expression[] { + const out: ts.Expression[] = []; + const visit = (x: ts.Expression): void => { + if (ts.isBinaryExpression(x) && x.operatorToken.kind === ts.SyntaxKind.CommaToken) { + visit(x.left); + visit(x.right); + } else out.push(x); + }; + visit(e); + return out; +} + +/** True when a call's result is discarded: an expression statement or a + * non-final comma operand (parentheses included). */ +function resultUnused(call: ts.Expression): boolean { + let child: ts.Node = call; + let p = call.parent; + while (p !== undefined && ts.isParenthesizedExpression(p)) { + child = p; + p = p.parent; + } + if (p === undefined) return false; + if (ts.isExpressionStatement(p)) return true; + if (ts.isBinaryExpression(p) && p.operatorToken.kind === ts.SyntaxKind.CommaToken) { + if (p.left === child) return true; + return resultUnused(p); + } + return false; +} + +/** True when a member access is a plain read (not written, deleted or + * updated). */ +function plainRead(access: ts.Expression): boolean { + const p = access.parent; + if (p === undefined) return true; + if (ts.isDeleteExpression(p)) return false; + if ( + ts.isBinaryExpression(p) && + p.left === access && + p.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + p.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) + return false; + if ( + (ts.isPrefixUnaryExpression(p) || ts.isPostfixUnaryExpression(p)) && + (p.operator === ts.SyntaxKind.PlusPlusToken || p.operator === ts.SyntaxKind.MinusMinusToken) + ) + return false; + return true; +} + +/** True when a member read is the callee of a call or a tag, which receives + * the object it was read from as `this`. */ +function calledWithReceiver(access: ts.Expression): boolean { + let callee: ts.Node = access; + while (callee.parent !== undefined && ts.isParenthesizedExpression(callee.parent)) + callee = callee.parent; + const p = callee.parent; + return ( + p !== undefined && + ((ts.isCallExpression(p) && p.expression === callee) || + (ts.isTaggedTemplateExpression(p) && p.tag === callee)) + ); +} + +/** True when a function reads its own `this`: in its body or a parameter's + * default value, or in an arrow function inside them. */ +function readsThis(fn: ts.FunctionDeclaration): boolean { + let found = false; + if (fn.body === undefined) return false; + const visit = (n: ts.Node): "stop" | "skip" | undefined => { + if (found) return "stop"; + if ( + ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isMethodDeclaration(n) || + ts.isGetAccessorDeclaration(n) || + ts.isSetAccessorDeclaration(n) || + ts.isConstructorDeclaration(n) || + ts.isClassDeclaration(n) || + ts.isClassExpression(n) + ) + return "skip"; + if (n.kind === ts.SyntaxKind.ThisKeyword) found = true; + return undefined; + }; + for (const parameter of fn.parameters.nodes) ts.walkPreorder(parameter, visit); + ts.walkPreorder(fn.body, visit); + return found; +} + +/** The `__export(T, { name: () => value, … })` getter table of T, or null. */ +function exportTableOf( + call: ts.CallExpression, +): { target: string; entries: [string, ts.Expression][] } | null { + if (!ts.isIdentifier(call.expression) || helperBase(call.expression.text) !== "__export") + return null; + const [target, table] = call.arguments.nodes; + if ( + call.arguments.nodes.length !== 2 || + !target || + !table || + !ts.isIdentifier(target) || + !ts.isObjectLiteralExpression(table) + ) + return null; + const entries: [string, ts.Expression][] = []; + for (const prop of table.properties.nodes) { + if (!ts.isPropertyAssignment(prop)) return null; + if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) return null; + const getter = unparen(prop.initializer); + if (!ts.isArrowFunction(getter) || getter.parameters.nodes.length !== 0) return null; + if (ts.isBlock(getter.body)) return null; + entries.push([prop.name.text, getter.body]); + } + return { target: target.text, entries }; +} + +/** Lowers a CommonJS bundle's __esm initializers (see the header). Null: + * nothing to lower. */ +export function lowerBundledEsmInits( + sf: ts.SourceFile, + filePath: string, + host: EsmInitHost, +): string | { degrade: string } | null { + const source = sf.text; + if (!source.includes("__esm")) return null; + if (!filePath.endsWith(".cjs") && ts.isExternalModule(sf)) return null; + + interface Top { + stmt: ts.Statement; + node: ts.VariableDeclaration | ts.FunctionDeclaration; + } + const top = new Map(); + const duplicate = new Set(); + for (const stmt of sf.statements.nodes) { + const add = (name: string, node: Top["node"]): void => { + if (top.has(name)) duplicate.add(name); + else top.set(name, { stmt, node }); + }; + if (ts.isFunctionDeclaration(stmt) && stmt.name !== undefined) add(stmt.name.text, stmt); + else if (ts.isVariableStatement(stmt)) { + for (const d of stmt.declarationList.declarations.nodes) { + if (ts.isIdentifier(d.name)) add(d.name.text, d); + } + } + } + const helpers = [...top.keys()].filter((n) => helperBase(n) === "__esm"); + if (helpers.length === 0) return null; + + const allRefs = references(sf); + const writes = new Map(); + for (const r of allRefs) if (isWritten(r)) writes.set(r.text, (writes.get(r.text) ?? 0) + 1); + const declaredAnywhere = declaredNames(sf); + if (declaredAnywhere.has("Object") || writes.has("Object")) return { degrade: ESM_SHAPE_DEGRADE }; + const stable = (name: string): boolean => !duplicate.has(name) && !writes.has(name); + const ownNames = (name: string): boolean => { + const t = top.get(name); + const init = + t !== undefined && ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + return ( + init !== undefined && + stable(name) && + helperTokens(getTextOfNode(init)) === helperTokens("Object.getOwnPropertyNames") + ); + }; + const forms = new Map(); + for (const name of helpers) { + const t = top.get(name)!; + const init = ts.isVariableDeclaration(t.node) ? t.node.initializer : undefined; + const form = init !== undefined && stable(name) ? esmHelperForm(init, sf, ownNames) : null; + if (form === null) return { degrade: ESM_SHAPE_DEGRADE }; + forms.set(name, form); + } + + // The initializers: `NAME = HELPER(factory)` declarators. + interface Init { + name: string; + call: ts.CallExpression; + body: ts.Block; + } + const inits = new Map(); + for (const [name, t] of top) { + if (!ts.isVariableDeclaration(t.node) || t.node.initializer === undefined) continue; + const call = unparen(t.node.initializer); + if (!ts.isCallExpression(call) || !ts.isIdentifier(call.expression)) continue; + const form = forms.get(call.expression.text); + if (form === undefined) continue; + if (call.arguments.nodes.length !== 1 || !stable(name)) return { degrade: ESM_SHAPE_DEGRADE }; + let factory: ts.Node = call.arguments.nodes[0]!; + if (form === "keyed") { + if (!ts.isObjectLiteralExpression(factory) || factory.properties.nodes.length !== 1) + return { degrade: ESM_SHAPE_DEGRADE }; + factory = factory.properties.nodes[0]!; + if (!ts.isMethodDeclaration(factory) || ts.isComputedPropertyName(factory.name)) + return { degrade: ESM_SHAPE_DEGRADE }; + } else { + factory = unparen(factory as ts.Expression); + if (!ts.isArrowFunction(factory) && !ts.isFunctionExpression(factory)) + return { degrade: ESM_SHAPE_DEGRADE }; + } + const fn = factory as ts.MethodDeclaration | ts.ArrowFunction | ts.FunctionExpression; + if (fn.modifiers?.nodes.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword)) + return { + degrade: `its bundled ES module initializer '${name}' is asynchronous (top-level await), which the lazy initialization rewrite does not cover`, + }; + if ( + fn.body === undefined || + !ts.isBlock(fn.body) || + fn.asteriskToken !== undefined || + (fn.modifiers?.nodes.length ?? 0) > 0 || + fn.parameters.nodes.length > 0 + ) + return { degrade: ESM_SHAPE_DEGRADE }; + const frame = factoryFrameUse(fn.body, false); + if (frame !== null) return { degrade: `its bundled ES module initializer '${name}' ${frame}` }; + inits.set(name, { name, call, body: fn.body }); + } + if (inits.size === 0) return null; + + const edits: Edit[] = []; + // Every initializer reference is a direct call whose result is unused. + for (const r of allRefs) { + if (!inits.has(r.text)) continue; + const call = r.parent; + if ( + call === undefined || + !ts.isCallExpression(call) || + call.expression !== r || + call.arguments.nodes.length !== 0 || + !resultUnused(call) + ) + return { + degrade: `its bundled ES module initializer '${r.text}' is used other than as a statement call`, + }; + } + for (const name of inits.keys()) { + if (countDeclarations(sf, name) !== 1) return { degrade: ESM_SHAPE_DEGRADE }; + } + + /* Interop bindings an initializer assigns once from require(). */ + const refsByName = new Map(); + for (const r of allRefs) { + const list = refsByName.get(r.text); + if (list === undefined) refsByName.set(r.text, [r]); + else list.push(r); + } + const deadDeclarators = new Set(); + for (const init of inits.values()) { + for (const stmt of init.body.statements.nodes) { + if (!ts.isExpressionStatement(stmt)) continue; + for (const el of commaElements(stmt.expression)) { + if ( + !ts.isBinaryExpression(el) || + el.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !ts.isIdentifier(el.left) + ) + continue; + const name = el.left.text; + const decl = top.get(name)?.node; + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + decl.initializer !== undefined || + duplicate.has(name) || + writes.get(name) !== 1 || + countDeclarations(sf, name) !== 1 + ) + continue; + let rhs = unparen(el.right); + let isNodeMode = false; + let interop = false; + if ( + ts.isCallExpression(rhs) && + ts.isIdentifier(rhs.expression) && + helperBase(rhs.expression.text) === "__toESM" + ) { + const [arg, mode] = rhs.arguments.nodes; + if (!arg || rhs.arguments.nodes.length > 2) continue; + if (mode !== undefined) { + if (ts.isNumericLiteral(mode)) isNodeMode = Number(mode.text) !== 0; + else if (mode.kind === ts.SyntaxKind.TrueKeyword) isNodeMode = true; + else if (mode.kind !== ts.SyntaxKind.FalseKeyword) continue; + } + rhs = unparen(arg); + interop = true; + } + const spec = bareRequireSpecOf(rhs); + if (spec === null) continue; + const req = getTextOfNode(rhs); + const defaultIsModule = + interop && (isNodeMode || !host.requireTargetEsModuleStamped(filePath, spec)); + // Every other reference must be a plain member read. + const uses: Edit[] = []; + let ok = true; + for (const r of refsByName.get(name) ?? []) { + if (r === el.left) continue; + const access = r.parent; + if ( + access === undefined || + !ts.isPropertyAccessExpression(access) || + access.expression !== r || + !plainRead(access) + ) { + ok = false; + break; + } + if (defaultIsModule && access.name.text === "default") + uses.push({ start: tokenStartOfNode(access), end: access.end, text: `(${req})` }); + else uses.push({ start: tokenStartOfNode(r), end: r.end, text: req }); + } + if (!ok) continue; + edits.push({ start: tokenStartOfNode(el), end: el.end, text: req }, ...uses); + deadDeclarators.add(decl); + } + } + } + + /* Namespaces of lazily initialized modules read through members. */ + let mainTarget: string | null = null; + for (const stmt of sf.statements.nodes) { + if (!ts.isExpressionStatement(stmt) || !ts.isBinaryExpression(stmt.expression)) continue; + const e = stmt.expression; + const rhs = e.right; + if ( + e.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(e.left) && + ts.isIdentifier(e.left.expression) && + e.left.expression.text === "module" && + e.left.name.text === "exports" && + ts.isCallExpression(rhs) && + ts.isIdentifier(rhs.expression) && + helperBase(rhs.expression.text) === "__toCommonJS" && + rhs.arguments.nodes.length === 1 && + ts.isIdentifier(rhs.arguments.nodes[0]!) + ) + mainTarget = (rhs.arguments.nodes[0] as ts.Identifier).text; + } + const tables = new Map(); + for (const stmt of sf.statements.nodes) { + if (!ts.isExpressionStatement(stmt) || !ts.isCallExpression(stmt.expression)) continue; + const table = exportTableOf(stmt.expression); + if (table !== null && table.target !== mainTarget) { + if (tables.has(table.target)) return { degrade: ESM_SHAPE_DEGRADE }; + tables.set(table.target, { stmt, entries: table.entries }); + } + } + const lowered = new Set(); + /** Some binding between the use site and the top level rebinds a name + * the table's getters read. */ + const shadowed = (site: ts.Node, entries: readonly [string, ts.Expression][]): boolean => { + const read = new Set(); + for (const [, value] of entries) for (const x of references(value)) read.add(x.text); + for (let a = site.parent; a !== undefined && !ts.isSourceFile(a); a = a.parent) { + if ( + ts.isFunctionLike(a) || + ts.isBlock(a) || + ts.isClassDeclaration(a) || + ts.isClassExpression(a) + ) { + for (const n of declaredNames(a)) if (read.has(n)) return true; + } + } + return false; + }; + const getterText = (value: ts.Expression): string => + applyEdits(source, tokenStartOfNode(value), value.end, edits); + /** A call through the namespace (`ns.fn()`, `L().fn()`) passes the object + * the read came from as `this`; the lowered object differs from Node's + * module object (`__esModule`, property attributes), so it must stay + * unobservable: the callee is a top-level function that never reads + * `this`. */ + const receiverHidden = (values: ReadonlyMap, name: string): boolean => { + const value = values.get(name); + if (value === undefined || !ts.isIdentifier(value) || !stable(value.text)) return false; + const decl = top.get(value.text)?.node; + return decl !== undefined && ts.isFunctionDeclaration(decl) && !readsThis(decl); + }; + + /* Memoized lazy loaders of those namespaces, `L = () => M ??= NS` (a + * bundle plugin's lazy re-exports): every `L().name` becomes the + * initializer call followed by the export's live read, so the first + * access still initializes the module, once, and later accesses read + * the binding. */ + const loaderSpans: { start: number; end: number }[] = []; + for (const [name, t] of top) { + const d = t.node; + if (!ts.isVariableDeclaration(d) || d.initializer === undefined) continue; + const fn = unparen(d.initializer); + if (!ts.isArrowFunction(fn) || fn.parameters.nodes.length !== 0 || ts.isBlock(fn.body)) + continue; + const memoize = unparen(fn.body); + if ( + !ts.isBinaryExpression(memoize) || + (memoize.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionEqualsToken && + memoize.operatorToken.kind !== ts.SyntaxKind.BarBarEqualsToken) || + !ts.isIdentifier(memoize.left) + ) + continue; + let w = unparen(memoize.right); + let interop = false; + let nodeMode = false; + if ( + ts.isCallExpression(w) && + ts.isIdentifier(w.expression) && + helperBase(w.expression.text) === "__toESM" + ) { + const [arg, mode] = w.arguments.nodes; + if (!arg || w.arguments.nodes.length > 2) continue; + if (mode !== undefined) { + if (ts.isNumericLiteral(mode)) nodeMode = Number(mode.text) !== 0; + else if (mode.kind === ts.SyntaxKind.TrueKeyword) nodeMode = true; + else if (mode.kind !== ts.SyntaxKind.FalseKeyword) continue; + } + interop = true; + w = unparen(arg); + } + let initCall: ts.CallExpression | null = null; + if (ts.isBinaryExpression(w) && w.operatorToken.kind === ts.SyntaxKind.CommaToken) { + const left = unparen(w.left); + if ( + !ts.isCallExpression(left) || + !ts.isIdentifier(left.expression) || + !inits.has(left.expression.text) || + left.arguments.nodes.length !== 0 + ) + continue; + initCall = left; + w = unparen(w.right); + } + if ( + !ts.isCallExpression(w) || + !ts.isIdentifier(w.expression) || + helperBase(w.expression.text) !== "__toCommonJS" || + w.arguments.nodes.length !== 1 || + !ts.isIdentifier(w.arguments.nodes[0]!) + ) + continue; + const target = (w.arguments.nodes[0] as ts.Identifier).text; + const table = tables.get(target); + if (table === undefined) continue; + const loaderWhy = { + degrade: `its memoized lazy loader '${name}' returns the namespace of a bundled ES module as a value, a module object the static rewrite cannot represent (only member reads of its result compile, and calls only of exported functions that do not read 'this')`, + }; + const memo = memoize.left.text; + const memoDecl = top.get(memo)?.node; + if ( + memoDecl === undefined || + !ts.isVariableDeclaration(memoDecl) || + memoDecl.initializer !== undefined || + writes.get(memo) !== 1 || + countDeclarations(sf, memo) !== 1 || + (refsByName.get(memo) ?? []).some((x) => x !== memoize.left) || + writes.has(name) || + countDeclarations(sf, name) !== 1 + ) + return loaderWhy; + const entries = new Map(table.entries); + for (const ref of refsByName.get(name) ?? []) { + const call = ref.parent; + if ( + call === undefined || + !ts.isCallExpression(call) || + call.expression !== ref || + call.arguments.nodes.length !== 0 + ) + return loaderWhy; + let access: ts.Node | undefined = call.parent; + if ( + access === undefined || + !ts.isPropertyAccessExpression(access) || + access.expression !== call || + !plainRead(access) + ) + return loaderWhy; + // Node mode binds `default` to the namespace object itself. + if (interop && nodeMode && access.name.text === "default") { + const outer: ts.Node | undefined = access.parent; + if ( + outer === undefined || + !ts.isPropertyAccessExpression(outer) || + outer.expression !== access || + !plainRead(outer) + ) + return loaderWhy; + access = outer; + } + const member = (access as ts.PropertyAccessExpression).name.text; + const value = entries.get(member); + if (value === undefined || shadowed(access, table.entries)) return loaderWhy; + // The rewrite reads the export without the namespace object, so a + // call through it (`L().fn()`) loses its receiver. + if (calledWithReceiver(access as ts.Expression) && !receiverHidden(entries, member)) + return loaderWhy; + const init = initCall === null ? "" : `${getTextOfNode(initCall)}, `; + edits.push({ + start: tokenStartOfNode(access), + end: access.end, + text: `(${init}${getterText(value)})`, + }); + } + deadDeclarators.add(d); + deadDeclarators.add(memoDecl); + loaderSpans.push({ start: tokenStartOfNode(d), end: d.end }); + lowered.add(target); + } + + for (const r of allRefs) { + const table = tables.get(r.text); + if (table === undefined) continue; + if (loaderSpans.some((span) => tokenStartOfNode(r) >= span.start && r.end <= span.end)) + continue; + const p = r.parent; + // The table's own declaration and __export call are handled below. + if (p !== undefined && ts.isVariableDeclaration(p) && p.name === r) continue; + if ( + p !== undefined && + ts.isCallExpression(p) && + p.arguments.nodes[0] === r && + ts.isExpressionStatement(p.parent) && + p.parent === table.stmt + ) + continue; + const why = `reads the namespace of a bundled ES module ('${r.text}') in a way the static rewrite cannot follow (only member reads of its exports compile, and calls only of exported functions that do not read 'this')`; + if ( + p === undefined || + !ts.isCallExpression(p) || + !ts.isIdentifier(p.expression) || + helperBase(p.expression.text) !== "__toCommonJS" || + p.arguments.nodes.length !== 1 + ) + return { degrade: `its shipped JS ${why}` }; + // The namespace expression: `(init_x(), __toCommonJS(T))` or the call. + let ns: ts.Expression = p; + for (;;) { + const up = ns.parent; + if (up !== undefined && ts.isParenthesizedExpression(up)) ns = up; + else if ( + up !== undefined && + ts.isBinaryExpression(up) && + up.operatorToken.kind === ts.SyntaxKind.CommaToken && + up.right === ns + ) + ns = up; + else break; + } + const values = new Map(table.entries); + const memberRead = (access: ts.Node | undefined, of: ts.Node): boolean => + access !== undefined && + ts.isPropertyAccessExpression(access) && + access.expression === of && + values.has(access.name.text) && + plainRead(access) && + (!calledWithReceiver(access) || receiverHidden(values, access.name.text)); + const holder = ns.parent; + // `__reExport(main, ns, module.exports)`: a star re-export of the + // module into the bundle's own exports (`export * from` in its entry). + // The initializer runs at the statement; the names become live getter + // entries of the export table, after its explicit ones (first wins) + // and without `default`, exactly the names __reExport copies. + if ( + holder !== undefined && + ts.isCallExpression(holder) && + ts.isIdentifier(holder.expression) && + helperBase(holder.expression.text) === "__reExport" && + holder.arguments.nodes[1] === ns && + holder.arguments.nodes.length <= 3 && + ts.isExpressionStatement(holder.parent) && + mainTarget !== null && + ts.isIdentifier(holder.arguments.nodes[0]!) && + (holder.arguments.nodes[0] as ts.Identifier).text === mainTarget + ) { + const inner = unparen(ns); + const init = + ts.isBinaryExpression(inner) && inner.operatorToken.kind === ts.SyntaxKind.CommaToken + ? `${getTextOfNode(unparen(inner.left))}; ` + : ""; + if (shadowed(holder, table.entries)) return { degrade: `its shipped JS ${why}` }; + const entries = table.entries + .filter(([n]) => n !== "default") + .map(([n, value]) => `${JSON.stringify(n)}: () => ${getterText(value)}`) + .join(", "); + edits.push({ + start: tokenStartOfNode(holder.parent), + end: holder.parent.end, + text: `${init}__export(${mainTarget}, { ${entries} });`, + }); + lowered.add(r.text); + continue; + } + let readable = memberRead(holder, ns); + if ( + !readable && + holder !== undefined && + ts.isVariableDeclaration(holder) && + holder.initializer === ns && + ts.isIdentifier(holder.name) + ) { + const local = holder.name.text; + const localRefs = (refsByName.get(local) ?? []).filter((x) => x !== holder.name); + readable = + countDeclarations(sf, local) === 1 && + !writes.has(local) && + localRefs.every((x) => memberRead(x.parent, x)); + } + if (!readable) { + // A namespace kept for later (`x ??= __toESM(ns)`, a hand-rolled + // lazy loader) is a value with no static representation. + let outer: ts.Node | undefined = holder; + while ( + outer !== undefined && + (ts.isParenthesizedExpression(outer) || + (ts.isCallExpression(outer) && + ts.isIdentifier(outer.expression) && + helperBase(outer.expression.text) === "__toESM")) + ) + outer = outer.parent; + if ( + outer !== undefined && + ts.isBinaryExpression(outer) && + outer.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + outer.operatorToken.kind <= ts.SyntaxKind.LastAssignment + ) + return { + degrade: `its shipped JS stores the namespace of a bundled ES module ('${r.text}') in a variable (a memoized lazy loader), a module object the static rewrite cannot represent`, + }; + return { degrade: `its shipped JS ${why}` }; + } + // The getters read top-level bindings: nothing between the use site + // and the top level may rebind one of their names. + if (shadowed(p, table.entries)) return { degrade: `its shipped JS ${why}` }; + const getters = table.entries + .map( + ([n, value]) => + `get ${/^[A-Za-z_$][\w$]*$/.test(n) ? n : JSON.stringify(n)}() { return ${getterText(value)}; }`, + ) + .join(", "); + edits.push({ start: tokenStartOfNode(p), end: p.end, text: `{ ${getters} }` }); + lowered.add(r.text); + } + // A table nothing reads (its module is never required) goes too: its + // names are no exports of the bundle. + for (const [target, table] of tables) { + if (lowered.has(target)) continue; + const live = (refsByName.get(target) ?? []).some((x) => { + const p = x.parent; + if (p !== undefined && ts.isVariableDeclaration(p) && p.name === x) return false; + return !(tokenStartOfNode(x) >= tokenStartOfNode(table.stmt) && x.end <= table.stmt.end); + }); + if (!live) lowered.add(target); + } + for (const target of lowered) { + const table = tables.get(target)!; + const decl = top.get(target)?.node; + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + decl.initializer === undefined || + !ts.isObjectLiteralExpression(unparen(decl.initializer)) || + (unparen(decl.initializer) as ts.ObjectLiteralExpression).properties.nodes.length !== 0 || + writes.has(target) + ) + return { degrade: ESM_SHAPE_DEGRADE }; + edits.push({ start: tokenStartOfNode(table.stmt), end: table.stmt.end, text: "" }); + deadDeclarators.add(decl); + } + // Every removed declarator in one pass: declarators of one statement + // removed separately would leave overlapping edits, and applyEdits keeps + // only the first of them. + edits.push(...removeDeclarators(sf, deadDeclarators)); + + /* The run-once closures. */ + for (const init of inits.values()) { + const { call, body } = init; + edits.push( + { + start: tokenStartOfNode(call), + end: tokenStartOfNode(body) + 1, + text: "(() => { let __scriptc_done = false; function __scriptc_init() {", + }, + { + start: body.end - 1, + end: call.end, + text: "} return function () { if (__scriptc_done) return; __scriptc_done = true; __scriptc_init(); }; })()", + }, + ); + } + return applyEdits(source, 0, source.length, edits); +} diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.test.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.test.ts new file mode 100644 index 000000000..c3704ea29 --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.test.ts @@ -0,0 +1,81 @@ +import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterAll, expect, test } from "vitest"; +import { nodeFrontendServices } from "./services-node.js"; + +const dirs: string[] = []; +afterAll(() => { + for (const dir of dirs) rmSync(dir, { recursive: true, force: true }); +}); + +function rewrite(source: string): string | null { + return nodeFrontendServices().cjsFunctionExports(source, "/pkg/index.js"); +} + +function run(file: string) { + const result = spawnSync(process.execPath, [file], { encoding: "utf8" }); + return { status: result.status, stdout: result.stdout, stderr: result.stderr }; +} + +/** The rewritten module prints what the original prints. */ +function expectSameBehavior(source: string): string { + const result = rewrite(source); + expect(typeof result).toBe("string"); + const dir = mkdtempSync(join(tmpdir(), "scriptc-cjs-function-exports-")); + dirs.push(dir); + const original = join(dir, "original.cjs"); + const rewritten = join(dir, "rewritten.cjs"); + writeFileSync(original, source); + writeFileSync(rewritten, result as string); + const oracle = run(original); + expect(oracle.status).toBe(0); + expect(run(rewritten)).toEqual(oracle); + return result as string; +} + +const report = ` +console.log(typeof module.exports, module.exports === Version, Object.keys(Version).join(",")); +`; + +test("members of a function export object read and write the function", () => { + const rewritten = expectSameBehavior(`exports = module.exports = Version; +exports.parse = parse; +module.exports.major = (v) => parse(v)[0]; +function Version(v) { return { v }; } +function parse(v) { return v.split(".").map(Number); } +console.log(exports.major("2.1.0"), module.exports.parse("1.2").length); +${report}`); + expect(rewritten).toContain("Version.parse = parse"); + expect(rewritten).not.toContain("exports = module.exports"); +}); + +test.each([ + ["a parameter", "function inner(Version) { return exports.parse; }"], + ["a local", "function inner() { const Version = {}; return exports.parse; }"], + ["a nested function", "function inner() { function Version() {} return exports.parse; }"], + [ + "a catch binding", + "function inner() { try { throw 0; } catch (Version) { return exports.parse; } }", + ], +])("a nested binding of the function's name (%s) keeps the module as it is", (_, inner) => { + expect( + rewrite(`exports = module.exports = Version; +exports.parse = parse; +function Version() {} +function parse() {} +${inner} +`), + ).toBeNull(); +}); + +test("an `exports` shorthand property keeps its key", () => { + const rewritten = expectSameBehavior(`exports = module.exports = Version; +exports.tag = "v"; +function Version() {} +const holder = { exports }; +console.log(Object.keys(holder).join(","), holder.exports === Version, holder.exports.tag); +${report}`); + expect(rewritten).toContain("{ exports: Version }"); +}); diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts new file mode 100644 index 000000000..bf753be8c --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-alias.ts @@ -0,0 +1,192 @@ +/* CommonJS modules whose export object is one of their own functions: + * + * exports = module.exports = SemVer; // or module.exports = SemVer; + * exports.parse = parse; // or module.exports.parse = … + * function SemVer(version) { … } + * + * After that statement `module.exports` (and, after the rebinding form, + * `exports`) IS the function, so every member read or write through them + * is a member of the function itself. The pass respells those accesses + * through the function's name, the expando form both resolvers model + * (members on a function declaration), and drops the `exports =` + * rebinding the static export protocol has no storage for. + * + * Exact only while nothing can observe the module before the statement + * runs and nothing replaces the export object again: the statement is a + * top-level statement preceded only by inert declarations, it is the + * file's only write of `module.exports` and `exports`, the function is a + * top-level declaration nothing reassigns and the file's only binding of + * its name, and `exports`/`module` are the CommonJS globals (never + * declared in the file). Other shapes answer + * null and keep their existing treatment. */ +import * as ts from "./ts7/syntax.js"; +import { + applyEdits, + countDeclarations, + declaredNames, + isWritten, + references, + unparen, + type Edit, +} from "./npm-static-bundled-chunks.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; + +function isModuleExports(e: ts.Expression): e is ts.PropertyAccessExpression { + const x = unparen(e); + return ( + ts.isPropertyAccessExpression(x) && + !x.questionDotToken && + ts.isIdentifier(x.expression) && + x.expression.text === "module" && + x.name.text === "exports" + ); +} + +/** True for a statement whose evaluation runs no code that could observe + * the export object: declarations and member writes (`F.prototype.m = + * function …`) without calls, and directives. */ +function inert(stmt: ts.Statement): boolean { + if (ts.isFunctionDeclaration(stmt) || ts.isEmptyStatement(stmt)) return true; + if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression)) return true; + if (ts.isExpressionStatement(stmt)) { + const e = unparen(stmt.expression); + if ( + !ts.isBinaryExpression(e) || + e.operatorToken.kind !== ts.SyntaxKind.EqualsToken || + !(ts.isPropertyAccessExpression(e.left) || ts.isElementAccessExpression(e.left)) + ) + return false; + let root: ts.Expression = e.left; + while (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root)) + root = root.expression; + if (!ts.isIdentifier(root) || root.text === "module" || root.text === "exports") return false; + } else if (!ts.isVariableStatement(stmt)) return false; + let calls = false; + ts.walkPreorder(stmt, (n) => { + if (calls) return "stop"; + if (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) return "skip"; + if (ts.isCallExpression(n) || ts.isNewExpression(n) || ts.isTaggedTemplateExpression(n)) + calls = true; + return undefined; + }); + return !calls; +} + +export function normalizeCjsFunctionExports(sf: ts.SourceFile): string | null { + const source = sf.text; + if (!source.includes("module.exports")) return null; + if (!sf.fileName.endsWith(".cjs") && ts.isExternalModule(sf)) return null; + const declared = declaredNames(sf); + if (declared.has("exports") || declared.has("module")) return null; + + // The whole-export statement: `[exports =] module.exports = W;`. + let whole: { stmt: ts.ExpressionStatement; fn: string; rebinds: boolean } | null = null; + const stmts = sf.statements.nodes; + for (let i = 0; i < stmts.length; i++) { + const stmt = stmts[i]!; + if (!ts.isExpressionStatement(stmt)) continue; + const top = unparen(stmt.expression); + if (!ts.isBinaryExpression(top) || top.operatorToken.kind !== ts.SyntaxKind.EqualsToken) + continue; + let e: ts.BinaryExpression = top; + let rebinds = false; + if (ts.isIdentifier(top.left) && top.left.text === "exports") { + const inner = unparen(top.right); + if (!ts.isBinaryExpression(inner) || inner.operatorToken.kind !== ts.SyntaxKind.EqualsToken) + continue; + rebinds = true; + e = inner; + } + if (!isModuleExports(e.left)) continue; + const value = unparen(e.right); + if (!ts.isIdentifier(value)) return null; + if (whole !== null || !stmts.slice(0, i).every(inert)) return null; + whole = { stmt, fn: value.text, rebinds }; + } + if (whole === null) return null; + const fnName = whole.fn; + const fnDecls = stmts.filter( + (st) => ts.isFunctionDeclaration(st) && st.name?.text === fnName && st.body !== undefined, + ); + // The respelled reads name the function from wherever they sit, so no + // other binding of its name (a parameter, a local, a nested function) + // may exist anywhere in the file. + if (fnDecls.length !== 1 || countDeclarations(sf, fnName) !== 1) return null; + + const edits: Edit[] = []; + let unsafe = false; + const refs = references(sf); + for (const r of refs) { + if (r.text === fnName && isWritten(r)) unsafe = true; + if (r.text === "exports") { + if (isWritten(r)) { + // Only the whole statement's own rebinding. + const assign = r.parent; + if ( + !whole.rebinds || + assign === undefined || + !ts.isBinaryExpression(assign) || + assign.parent !== whole.stmt || + assign.left !== r + ) + unsafe = true; + continue; + } + // Without the rebinding, `exports` keeps the replaced object. + if (!whole.rebinds || tokenStartOfNode(r) < whole.stmt.end) { + unsafe = true; + continue; + } + // `{ exports }` keeps its key: the shorthand spells out the value. + const shorthand = + r.parent !== undefined && ts.isShorthandPropertyAssignment(r.parent) && r.parent.name === r; + edits.push({ + start: tokenStartOfNode(r), + end: r.end, + text: shorthand ? `exports: ${fnName}` : fnName, + }); + continue; + } + if (r.text === "module") { + const access = r.parent; + if (access === undefined || !isModuleExports(access as ts.Expression)) continue; + const outer = access.parent; + if (outer !== undefined && ts.isBinaryExpression(outer) && outer.left === access) { + if ( + outer.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + outer.operatorToken.kind <= ts.SyntaxKind.LastAssignment && + !( + (outer.parent === whole.stmt && !whole.rebinds) || + (whole.rebinds && outer.parent?.parent === whole.stmt) + ) + ) + unsafe = true; + continue; + } + // A member access through module.exports: a member of the function. + if ( + outer !== undefined && + (ts.isPropertyAccessExpression(outer) || ts.isElementAccessExpression(outer)) && + outer.expression === access + ) { + if (tokenStartOfNode(access) < whole.stmt.end && !insideFunction(access)) unsafe = true; + edits.push({ start: tokenStartOfNode(access), end: access.end, text: fnName }); + } + } + } + if (unsafe) return null; + if (whole.rebinds) { + // `exports = module.exports = W` → `module.exports = W`. + const e = unparen(whole.stmt.expression) as ts.BinaryExpression; + edits.push({ start: tokenStartOfNode(e), end: tokenStartOfNode(unparen(e.right)), text: "" }); + } + if (edits.length === 0) return null; + return applyEdits(source, 0, source.length, edits); +} + +function insideFunction(node: ts.Node): boolean { + for (let p = node.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) { + if (ts.isFunctionLike(p)) return true; + } + return false; +} diff --git a/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts new file mode 100644 index 000000000..1e7489595 --- /dev/null +++ b/packages/compiler/src/frontend/npm-static-cjs-exports-var.ts @@ -0,0 +1,239 @@ +/* CommonJS modules that name their export object through a variable: + * + * var ip = exports; + * ip.isPrivate = function (addr) { … }; + * ip.isPublic = function (addr) { return !ip.isPrivate(addr); }; + * + * `ip` IS the export object, so every member read or write through it is + * an export. The pass respells those accesses as `exports.`, the + * form the static export protocol models, and drops the alias declaration + * (the export object has no storage of its own to bind). + * + * Exact only while the variable is nothing but that alias: one top-level + * declaration initialized with the CommonJS `exports` global, never + * reassigned, never declared again anywhere in the file (so no nested + * binding shadows it), and used only as the object of member accesses. + * `exports` itself must be the global (never declared in the file) and is + * never reassigned. Other shapes answer null and keep their existing + * treatment. */ +import * as ts from "./ts7/syntax.js"; +import { tokenStartOfNode } from "./ts7/node-functions.js"; + +function unparen(e: ts.Expression): ts.Expression { + let x = e; + while (ts.isParenthesizedExpression(x)) x = x.expression; + return x; +} + +function isDeclarationName(id: ts.Identifier): boolean { + const p = id.parent; + return ( + ((ts.isVariableDeclaration(p) || + ts.isParameter(p) || + ts.isBindingElement(p) || + ts.isFunctionDeclaration(p) || + ts.isFunctionExpression(p) || + ts.isClassDeclaration(p) || + ts.isClassExpression(p)) && + p.name === id) || + (ts.isPropertyAccessExpression(p) && p.name === id) || + ((ts.isPropertyAssignment(p) || ts.isMethodDeclaration(p) || ts.isPropertyDeclaration(p)) && + p.name === id) + ); +} + +function isFunctionScope(n: ts.Node): boolean { + return ( + ts.isFunctionDeclaration(n) || + ts.isFunctionExpression(n) || + ts.isArrowFunction(n) || + ts.isMethodDeclaration(n) || + ts.isGetAccessorDeclaration(n) || + ts.isSetAccessorDeclaration(n) || + ts.isConstructorDeclaration(n) + ); +} + +function bindsName(name: ts.BindingName | undefined, text: string): boolean { + if (name === undefined) return false; + if (ts.isIdentifier(name)) return name.text === text; + for (const element of name.elements.nodes) { + if (!ts.isOmittedExpression(element) && bindsName(element.name, text)) return true; + } + return false; +} + +/** True when one of `statements` declares `text` lexically: a let/const, + * or a class or function declaration. */ +function declaresLexically(statements: readonly ts.Statement[], text: string): boolean { + for (const stmt of statements) { + if ( + ts.isVariableStatement(stmt) && + (stmt.declarationList.flags & ts.NodeFlags.BlockScoped) !== 0 && + stmt.declarationList.declarations.nodes.some((d) => bindsName(d.name, text)) + ) + return true; + if ((ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt)) && stmt.name?.text === text) + return true; + } + return false; +} + +/** True when a function body declares `text` with `var` (or a function + * declaration in a nested block) anywhere outside nested functions. */ +function declaresVar(body: ts.Node, text: string): boolean { + let found = false; + ts.walkPreorder(body, (n) => { + if (found) return "stop"; + if (n === body) return undefined; + if (isFunctionScope(n)) { + if (ts.isFunctionDeclaration(n) && n.name?.text === text) found = true; + return "skip"; + } + if ( + ts.isVariableDeclarationList(n) && + (n.flags & ts.NodeFlags.BlockScoped) === 0 && + n.declarations.nodes.some((d) => bindsName(d.name, text)) + ) + found = true; + return undefined; + }); + return found; +} + +/** True when `scope` declares `text` itself, so references inside it name + * that binding instead of the module-level alias: a function's parameters, + * own name, `var`s and the let/const, class and function declarations of + * its body; a block's or case block's own let/const, class and function + * declarations; a catch binding; a for loop's let/const. A let/const in a + * nested block shadows only that block, which the walk reaches as its own + * scope. */ +function shadows(scope: ts.Node, text: string): boolean { + if (isFunctionScope(scope)) { + const fn = scope as ts.SignatureDeclaration; + if (fn.parameters.nodes.some((p) => bindsName(p.name, text))) return true; + if (ts.isFunctionExpression(scope) && scope.name?.text === text) return true; + const body = (scope as { body?: ts.Node }).body; + if (body === undefined || !ts.isBlock(body)) return false; + return declaresVar(body, text) || declaresLexically(body.statements.nodes, text); + } + if (ts.isBlock(scope)) return declaresLexically(scope.statements.nodes, text); + if (ts.isCaseBlock(scope)) + return declaresLexically( + scope.clauses.nodes.flatMap((clause) => clause.statements.nodes), + text, + ); + if (ts.isCatchClause(scope)) return bindsName(scope.variableDeclaration?.name, text); + if (ts.isForStatement(scope) || ts.isForInStatement(scope) || ts.isForOfStatement(scope)) { + const init = scope.initializer; + return ( + init !== undefined && + ts.isVariableDeclarationList(init) && + (init.flags & ts.NodeFlags.BlockScoped) !== 0 && + init.declarations.nodes.some((d) => bindsName(d.name, text)) + ); + } + if (ts.isClassExpression(scope)) return scope.name?.text === text; + return false; +} + +export function normalizeCjsExportsVar(sf: ts.SourceFile): string | null { + const source = sf.text; + if (!/\b(?:var|let|const)\s+[A-Za-z_$][\w$]*\s*=\s*exports\s*[;,\n]/.test(source)) return null; + if (!sf.fileName.endsWith(".cjs") && ts.isExternalModule(sf)) return null; + let alias: { name: string; stmt: ts.VariableStatement } | null = null; + for (const stmt of sf.statements.nodes) { + if (!ts.isVariableStatement(stmt)) continue; + const decls = stmt.declarationList.declarations.nodes; + if (decls.length !== 1) continue; + const decl = decls[0]!; + const init = decl.initializer !== undefined ? unparen(decl.initializer) : undefined; + if ( + ts.isIdentifier(decl.name) && + init !== undefined && + ts.isIdentifier(init) && + init.text === "exports" + ) { + if (alias !== null) return null; /* two aliases: keep the plain treatment */ + alias = { name: decl.name.text, stmt }; + } + } + if (alias === null) return null; + const name = alias.name; + const aliasDecl = alias.stmt.declarationList.declarations.nodes[0]!; + const edits: { start: number; end: number; text: string }[] = []; + let ok = true; + ts.walkPreorder(sf, (n) => { + if (!ok) return "stop"; + if (n.kind >= ts.SyntaxKind.FirstJSDocNode && n.kind <= ts.SyntaxKind.LastJSDocNode) + return "skip"; + if ( + ts.isBinaryExpression(n) && + n.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isPropertyAccessExpression(unparen(n.left)) && + (unparen(n.left) as ts.PropertyAccessExpression).name.text === "exports" && + ts.isIdentifier((unparen(n.left) as ts.PropertyAccessExpression).expression) && + ((unparen(n.left) as ts.PropertyAccessExpression).expression as ts.Identifier).text === + "module" + ) { + ok = false; /* module.exports replaced: the alias no longer names the exports */ + return "stop"; + } + // A scope that declares the alias's name again: its references name + // that binding, not the export object. + if ( + n !== sf && + (isFunctionScope(n) || + ts.isBlock(n) || + ts.isCaseBlock(n) || + ts.isCatchClause(n) || + ts.isForStatement(n) || + ts.isForInStatement(n) || + ts.isForOfStatement(n) || + ts.isClassExpression(n)) && + shadows(n, name) + ) + return "skip"; + if (!ts.isIdentifier(n)) return undefined; + if (n.text === "exports" && n !== aliasDecl.initializer) { + // `exports` declared in the file, or the export object replaced + if (isDeclarationName(n) && !ts.isPropertyAccessExpression(n.parent)) ok = false; + const p = n.parent; + if ( + ts.isBinaryExpression(p) && + p.left === n && + p.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) + ok = false; + return undefined; + } + if (n.text !== name) return undefined; + if (n === aliasDecl.name) return undefined; + const p = n.parent; + if (ts.isPropertyAccessExpression(p) && p.name === n) return undefined; + if ((ts.isPropertyAssignment(p) || ts.isMethodDeclaration(p)) && p.name === n) return undefined; + if (isDeclarationName(n)) { + ok = false; /* declared again (a shadowing binding): not a plain alias */ + return undefined; + } + if ( + (ts.isPropertyAccessExpression(p) || ts.isElementAccessExpression(p)) && + p.expression === n + ) { + edits.push({ start: tokenStartOfNode(n), end: n.end, text: "exports" }); + return undefined; + } + ok = false; /* used as a value, assigned, or passed along */ + return undefined; + }); + if (!ok || edits.length === 0) return null; + edits.push({ start: tokenStartOfNode(alias.stmt), end: alias.stmt.end, text: "" }); + edits.sort((a, b) => a.start - b.start); + let out = ""; + let at = 0; + for (const e of edits) { + out += source.slice(at, e.start) + e.text; + at = e.end; + } + return out + source.slice(at); +} diff --git a/packages/compiler/src/frontend/npm-static-declaration-syntax.ts b/packages/compiler/src/frontend/npm-static-declaration-syntax.ts index 4a84dcfeb..0d713dcff 100644 --- a/packages/compiler/src/frontend/npm-static-declaration-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-declaration-syntax.ts @@ -10,6 +10,7 @@ * too; inferred checked values validate at the ordinary return boundary. */ import * as ts from "./ts7/syntax.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; export interface NpmStaticOverloadParameter { name: string; @@ -56,11 +57,11 @@ export function applyNpmStaticJsDocNamepaths( for (const tag of doc.tags?.nodes ?? []) { const expression = tag.typeExpression; if (expression?.kind !== ts.SyntaxKind.JSDocTypeExpression) continue; - const start = expression.getStart(sourceFile); + const start = tokenStartOfNode(expression); if (source[start] !== "{") continue; let depth = 1; let quote = ""; - for (let i = start + 1; i < tag.getEnd() && depth > 0; i++) { + for (let i = start + 1; i < tag.end && depth > 0; i++) { const char = source[i]!; if (quote !== "") { if (char === "\\") i++; @@ -80,7 +81,7 @@ export function applyNpmStaticJsDocNamepaths( continue; } const name = /^[$A-Z_a-z][$\w]*(?:[.#~][$A-Z_a-z][$\w]*)*/.exec( - source.slice(i, tag.getEnd()), + source.slice(i, tag.end), )?.[0]; if (!name) continue; if (name.includes("~") || name.includes("#")) replacements.set(i, name.length); @@ -171,7 +172,7 @@ export function applyNpmStaticNullableClassFields( hasModifier(member, ts.SyntaxKind.StaticKeyword) ) continue; - const leading = source.slice(member.getFullStart(), member.getStart(sourceFile)); + const leading = source.slice(member.pos, tokenStartOfNode(member)); if (jsDocTypeOf(member) !== undefined || leading.includes("@type")) continue; fields.set(member.name.text, { member, invalid: false }); } @@ -285,7 +286,7 @@ export function applyNpmStaticNullableClassFields( for (const field of fields.values()) { if (field.invalid || field.className === undefined) continue; inserts.push({ - offset: field.member.getStart(sourceFile), + offset: tokenStartOfNode(field.member), text: `/** @type {${field.className} | null} */ `, }); } @@ -358,7 +359,7 @@ export function applyNpmStaticFindReturnWidening( (returnType.typeArguments?.nodes.length ?? 0) !== 0 ) continue; - insertions.push({ offset: returnType.getEnd(), length: " | undefined".length }); + insertions.push({ offset: returnType.end, length: " | undefined".length }); } } if (insertions.length === 0) return null; @@ -428,26 +429,22 @@ function hasModifier(node: ts.Node, kind: ts.SyntaxKind): boolean { ); } -function safeTypeText( - node: ts.TypeNode, - sourceFile: ts.SourceFile, - className: string, -): string | null { - if (SAFE_KEYWORD_TYPES.has(node.kind) || ts.isThisTypeNode(node)) return node.getText(sourceFile); +function safeTypeText(node: ts.TypeNode, className: string): string | null { + if (SAFE_KEYWORD_TYPES.has(node.kind) || ts.isThisTypeNode(node)) return getTextOfNode(node); if (ts.isLiteralTypeNode(node) && node.literal.kind === ts.SyntaxKind.NullKeyword) return "null"; if (ts.isParenthesizedTypeNode(node)) { - const inner = safeTypeText(node.type, sourceFile, className); + const inner = safeTypeText(node.type, className); return inner === null ? null : `(${inner})`; } if (ts.isArrayTypeNode(node)) { - const element = safeTypeText(node.elementType, sourceFile, className); + const element = safeTypeText(node.elementType, className); return element === null ? null : `${element}[]`; } if (ts.isTypeOperatorNode(node) && node.operator === ts.SyntaxKind.ReadonlyKeyword) { - return safeTypeText(node.type, sourceFile, className); + return safeTypeText(node.type, className); } if (ts.isUnionTypeNode(node)) { - const arms = node.types.nodes.map((type) => safeTypeText(type, sourceFile, className)); + const arms = node.types.nodes.map((type) => safeTypeText(type, className)); return arms.some((arm) => arm === null) ? null : arms.join(" | "); } if ( @@ -483,7 +480,7 @@ function overloadSignature( ) { return null; } - const returnType = safeTypeText(method.type, sourceFile, className); + const returnType = safeTypeText(method.type, className); if (returnType === null) return null; const parameters: NpmStaticOverloadParameter[] = []; for (const parameter of method.parameters.nodes) { @@ -496,7 +493,7 @@ function overloadSignature( ) { return null; } - const type = safeTypeText(parameter.type, sourceFile, className); + const type = safeTypeText(parameter.type, className); if (type === null) return null; parameters.push({ name: parameter.name.text, @@ -553,13 +550,9 @@ export function parseNpmStaticDeclarationOverloads( return classes; } -function nullableSelfType( - node: ts.TypeNode, - sourceFile: ts.SourceFile, - className: string, -): string | null { +function nullableSelfType(node: ts.TypeNode, className: string): string | null { if (!ts.isUnionTypeNode(node) || node.types.nodes.length !== 2) return null; - const arms = node.types.nodes.map((type) => safeTypeText(type, sourceFile, className)); + const arms = node.types.nodes.map((type) => safeTypeText(type, className)); return arms.includes(className) && arms.includes("null") ? `${className} | null` : null; } @@ -613,8 +606,7 @@ export function parseNpmStaticDeclarationProperties( continue; } const type = - nullableSelfType(member.type, sourceFile, className) ?? - nullableVoidCallbackType(member.type); + nullableSelfType(member.type, className) ?? nullableVoidCallbackType(member.type); if (type !== null) properties.set(member.name.text, type); } if (properties.size > 0) classes.set(className, properties); @@ -1012,13 +1004,10 @@ export function applyNpmStaticDeclarationOverloads( ) { continue; } - const leading = source.slice( - bodyStatement.getFullStart(), - bodyStatement.getStart(sourceFile), - ); + const leading = source.slice(bodyStatement.pos, tokenStartOfNode(bodyStatement)); if (!leading.includes("@type")) { inserts.push({ - offset: bodyStatement.getStart(sourceFile), + offset: tokenStartOfNode(bodyStatement), text: `/** @type {${backing.type}} */ `, }); } @@ -1036,13 +1025,13 @@ export function applyNpmStaticDeclarationOverloads( (parameter) => parameter.optional && !existing.includes(`[${parameter.name}]`), ); if (missingOptional) - inserts.push({ offset: member.getStart(sourceFile), text: `${implementation} ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `${implementation} ` }); else if ( jsDocs.length === 0 && member.parameters.nodes.length === 0 && SCALAR_METHOD_RETURNS.has(signatures[0]!.returnType) ) { - inserts.push({ offset: member.getStart(sourceFile), text: `${implementation} ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `${implementation} ` }); } } continue; @@ -1050,7 +1039,9 @@ export function applyNpmStaticDeclarationOverloads( const implementation = jsDocs.length === 0 ? implementationComment(statement.name.text, member, signatures) : null; if (jsDocs.length === 0 && implementation === null) continue; - const offset = jsDocs[0]?.getStart(sourceFile) ?? member.getStart(sourceFile); + const offset = + (jsDocs[0] === undefined ? undefined : tokenStartOfNode(jsDocs[0])) ?? + tokenStartOfNode(member); const text = `${signatures.map(overloadComment).join(" ")} ${implementation === null ? "" : implementation + " "}`; inserts.push({ offset, text }); } @@ -1158,9 +1149,9 @@ export function applyNpmStaticDeclarationProperties( !callbackInitializer(member.initializer)) ) continue; - const leading = source.slice(member.getFullStart(), member.getStart(sourceFile)); + const leading = source.slice(member.pos, tokenStartOfNode(member)); if (jsDocTypeOf(member) !== undefined || leading.includes("@type")) continue; - inserts.push({ offset: member.getStart(sourceFile), text: `/** @type {${type}} */ ` }); + inserts.push({ offset: tokenStartOfNode(member), text: `/** @type {${type}} */ ` }); } const constructor = statement.members.nodes.find( (member): member is ts.ConstructorDeclaration => @@ -1184,12 +1175,9 @@ export function applyNpmStaticDeclarationProperties( } const type = properties.get(left.name.text); if (type === undefined) continue; - const leading = source.slice( - bodyStatement.getFullStart(), - bodyStatement.getStart(sourceFile), - ); + const leading = source.slice(bodyStatement.pos, tokenStartOfNode(bodyStatement)); if (leading.includes("@type")) continue; - inserts.push({ offset: bodyStatement.getStart(sourceFile), text: `/** @type {${type}} */ ` }); + inserts.push({ offset: tokenStartOfNode(bodyStatement), text: `/** @type {${type}} */ ` }); } } if (inserts.length === 0) return null; diff --git a/packages/compiler/src/frontend/npm-static-rewrite-host.ts b/packages/compiler/src/frontend/npm-static-rewrite-host.ts index ee36f58d1..4e0c92e31 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-host.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-host.ts @@ -2,10 +2,16 @@ import { dirname, join, resolve as resolvePath } from "node:path"; import { cjsLexedExportsOfFile, cjsVisibleNames } from "./cjs-syntax.js"; import type { Ts7SourceParser } from "./ts7/source-parser.js"; -import { trackedDirectoryExists, trackedFileExists, trackedReadFile } from "./input-tracker.js"; +import { + isVirtualSource, + trackedDirectoryExists, + trackedFileExists, + trackedReadFile, +} from "./input-tracker.js"; import { resolveExports } from "./resolve.js"; import { packageNameOfSpecifier } from "./workspace-registry.js"; import { isBundlerCjsCandidate, rewriteBundlerCjsSyntax } from "./npm-static-rewrite-syntax.js"; +import { lowerBundledEsmInits } from "./npm-static-bundled-esm.js"; const NODE_REQUIRE_CONDITIONS = new Set(["require", "node", "default"]); @@ -134,7 +140,7 @@ function requireTargetEsModuleStamped( } catch { /* unlexable — keep probing */ } - if (src.includes("__toCommonJS(")) return true; + if (/__toCommonJS\d*\(/.test(src)) return true; const fwd = /module\.exports\s*=\s*require\(\s*["']([^"']+)["']\s*\)/.exec(src); if (fwd !== null) return requireTargetEsModuleStamped(parser, file, fwd[1]!, depth + 1); return false; @@ -159,13 +165,29 @@ function starTargetNames(parser: Ts7SourceParser, file: string): Set { } } -export function rewriteBundlerCjsWithParser( +export function lowerEsmInitsWithParser( parser: Ts7SourceParser, source: string, filePath: string, +): string | { degrade: string } | null { + if (!source.includes("__esm")) return null; + return lowerBundledEsmInits(parser.parse(filePath, source, "js"), filePath, { + requireTargetEsModuleStamped: (from, spec) => requireTargetEsModuleStamped(parser, from, spec), + }); +} + +export function rewriteBundlerCjsWithParser( + parser: Ts7SourceParser, + source: string, + filePath: string, + liveExportTable?: (filePath: string) => void, + lexerVisibleNames?: (filePath: string, names: readonly string[]) => void, ): string | { degrade: string } | null { if (!isBundlerCjsCandidate(source)) return null; return rewriteBundlerCjsSyntax(parser.parse(filePath, source, "js"), filePath, { + liveExportTable, + lexerVisibleNames, + requireOnly: isVirtualSource, requireTargetEsModuleStamped: (from, spec) => requireTargetEsModuleStamped(parser, from, spec), starTargetNames: (from, spec) => { const target = resolveRelativeCjs(from, spec); diff --git a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts index 7938471f3..c48420625 100644 --- a/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts +++ b/packages/compiler/src/frontend/npm-static-rewrite-syntax.ts @@ -6,12 +6,80 @@ import { isModuleExports, } from "./cjs-syntax.js"; import { rewriteBundledFunctionImports } from "./npm-static-bundled-cjs.js"; +import { getTextOfNode, tokenStartOfNode } from "./ts7/node-functions.js"; /** Resolution stays with the compilation host. Syntax never reads disk or * starts a parser implicitly, so native and Node hosts run the same pass. */ export interface CjsRewriteHost { requireTargetEsModuleStamped(fromFile: string, specifier: string): boolean; starTargetNames(fromFile: string, specifier: string): Set; + /** Told when the rewritten file's tail export table stands for a live + * getter table the original installed before it could run another + * module's code (liveExportTableFirst): a module of a require cycle that + * captured the export object early observes exactly that table. */ + liveExportTable?: ((filePath: string) => void) | undefined; + /** True for a module only ever loaded through require() (a bundle's + * CommonJS chunk split into its own module): no ES-module importer + * exists, so Node's lexer-visible name set bounds nothing and every + * name the export plumbing defines is part of its surface. */ + requireOnly?: ((filePath: string) => boolean) | undefined; + /** Told the names Node's lexer detects in the original file that the + * rewritten text spells only as accessors (which the lexer skips). */ + lexerVisibleNames?: ((filePath: string, names: readonly string[]) => void) | undefined; +} + +/** True when esbuild's export object (`module.exports = __toCommonJS(T)` + * over a getter table `__export(T, {...})`) is installed before anything + * that can run another module's code, and nothing else writes the export + * object. Every statement before the installation is then a declaration, + * a directive, or the getter-table call (whose getters run only when + * read), and variable initializers evaluate no calls outside function + * bodies. The getters read live bindings, so the object a cycle partner + * captures early answers every later read exactly like the final table. */ +function liveExportTableFirst( + sf: ts.SourceFile, + installAt: number, + target: string, + exportTables: readonly { target: string }[], +): boolean { + if (!exportTables.some((t) => t.target === target)) return false; + let callsOutsideFunctions = false; + const noCalls = (root: ts.Node): boolean => { + callsOutsideFunctions = false; + ts.walkPreorder(root, (n) => { + if (callsOutsideFunctions) return "stop"; + if (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n)) + return "skip"; + if (ts.isCallExpression(n) || ts.isNewExpression(n) || ts.isTaggedTemplateExpression(n)) + callsOutsideFunctions = true; + return undefined; + }); + return !callsOutsideFunctions; + }; + let sawTable = false; + for (const stmt of sf.statements.nodes) { + if (tokenStartOfNode(stmt) >= installAt) return sawTable; + if (ts.isFunctionDeclaration(stmt) || ts.isEmptyStatement(stmt)) continue; + if (ts.isExpressionStatement(stmt) && ts.isStringLiteral(stmt.expression)) continue; + if (ts.isVariableStatement(stmt)) { + if ( + stmt.declarationList.declarations.nodes.every( + (d) => ts.isIdentifier(d.name) && (d.initializer === undefined || noCalls(d.initializer)), + ) + ) + continue; + return false; + } + if (ts.isExpressionStatement(stmt) && ts.isCallExpression(stmt.expression)) { + const table = exportCallOf(stmt.expression); + if (table !== null && table.target === target) { + sawTable = true; + continue; + } + } + return false; + } + return false; } export function isBundlerCjsCandidate(source: string): boolean { @@ -24,24 +92,43 @@ export function isBundlerCjsCandidate(source: string): boolean { ); } +/** The helper a bundler-emitted name spells. esbuild suffixes a helper + * that a bundled module declares again with a counter (a bundled esbuild + * dist inside another bundle carries `__export2`, `__toCommonJS2`, …); the + * recognizers below verify each call's shape, so the counter carries no + * meaning beyond the name collision it avoided. */ +export function bundlerHelperBase(name: string): string { + return /^__[A-Za-z]+\d+$/.test(name) ? name.replace(/\d+$/, "") : name; +} + /** The last NAME of a callee: bare identifier or any member chain's final - * name (`tslib_1.__exportStar` → "__exportStar"). */ + * name (`tslib_1.__exportStar` → "__exportStar"), helper counters dropped. */ function calleeNameOf(call: ts.CallExpression): string | null { const callee = call.expression; - if (ts.isIdentifier(callee)) return callee.text; + if (ts.isIdentifier(callee)) return bundlerHelperBase(callee.text); if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) - return callee.name.text; + return bundlerHelperBase(callee.name.text); return null; } +/** True for an identifier spelling the named helper (counter-suffixed + * spellings included). */ +function isHelperIdent(node: ts.Node, base: string): node is ts.Identifier { + return ts.isIdentifier(node) && bundlerHelperBase(node.text) === base; +} + /** An entry the canonical table can carry. */ interface EntrySpec { /** The export name (may need string-key spelling). */ name: string; /** "ident": VALUE is a plain identifier spelled directly in the table. * "hoist": VALUE is an expression text hoisted to a tail const first. + * "getter": VALUE is a getter body's returned expression the table + * keeps as a live accessor (read on every access, like the original + * getter, so a lazily initialized module still initializes at the + * first read). * "any": the checked-dynamic fallback (`__scriptc_any`). */ - kind: "ident" | "hoist" | "any"; + kind: "ident" | "hoist" | "getter" | "any"; value?: string; } @@ -60,7 +147,7 @@ function chaseValue(e: ts.Expression): { kind: "ident" | "hoist"; text: string } cur.kind === ts.SyntaxKind.FalseKeyword || cur.kind === ts.SyntaxKind.NullKeyword ) { - return { kind: "hoist", text: cur.getText() }; + return { kind: "hoist", text: getTextOfNode(cur) }; } // ident-rooted member chains: this.x / a.b.c / a["k"].d — side-effect // free reads the tail const can evaluate once. @@ -75,10 +162,30 @@ function chaseValue(e: ts.Expression): { kind: "ident" | "hoist"; text: string } ) { walk = walk.expression; } - if (ts.isIdentifier(walk)) return { kind: "hoist", text: cur.getText() }; + if (ts.isIdentifier(walk)) return { kind: "hoist", text: getTextOfNode(cur) }; return null; } +/** An object literal of side-effect-free member values (`exports.default + * = { context: context_api_1.context, metrics: metrics_api_1.metrics }`, + * tsc's default-export object): the tail const can build it once, like a + * chased value. Null for any other shape (spreads, methods, accessors, + * computed keys, or a value chaseValue rejects). */ +function chaseObjectLiteral(e: ts.Expression): { kind: "hoist"; text: string } | null { + let cur: ts.Expression = e; + while (ts.isParenthesizedExpression(cur)) cur = cur.expression; + if (!ts.isObjectLiteralExpression(cur)) return null; + for (const prop of cur.properties.nodes) { + if ( + !ts.isPropertyAssignment(prop) || + !(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) || + (chaseValue(prop.initializer) === null && chaseObjectLiteral(prop.initializer) === null) + ) + return null; + } + return { kind: "hoist", text: getTextOfNode(cur) }; +} + /** The getter body's returned expression for the two descriptor getter * spellings (`get() { return X }` / `get: function () { return X }`) and * the arrow forms the esbuild table uses (`() => X`, `() => { return X }`). */ @@ -194,13 +301,8 @@ interface ToEsmPlan { * __copyProps(…, mod) call copying onto a conditional that tests * isNodeMode/__esModule and defines "default" with value mod. Comments and * formatting are free; the SHAPE is the contract. */ -function recognizedToEsmDecl(stmt: ts.Statement): boolean { - if (!ts.isVariableStatement(stmt)) return false; - const decls = stmt.declarationList.declarations.nodes; - if (decls.length !== 1) return false; - const d = decls[0]!; - if (!ts.isIdentifier(d.name) || d.name.text !== "__toESM" || d.initializer === undefined) - return false; +function recognizedToEsmDecl(d: ts.VariableDeclaration): boolean { + if (!isHelperIdent(d.name, "__toESM") || d.initializer === undefined) return false; let init: ts.Expression = d.initializer; while (ts.isParenthesizedExpression(init)) init = init.expression; if (!ts.isArrowFunction(init)) return false; @@ -218,28 +320,18 @@ function recognizedToEsmDecl(stmt: ts.Statement): boolean { expr = expr.right; } while (ts.isParenthesizedExpression(expr)) expr = expr.expression; - if ( - !ts.isCallExpression(expr) || - !ts.isIdentifier(expr.expression) || - expr.expression.text !== "__copyProps" - ) - return false; + if (!ts.isCallExpression(expr) || !isHelperIdent(expr.expression, "__copyProps")) return false; if (expr.arguments.nodes.length < 2) return false; const from = expr.arguments.nodes[1]!; if (!ts.isIdentifier(from) || from.text !== mod) return false; let to: ts.Expression = expr.arguments.nodes[0]!; while (ts.isParenthesizedExpression(to)) to = to.expression; if (!ts.isConditionalExpression(to)) return false; - const condText = to.condition.getText(); + const condText = getTextOfNode(to.condition); if (!condText.includes(isNodeMode) || !condText.includes("__esModule")) return false; let stamp: ts.Expression = to.whenTrue; while (ts.isParenthesizedExpression(stamp)) stamp = stamp.expression; - if ( - !ts.isCallExpression(stamp) || - !ts.isIdentifier(stamp.expression) || - stamp.expression.text !== "__defProp" - ) - return false; + if (!ts.isCallExpression(stamp) || !isHelperIdent(stamp.expression, "__defProp")) return false; if (stamp.arguments.nodes.length !== 3) return false; const nameArg = stamp.arguments.nodes[1]!; if (!ts.isStringLiteral(nameArg) || nameArg.text !== "default") return false; @@ -278,19 +370,23 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH degrade: reason, }); - const declStmts = sf.statements.nodes.filter( - (s) => - ts.isVariableStatement(s) && - s.declarationList.declarations.nodes.some( - (d) => ts.isIdentifier(d.name) && d.name.text === "__toESM", - ), - ); + // Top-level declarators of the helper, any statement shape (minified + // dists share one `var` among several helpers), by spelling. + const decls = new Map(); + for (const s of sf.statements.nodes) { + if (!ts.isVariableStatement(s)) continue; + for (const d of s.declarationList.declarations.nodes) { + if (!isHelperIdent(d.name, "__toESM")) continue; + const list = decls.get(d.name.text); + if (list === undefined) decls.set(d.name.text, [d]); + else list.push(d); + } + } const refs: ts.Identifier[] = []; const calls: ts.CallExpression[] = []; const collect = (n: ts.Node): void => { - if (ts.isIdentifier(n) && n.text === "__toESM") refs.push(n); - if (ts.isCallExpression(n) && ts.isIdentifier(n.expression) && n.expression.text === "__toESM") - calls.push(n); + if (isHelperIdent(n, "__toESM")) refs.push(n); + if (ts.isCallExpression(n) && isHelperIdent(n.expression, "__toESM")) calls.push(n); }; ts.walkPreorder(sf, collect); if (calls.length === 0 && refs.length === 0) return plan; @@ -298,15 +394,16 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH // a helper that escapes as a VALUE is outside the recognized shape. const callees = new Set(calls.map((c) => c.expression)); const declNames = new Set(); - for (const s of declStmts) { - for (const d of (s as ts.VariableStatement).declarationList.declarations.nodes) { - if (ts.isIdentifier(d.name) && d.name.text === "__toESM") declNames.add(d.name); - } - } + for (const list of decls.values()) for (const d of list) declNames.add(d.name); if (refs.some((r) => !callees.has(r) && !declNames.has(r))) return fail(TO_ESM_ESCAPE_DEGRADE); if (calls.length === 0) return plan; // declared but never called — dead helper - if (declStmts.length !== 1 || !recognizedToEsmDecl(declStmts[0]!)) - return fail(TO_ESM_SHAPE_DEGRADE); + // Every spelling a call uses must be declared exactly once, in the + // recognized shape. + for (const call of calls) { + const list = decls.get((call.expression as ts.Identifier).text); + if (list === undefined || list.length !== 1 || !recognizedToEsmDecl(list[0]!)) + return fail(TO_ESM_SHAPE_DEGRADE); + } /** Module-scope interop bindings: name → whether `.default` IS the * module (pad the access) rather than a member read of its `default`. */ @@ -347,7 +444,7 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH ) { if (parent.name.text === "default" && defaultIsModule) { if (!readOnlyAccess(parent)) return fail(TO_ESM_ESCAPE_DEGRADE); - pads.push({ start: child.getEnd(), end: parent.getEnd() }); + pads.push({ start: child.end, end: parent.end }); } } else { return fail(TO_ESM_ESCAPE_DEGRADE); @@ -355,8 +452,8 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH // the wrapper itself: callee + '(' down, and everything after the // require argument (`, 1` included) — the bare require survives at its // original offsets. - pads.push({ start: call.getStart(sf), end: arg0!.getStart(sf) }); - pads.push({ start: arg0!.getEnd(), end: call.getEnd() }); + pads.push({ start: tokenStartOfNode(call), end: tokenStartOfNode(arg0!) }); + pads.push({ start: arg0!.end, end: call.end }); } if (bindings.size > 0) { @@ -417,7 +514,7 @@ function planToEsmInterop(sf: ts.SourceFile, filePath: string, host: CjsRewriteH violated = true; return; } - pads.push({ start: n.expression.getEnd(), end: n.getEnd() }); + pads.push({ start: n.expression.end, end: n.end }); } }; ts.walkPreorder(sf, padDefaults); @@ -467,16 +564,20 @@ export function rewriteBundlerCjsSyntax( // ESM bundles can contain closed CommonJS function factories. Recognize // those before refusing interop that needs a dynamic module namespace. - if (ts.isExternalModule(sf)) { + // TypeScript forces every `.cjs` file to be an external module + // (isFileForcedToBeModuleByFormat), yet Node always loads one as + // CommonJS: the extension, not that flag, decides the format there. + if (!filePath.endsWith(".cjs") && ts.isExternalModule(sf)) { const bundled = rewriteBundledFunctionImports(source, sf); if (bundled !== null) return bundled; - return source.includes("__toESM(") - ? { - degrade: - "its ES-module dist routes an external dependency through the __toESM " + - "bundler-interop helper, which cannot compile natively in ESM output", - } - : null; + if (!source.includes("__toESM(")) return null; + return { + degrade: source.includes("__commonJS") + ? "its ES-module bundle reaches bundled CommonJS modules through the __commonJS and " + + "__toESM bundler-interop helpers, which cannot compile natively in ESM output" + : "its ES-module dist routes an external dependency through the __toESM " + + "bundler-interop helper, which cannot compile natively in ESM output", + }; } // The __toESM interop pass (see the section header): wrapper call sites @@ -528,8 +629,8 @@ export function rewriteBundlerCjsSyntax( ts.isIdentifier(rhs.arguments.nodes[0]!) ) { esbuildMainTarget = (rhs.arguments.nodes[0] as ts.Identifier).text; - tablePos = stmt.getStart(sf); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + tablePos = tokenStartOfNode(stmt); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -562,9 +663,9 @@ export function rewriteBundlerCjsSyntax( names, finalRhs: cur, viaBareExports, - pos: stmt.getStart(sf), - start: stmt.getStart(sf), - end: stmt.getEnd(), + pos: tokenStartOfNode(stmt), + start: tokenStartOfNode(stmt), + end: stmt.end, }); continue; // classified later (only when a rewrite actually happens) } @@ -584,7 +685,7 @@ export function rewriteBundlerCjsSyntax( isModuleExports(inner.left) && ts.isObjectLiteralExpression(inner.right) ) { - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -595,7 +696,7 @@ export function rewriteBundlerCjsSyntax( const table = exportCallOf(e); if (table !== null) { exportTables.push(table); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -605,7 +706,7 @@ export function rewriteBundlerCjsSyntax( const spec = bareRequireSpecOf(e.arguments.nodes[0]!); if (spec !== null) { addStarSpec(spec); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -616,7 +717,7 @@ export function rewriteBundlerCjsSyntax( const spec = bareRequireSpecOf(e.arguments.nodes[1]!); if (spec !== null) { addStarSpec(spec); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; continue; } @@ -625,7 +726,7 @@ export function rewriteBundlerCjsSyntax( const def = definePropertyExportOf(e); if (def !== null) { if (def.esModuleStamp) { - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); // the canonical table re-stamps it (lexer-visibly — merve detects // this defineProperty form, so parity wants a visible entry) continue; @@ -633,7 +734,7 @@ export function rewriteBundlerCjsSyntax( const chased = def.value !== null ? chaseValue(def.value) : null; if (chased !== null) { definePropEntries.push({ name: def.name, value: def.value }); - neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() }); + neutralize.push({ start: tokenStartOfNode(stmt), end: stmt.end }); sawBundleShape = true; } // unchaseable descriptor: LEAVE the statement (its runtime fence @@ -644,6 +745,11 @@ export function rewriteBundlerCjsSyntax( } const lexed = cjsLexedExportsOfFile(sf); + if (host.requireOnly?.(filePath) === true) { + for (const t of exportTables) for (const [name] of t.entries) lexed.exports.add(name); + for (const d of definePropEntries) lexed.exports.add(d.name); + for (const m of memberStmts) for (const name of m.names) lexed.exports.add(name); + } let emitTable = sawBundleShape && (lexed.exports.size > 0 || lexed.reexports.length > 0); const starSpecs: string[] = []; @@ -682,7 +788,14 @@ export function rewriteBundlerCjsSyntax( ...exportTables.filter((t) => t.target !== esbuildMainTarget), ]; for (const t of tablesOrdered) { - for (const [name, value] of t.entries) claim(name, classify(name, value)); + for (const [name, value] of t.entries) { + const spec = classify(name, value); + // The table feeding module.exports keeps a getter it cannot chase + // as a live accessor rather than a snapshot. + if (spec.kind === "any" && value !== null && t.target === esbuildMainTarget) + claim(name, { name, kind: "getter", value: getTextOfNode(value) }); + else claim(name, spec); + } } for (const d of definePropEntries) claim(d.name, classify(d.name, d.value)); @@ -714,7 +827,7 @@ export function rewriteBundlerCjsSyntax( // anything else keeps the file untouched (the offender path owns // it: neutralizing would drop an evaluation, keeping it meets the // lowering's discard fence at load). - const chased = chaseValue(cur); + const chased = chaseValue(cur) ?? chaseObjectLiteral(cur); if (chased === null) { return toEsm.pads.length > 0 ? { degrade: TO_ESM_MIXED_DEGRADE } : null; } @@ -814,21 +927,36 @@ export function rewriteBundlerCjsSyntax( "__reExport", "__toESM", "__toCommonJS", + "__commonJS", + "__esm", "__exportStar", "__createBinding", "__setModuleDefault", "__importStar", "__importDefault", ]); + /* Helper declarators by name. A minified dist shares one `var` among + * several helpers (`var __getProtoOf=…,__hasOwnProp=…;`): each + * declarator is its own candidate, and the statement pads whole only + * once every declarator in it is dead. */ const helperDecls = new Map(); + const helperStmts: ts.VariableStatement[] = []; for (const stmt of sf.statements.nodes) { if (!ts.isVariableStatement(stmt)) continue; const decls = stmt.declarationList.declarations.nodes; - if (decls.length !== 1) continue; - const d = decls[0]!; - if (ts.isIdentifier(d.name) && HELPER_NAMES.has(d.name.text)) { - helperDecls.set(d.name.text, { start: stmt.getStart(sf), end: stmt.getEnd() }); + let anyHelper = false; + for (const d of decls) { + if (!ts.isIdentifier(d.name) || !HELPER_NAMES.has(bundlerHelperBase(d.name.text))) continue; + if (helperDecls.has(d.name.text)) continue; // redeclared: keep the first, never sweep twice + anyHelper = true; + helperDecls.set( + d.name.text, + decls.length === 1 + ? { start: tokenStartOfNode(stmt), end: stmt.end } + : { start: tokenStartOfNode(d), end: d.end }, + ); } + if (anyHelper && decls.length > 1) helperStmts.push(stmt); } // every identifier occurrence of a helper name outside its own // declarator name node, position-recorded for the span test @@ -839,8 +967,8 @@ export function rewriteBundlerCjsSyntax( if (parent !== undefined && ts.isVariableDeclaration(parent) && parent.name === node) return; const list = refs.get(node.text); - if (list === undefined) refs.set(node.text, [node.getStart(sf)]); - else list.push(node.getStart(sf)); + if (list === undefined) refs.set(node.text, [tokenStartOfNode(node)]); + else list.push(tokenStartOfNode(node)); } }; ts.walkPreorder(sf, record); @@ -862,8 +990,39 @@ export function rewriteBundlerCjsSyntax( } } } + // A swept declarator of a shared statement pads with its separating + // comma, and the statement pads whole once every declarator is dead: + // padding the bare declarator span would leave a dangling comma. + const declaratorSpans = new Set<{ start: number; end: number }>(); + const declaratorPads: { start: number; end: number }[] = []; + for (const stmt of helperStmts) { + const decls = stmt.declarationList.declarations.nodes; + const deadAt = decls.map((d) => { + if (!ts.isIdentifier(d.name) || !removed.has(d.name.text)) return false; + const span = helperDecls.get(d.name.text)!; + if (span.start !== tokenStartOfNode(d)) return false; + declaratorSpans.add(span); + return true; + }); + if (deadAt.every((x) => x)) { + declaratorPads.push({ start: tokenStartOfNode(stmt), end: stmt.end }); + continue; + } + // Dead declarators before the last live one take their trailing + // comma; the dead tail after it pads from that declarator's end. + const lastLive = deadAt.lastIndexOf(false); + for (let i = 0; i < decls.length; i++) { + if (!deadAt[i]) continue; + const d = decls[i]!; + declaratorPads.push( + i < lastLive + ? { start: tokenStartOfNode(d), end: tokenStartOfNode(decls[i + 1]!) } + : { start: decls[lastLive]!.end, end: d.end }, + ); + } + } neutralize.length = 0; - neutralize.push(...dead); + neutralize.push(...dead.filter((span) => !declaratorSpans.has(span)), ...declaratorPads); } /* ── emission ─────────────────────────────────────────────────────── */ @@ -875,6 +1034,15 @@ export function rewriteBundlerCjsSyntax( if (!emitTable) return text; // interop pads only — nothing appends + if ( + esbuildMainTarget !== null && + tablePos !== null && + memberStmts.length === 0 && + definePropEntries.length === 0 && + liveExportTableFirst(sf, tablePos, esbuildMainTarget, exportTables) + ) + host.liveExportTable?.(filePath); + const lines: string[] = [""]; const hasAnyFallback = [...entries.values()].some((s) => s.kind === "any"); if (hasAnyFallback) { @@ -906,6 +1074,8 @@ export function rewriteBundlerCjsSyntax( const v = `__scriptc_e${hoistN++}`; hoists.push(`const ${v} = ${dropInteropDefault(spec.value!)};`); plainParts.push(`${keyOf(spec.name)}: ${v},`); + } else if (spec.kind === "getter") { + plainParts.push(`get ${keyOf(spec.name)}() { return ${dropInteropDefault(spec.value!)}; },`); } else { plainParts.push(`${keyOf(spec.name)}: __scriptc_any,`); } @@ -931,5 +1101,12 @@ export function rewriteBundlerCjsSyntax( lines.push( `module.exports = {${spreadParts.join("")}${plainParts.join("")}${esModulePart}${memberParts.join("")}};`, ); + /* Node's lexer skips accessors (and stops reading an object literal at + * the first one), so a canonical table with accessor entries hides + * names the original showed it (esbuild's dead annotation): the host + * keeps the original's lexer answer, so ES-module importers link + * exactly as they do against the original. */ + if ([...entries.values()].some((spec) => spec.kind === "getter")) + host.lexerVisibleNames?.(filePath, [...lexed.exports]); return text + lines.join("\n") + "\n"; } diff --git a/packages/compiler/src/frontend/npm-static-subtyping.ts b/packages/compiler/src/frontend/npm-static-subtyping.ts index 04c66a559..18d69d4c4 100644 --- a/packages/compiler/src/frontend/npm-static-subtyping.ts +++ b/packages/compiler/src/frontend/npm-static-subtyping.ts @@ -19,8 +19,8 @@ export function isNpmStaticSubclassArgument( symbol.declarations.some( (declaration) => (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)) && - /\.[cm]?js$/.test(declaration.getSourceFile().fileName) && - npmStaticPackageOfPath(declaration.getSourceFile().fileName) !== null, + /\.[cm]?js$/.test(ts.getSourceFileOfNode(declaration).fileName) && + npmStaticPackageOfPath(ts.getSourceFileOfNode(declaration).fileName) !== null, ) ); }; @@ -50,12 +50,12 @@ export function isNpmStaticSubclassArgument( }; let accepted = false; ts.walkPreorder(source, (node) => { - if (node.end < diagnostic.pos || node.getStart(source) > diagnostic.end) return "skip"; + if (node.end < diagnostic.pos || ts.tokenStartOfNode(node) > diagnostic.end) return "skip"; if (!ts.isCallExpression(node) && !ts.isNewExpression(node)) return undefined; const index = node.arguments?.nodes.findIndex( (argument) => - argument.getStart(source) === diagnostic.pos && argument.end === diagnostic.end, + ts.tokenStartOfNode(argument) === diagnostic.pos && argument.end === diagnostic.end, ) ?? -1; if (index < 0) return undefined; const parameter = checker.getResolvedSignature(node)?.getParameters()[index]; diff --git a/packages/compiler/src/frontend/npm-static.ts b/packages/compiler/src/frontend/npm-static.ts index 3c8fa22e9..ae8a4a8f4 100644 --- a/packages/compiler/src/frontend/npm-static.ts +++ b/packages/compiler/src/frontend/npm-static.ts @@ -13,7 +13,14 @@ import { registerWorkspacePackage, workspacePackageOfPath, } from "./workspace-registry.js"; -import { trackedExists, trackedReadFile, trackedRealpath } from "./input-tracker.js"; +import { + clearVirtualSources, + setVirtualSource, + trackedExists, + trackedReadFile, + trackedRealpath, +} from "./input-tracker.js"; +import { npmBuiltinAliasOf } from "./builtin-modules.js"; let activePackages: ReadonlySet = new Set(); let declarationOverloads: ReadonlyMap = new Map(); @@ -28,14 +35,38 @@ const offenders = new Map(); * the host probes the same file many times, and the rewrite parses. */ const rewriteCache = new Map(); +/** Rewritten files whose tail export table stands for a live getter table + * installed before the file could run another module's code (the rewrite + * certifies it; require-cycle admission consults it). */ +const liveExportFiles = new Set(); + +export function npmStaticLiveExportTable(path: string): boolean { + return liveExportFiles.has(path.split("\\").join("/")); +} + +/** Names Node's lexer detects in a rewritten file's original text that + * its rewrite spells only as accessors (the lexer skips those). */ +const lexerNames = new Map(); + +export function npmStaticLexerVisibleNames(path: string): readonly string[] { + return lexerNames.get(path.split("\\").join("/")) ?? []; +} + export function setNpmStaticPackages>(packages: T): void { - activePackages = new Set(packages); + // npm packages served as builtins (graceful-fs as fs) never compile + // their JavaScript, even when named. + activePackages = new Set([...packages].filter((name) => npmBuiltinAliasOf(name) === null)); declarationOverloads = new Map(); declarationProperties = new Map(); offenders.clear(); + fileDegrades.clear(); rewriteCache.clear(); + liveExportFiles.clear(); + lexerNames.clear(); + clearVirtualSources(); untypedPkgCache.clear(); realpathProbed.clear(); + aliasNames.clear(); } export function setNpmStaticDeclarationOverloads( @@ -81,6 +112,21 @@ export function npmStaticOffenders(): ReadonlyMap { return offenders; } +/** Files whose bundler interop the rewrite could not finish this attempt, + * with their package and reason. A file refuses its package only when + * the compiled program can load it (npmStaticLoadableFiles): a package is + * judged by what its imported subpaths reach, not by every file it ships. */ +const fileDegrades = new Map(); + +export function npmStaticFileDegrades(): ReadonlyMap { + return fileDegrades; +} + +function reportFileDegrade(path: string, pkg: string, reason: string): void { + const key = path.split("\\").join("/"); + if (!fileDegrades.has(key)) fileDegrades.set(key, { pkg, reason }); +} + /** The DefinitelyTyped name mangling ("@scope/pkg" → "scope__pkg") — the * @types twin that must hide alongside the package's own declarations. */ function mangledTypesName(name: string): string { @@ -135,9 +181,32 @@ function shadowTargetOf(path: string): { pkg: string; viaTypes: boolean } | null if (mangledTypesName(pkg) === mangled) return { pkg, viaTypes: true }; } } + // An npm alias (`"x-updated": "npm:x@6.3.0"`) installs the package under + // another directory name: its own manifest names the package. + const aliased = aliasedPackageName(`${norm.slice(0, idx)}/node_modules/${dirName}`); + if (aliased !== null && aliased !== dirName && activePackages.has(aliased)) + return { pkg: aliased, viaTypes: false }; return null; } +/** The "name" a node_modules directory's own package.json declares, or + * null (cached per load). */ +const aliasNames = new Map(); +function aliasedPackageName(pkgDir: string): string | null { + const hit = aliasNames.get(pkgDir); + if (hit !== undefined) return hit; + let name: string | null = null; + try { + const text = trackedReadFile(`${pkgDir}/package.json`); + const declared = text === null ? undefined : (JSON.parse(text) as { name?: unknown }).name; + if (typeof declared === "string") name = declared; + } catch { + name = null; + } + aliasNames.set(pkgDir, name); + return name; +} + /** The opted-in package.json transform, applied identically in BOTH * resolution worlds (the tsgo host's fs shadow and resolve.ts): * 1. every types claim goes — the top-level "types"/"typings" fields @@ -362,7 +431,36 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow if (hit !== undefined) return hit ?? undefined; let rewritten: string | null = null; try { - const original = trackedReadFile(path); + let original = trackedReadFile(path); + // A bundle's CommonJS chunks move to their own modules first, so + // every later pass (and this shadow, for each chunk) sees plain + // CommonJS module text. + const split = original === null ? null : services.splitCjsChunks(original, path); + if (split !== null && "degrade" in split) { + reportFileDegrade(path, target.pkg, split.degrade); + original = null; + } else if (split !== null) { + for (const chunk of split.chunks) setVirtualSource(chunk.path, chunk.source); + original = split.text; + } + // Then its lazily initialized ES modules become run-once closures. + const esm = original === null ? null : services.lowerEsmInits(original, path); + if (esm !== null && typeof esm === "object") { + reportFileDegrade(path, target.pkg, esm.degrade); + original = null; + } else if (esm !== null) { + original = esm; + } + // A module exporting one of its functions reaches its members + // through that function. + const fnExports = original === null ? null : services.cjsFunctionExports(original, path); + if (fnExports !== null) original = fnExports; + // A module that names its export object through a variable reaches + // its members through `exports`. + const exportsVar = original === null ? null : services.cjsExportsVar(original, path); + if (exportsVar !== null) original = exportsVar; + const respelled = + split !== null || esm !== null || fnExports !== null || exportsVar !== null; if (original !== null) { const namepaths = services.jsDocNamepaths(path, original); const source = namepaths ?? original; @@ -385,9 +483,11 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow classFields?.text ?? source, path, + (live) => liveExportFiles.add(live.split("\\").join("/")), + (file, names) => lexerNames.set(file.split("\\").join("/"), names), ); if (answer !== null && typeof answer === "object") { - reportNpmStaticOffender(target.pkg, answer.degrade); + reportFileDegrade(path, target.pkg, answer.degrade); } else { rewritten = answer ?? @@ -395,7 +495,8 @@ export function npmStaticFsShadow(services: FrontendServices): NpmStaticFsShadow propertyProjected?.text ?? findWidened?.text ?? classFields?.text ?? - namepaths; + namepaths ?? + (respelled ? original : null); } } } catch { @@ -434,10 +535,32 @@ function looksUnminified(source: string): boolean { return source.length / lines.length <= 200; } +/** A whitespace/syntax-minified bundler dist whose interop helpers keep + * their names (`var __toESM=…,__toCommonJS=mod=>…`): the recognizers + * verify those shapes and refuse the package precisely when they cannot, + * so the minification heuristic need not refuse it up front. */ +function namedBundlerHelpers(source: string): boolean { + return /\b__(?:toCommonJS|commonJS|toESM)\d*\s*=\s*(?:\(|[A-Za-z_$][\w$]*\s*=>)/.test(source); +} + function hasTransformMarkers(source: string): boolean { return TRANSFORM_MARKERS.some((m) => source.includes(m)); } +/** Why the declarations at `typesFile` cannot stand for the runtime + * package `pkg`, or null when they are its DefinitelyTyped twin + * (`@types/`). The declarations only check the importing + * program; the JavaScript compiles with its inferred types, and an + * inferred surface the program does not typecheck against refuses the + * package afterwards, whatever release the twin describes. */ +export function definitelyTypedMismatch(pkg: string, typesFile: string): string | null { + const owner = npmPackageNameOf(typesFile.split("\\").join("/")); + const twin = `@types/${mangledTypesName(pkg)}`; + return owner === twin + ? null + : `its declared types come from ${owner ?? typesFile}, which is not ${pkg}'s DefinitelyTyped package ${twin}`; +} + /** Auto-mode eligibility for one candidate package: `typesFile` is what * the ordinary (types-first) resolution answered for the import, * `jsEntry` what the js-only resolution answers. Returns null when @@ -446,17 +569,23 @@ export function npmStaticIneligibleReason( pkgName: string, typesFile: string, jsEntry: string | null, + /** A dependency another package's JavaScript imports: no declarations + * check that importer, so the package need not ship its own. */ + untypedDependency = false, ): string | null { - if (npmPackageNameOf(typesFile) !== pkgName) { - return "its declared types come from a third-party @types package, not the package itself"; - } - if (!/\.d\.(ts|mts|cts)$/.test(typesFile)) { - return "it ships no own .d.ts declaration surface"; + if (!untypedDependency) { + if (npmPackageNameOf(typesFile) !== pkgName) { + return "its declared types come from a third-party @types package, not the package itself"; + } + if (!/\.d\.(ts|mts|cts)$/.test(typesFile)) { + return "it ships no own .d.ts declaration surface"; + } } if (jsEntry === null) return "no runtime JS entry resolves"; const source = trackedReadFile(jsEntry); if (source === null) return `its runtime entry ${jsEntry} cannot be read`; - if (!looksUnminified(source)) return "its shipped JS looks minified"; + if (!looksUnminified(source) && !namedBundlerHelpers(source)) + return "its shipped JS looks minified"; if (hasTransformMarkers(source)) { return "its shipped JS carries build-transform markers (bundled/transpiled dist)"; } diff --git a/packages/compiler/src/frontend/pipeline.ts b/packages/compiler/src/frontend/pipeline.ts index 024c69d98..16c96247f 100644 --- a/packages/compiler/src/frontend/pipeline.ts +++ b/packages/compiler/src/frontend/pipeline.ts @@ -7,14 +7,18 @@ import { entryFunctionExports, type EntryExportInfo } from "./lib-exports.js"; import { entryContractFacts, type ContractFacts } from "./lib-contract.js"; import { canonicalBuiltinModule, + npmBuiltinAliasOf, checkPreflight, isNodeTypesPath, locOf, + npmStaticLoadableFiles, requiresOf, resolveNpmImport, type LoadResult, } from "./program.js"; import { + definitelyTypedMismatch, + npmStaticFileDegrades, npmStaticIneligibleReason, npmStaticOffenders, npmStaticPackageOfPath, @@ -195,6 +199,9 @@ function detectAutoPackages( // never npm candidates. Auto keeps its original path (the // @types/node answer skips them below), unchanged. if (mode === "lib" && canonicalBuiltinModule(spec) !== null) continue; + // npm packages served as builtins (graceful-fs as fs) never compile + // their JavaScript. + if (npmBuiltinAliasOf(spec) !== null) continue; // Explicit attempts name the runtime package. Types-first resolution // may instead identify its @types twin, which has no runtime to admit. if (judged?.has(packageNameOfSpecifier(spec))) continue; @@ -217,13 +224,26 @@ function detectAutoPackages( chosen.push(runtimePkg); continue; } + // Both answers compile the JavaScript with inferred types; the + // declarations only check the importer, and an inferred surface + // the program does not typecheck against still refuses the package + // below. A DefinitelyTyped twin qualifies; a package without + // declarations qualifies as a dependency of another package, whose + // JavaScript importer has no declarations to check. + const refusal = + npm === null + ? owner === null && !sf.fileName.includes("/node_modules/") + ? "it ships no own .d.ts declaration surface" + : null + : definitelyTypedMismatch(runtimePkg, npm.typesFile); + if (refusal === null) { + chosen.push(runtimePkg); + continue; + } statuses.push({ package: runtimePkg, status: "refused", - detail: - npm === null - ? "it ships no own .d.ts declaration surface" - : `its declared types come from the separate ${npm.packageName} package, not the package itself`, + detail: refusal, hint: npmStaticOptInHint(runtimePkg, owner), }); continue; @@ -254,6 +274,7 @@ function detectAutoPackages( pkg, typesFile, jsEntry !== null && isJsSourceFileName(jsEntry.typesFile) ? jsEntry.typesFile : null, + mode === "lib" && isJsSourceFileName(typesFile) && fromFile.includes("/node_modules/"), ); if (reason === null) chosen.push(pkg); else @@ -458,6 +479,15 @@ function loadFrontend( const typecheckRefused = new Set(); while (effective.size > 0) { const reasons = new Map(npmStaticOffenders()); + // Bundler interop the rewrite could not finish refuses its package + // only where the compiled program can load the file. + if (npmStaticFileDegrades().size > 0) { + const loadable = npmStaticLoadableFiles(load.program, load.moduleOrder); + for (const [file, { pkg, reason }] of npmStaticFileDegrades()) { + if (!reasons.has(pkg) && (loadable === null || loadable.has(file))) + reasons.set(pkg, reason); + } + } for (const d of preflight) { const pkg = npmStaticPackageOfPath(d.loc.file); if (pkg !== null && !reasons.has(pkg)) diff --git a/packages/compiler/src/frontend/program.ts b/packages/compiler/src/frontend/program.ts index d7c2ba66a..2e75542d7 100644 --- a/packages/compiler/src/frontend/program.ts +++ b/packages/compiler/src/frontend/program.ts @@ -68,6 +68,8 @@ import { isNpmStaticPackage, npmStaticActive, npmStaticFsShadow, + npmStaticLexerVisibleNames, + npmStaticLiveExportTable, npmStaticPackageOfPath, reportNpmStaticOffender, setNpmStaticDeclarationOverloads, @@ -99,8 +101,10 @@ import { import { builtinDefaultImportModule, canonicalBuiltinModule, + npmBuiltinAliasOf, SUPPORTED_NODE_MODULES, unsupportedModuleFeatureOf, + TEMPORAL_GLOBAL, } from "./builtin-modules.js"; import { clearWorkspacePackages, @@ -711,6 +715,42 @@ function entryPackageProgramRoots7( return [...roots].sort(); } +/** TS 7's ESNext temporal declarations: a lib slice scriptc adds only for + * programs whose sources name the global (programNamesTemporal7). */ +const TEMPORAL_LIB = "lib.esnext.temporal.d.ts"; + +/** True when a program source (user code or compiled npm JavaScript) + * contains the temporal global's name as a whole word. A cheap syntactic + * scan of the implementation files root discovery already materialized; a + * mention in a comment or string also counts, which only adds the lib + * slice. */ +function programNamesTemporal7(program: ts.Program): boolean { + for (const sf of program.getImplementationSourceFiles()) { + const text = sf.text; + for ( + let at = text.indexOf(TEMPORAL_GLOBAL); + at >= 0; + at = text.indexOf(TEMPORAL_GLOBAL, at + 8) + ) { + if (!isIdentifierCharAt(text, at - 1) && !isIdentifierCharAt(text, at + 8)) return true; + } + } + return false; +} + +function isIdentifierCharAt(text: string, index: number): boolean { + if (index < 0 || index >= text.length) return false; + const c = text.charCodeAt(index); + return ( + (c >= 48 && c <= 57) || + (c >= 65 && c <= 90) || + (c >= 97 && c <= 122) || + c === 36 || + c === 95 || + c > 127 + ); +} + /** "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" { @@ -790,6 +830,20 @@ function loadProgram7( program = host.createProgram([...programRoots, overridesDtsPath()], options); if (pass === 31) throw new Error("static program-root discovery did not converge"); } + // Node 24 ships no temporal global, but TS 7 types it in an ESNext + // lib slice. Programs that name it (feature probes such as smol-toml's + // typeof check) get that slice so they type-check, and lower to Node's + // answers (typeof "undefined", ReferenceError on use). Every other + // program keeps the identical lib set: the slice would add globals to + // the checker's world for everyone. + if (programNamesTemporal7(program)) { + program.dispose(); + options = { + ...options, + lib: [...((options.lib as string[] | undefined) ?? []), TEMPORAL_LIB], + }; + program = host.createProgram([...programRoots, overridesDtsPath()], options); + } const entry = program.getSourceFile(entryPath); if (!entry) throw new Error(`could not load ${entryPath}`); const externalTypeSpecifiersByFile = externalTypeFileClosure7(program, externalTypes); @@ -1182,7 +1236,7 @@ export function isEsModuleStamp(expr: ts.Expression): boolean { function pureCjsExport7(program: ts.Program, stmt: ts.Statement): boolean { const cjs = cjsExportAssignmentOf(stmt); if (cjs === null) return false; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); const plainMember = (left: ts.Expression): boolean => { if (!ts.isPropertyAccessExpression(left) || left.questionDotToken) return false; // A replacement export can have setters or non-writable function @@ -1237,7 +1291,7 @@ function sourceBinding7(program: ts.Program, ident: ts.Identifier): boolean { return ( symbol !== undefined && symbol.declarations.some( - (decl) => !decl.getSourceFile().isDeclarationFile && !ts.isSourceFile(decl), + (decl) => !ts.getSourceFileOfNode(decl).isDeclarationFile && !ts.isSourceFile(decl), ) ); } @@ -1264,7 +1318,7 @@ function purePrefixStmt7(program: ts.Program, s: ts.Statement): boolean { if (ts.isEmptyStatement(s) || ts.isFunctionDeclaration(s)) return true; if (ts.isExpressionStatement(s) && ts.isStringLiteral(s.expression)) return true; // directive if (isRequireStatement7(s)) return true; - if (isCjsJsFile7(s.getSourceFile(), program)) { + if (isCjsJsFile7(ts.getSourceFileOfNode(s), program)) { if (ts.isExpressionStatement(s) && isEsModuleStamp(s.expression)) { const call = s.expression as ts.CallExpression; const object = (call.expression as ts.PropertyAccessExpression).expression as ts.Identifier; @@ -1391,7 +1445,7 @@ function requireEarlyReference7( if (cached !== undefined) return cached; let found: ts.Identifier | null = null; const stmt = decl.parent?.parent; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); if (stmt !== undefined && ts.isVariableStatement(stmt) && ts.isSourceFile(stmt.parent)) { const stmts = sf.statements.nodes; const k = stmts.indexOf(stmt); @@ -1438,6 +1492,8 @@ export function requireBindingEarlyUse7( k: number, decl: ts.VariableDeclaration, precedingDecls: readonly ts.VariableDeclaration[], + /** A reference of the binding whose early evaluation is known exact. */ + exactEarlyRead?: (reference: ts.Identifier) => boolean, ): ts.Identifier | null { const checker = program.getTypeChecker(); const bindings = new Set(); @@ -1669,6 +1725,7 @@ export function requireBindingEarlyUse7( const sym = symbolOf(n); if (sym === undefined) return undefined; if (bindings.has(sym)) { + if (exactEarlyRead?.(n) === true) return undefined; hit = n; return "stop"; } @@ -1964,7 +2021,7 @@ function isCreateRequireImport7(program: ts.Program, ident: ts.Identifier): bool if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { const target = checker.getAliasedSymbol(symbol); const targetDecl = target.declarations[0]; - if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) { + if (targetDecl !== undefined && ts.getSourceFileOfNode(targetDecl).isDeclarationFile) { for ( let p: ts.Node | undefined = targetDecl.parent; p !== undefined && !ts.isSourceFile(p); @@ -1972,7 +2029,8 @@ function isCreateRequireImport7(program: ts.Program, ident: ts.Identifier): bool ) { if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) { return ( - canonicalBuiltinModule(p.name.text) === "module" && target.name === "createRequire" + canonicalBuiltinModule(p.name.text) === "module" && + ts.symbolName(target) === "createRequire" ); } } @@ -2056,7 +2114,7 @@ function stableCreateRequireBindingReason7( return "its createRequire binding is redeclared"; if ((ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Export) !== 0) return "its createRequire result escapes through an exported binding"; - const sf = decl.getSourceFile(); + const sf = ts.getSourceFileOfNode(decl); const refs = identifierOccurrences7(sf, decl.name.text); checker.prefetchSymbolNodesExact(refs); for (const ref of refs) { @@ -2123,7 +2181,7 @@ function npmStaticModuleImportReason7( } if (!bindings || !ts.isNamedImports(bindings)) return null; const checker = program.getTypeChecker(); - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); for (const specifier of bindings.elements.nodes) { if ((specifier.propertyName?.text ?? specifier.name.text) !== "createRequire") continue; const imported = checker.getSymbolAtLocation(specifier.name); @@ -2203,7 +2261,7 @@ function requireCallBindingKind7(program: ts.Program, node: ts.Node): RequireCal const symbol = checker.getSymbolAtLocation(call.expression); if (symbol === undefined) return "ambient"; const decls = symbol.declarations; - if (decls.every((d) => d.getSourceFile().isDeclarationFile)) return "ambient"; + if (decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) return "ambient"; return isCreateRequireBinding7(program, call.expression) ? "createRequire" : "source"; } @@ -2358,15 +2416,15 @@ function selfImportTdzFences7( // are initialized. Hoisted functions have none. const tdzEndOf = (aliased: ts.Symbol): number | null => { const d = aliased.valueDeclaration ?? aliased.declarations[0]; - if (d === undefined || d.getSourceFile() !== sf) return null; + if (d === undefined || ts.getSourceFileOfNode(d) !== sf) return null; if (ts.isFunctionDeclaration(d)) return null; - return d.getEnd(); + return d.end; }; const fence = (ref: ts.Identifier, name: string): void => { diags.push( unsupportedDiag( "SC1030", - { file: sf.fileName, start: ref.getStart(sf), end: ref.getEnd() }, + { file: sf.fileName, start: ts.tokenStartOfNode(ref), end: ref.end }, `the self-import binding '${name}' read above its declaration (the import aliases this module's OWN binding — Node initializes it when the declaration executes, so this read throws ReferenceError; move the read below the declaration)`, ), ); @@ -2382,7 +2440,7 @@ function selfImportTdzFences7( ts.isIdentifier(node) && node !== bindingName && node.text === bindingName.text && - node.getStart(sf) < end && + ts.tokenStartOfNode(node) < end && !insideFunction(node) && checker.getSymbolAtLocation(node) === sym ) { @@ -2415,7 +2473,7 @@ function selfImportTdzFences7( const memberSym = checker.getSymbolAtLocation(node.name); if (memberSym !== undefined) { const end = tdzEndOf(memberSym); - if (end !== null && node.getStart(sf) < end) + if (end !== null && ts.tokenStartOfNode(node) < end) fence(node.expression, `${nsName.text}.${node.name.text}`); } } @@ -2615,7 +2673,7 @@ function topLevelInertness7(program: ts.Program): (stmt: ts.Statement) => ts.Nod if (sym === undefined) return false; if (sym.flags & ts.SymbolFlags.Alias) sym = checker.getAliasedSymbol(sym); const decls = sym.declarations; - return decls.length > 0 && decls.every((d) => d.getSourceFile().isDeclarationFile); + return decls.length > 0 && decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile); }; const hasDecorator = (n: ts.Node): boolean => ((n as { modifiers?: ts.NodeArray }).modifiers?.nodes ?? []).some( @@ -2889,8 +2947,8 @@ export function makeCycleAdmission( } }; const lineOf = (node: ts.Node): string => { - const nsf = node.getSourceFile(); - return `${nsf.fileName}:${ts.getLineAndCharacterOfPosition(nsf, node.getStart(nsf)).line + 1}`; + const nsf = ts.getSourceFileOfNode(node); + return `${nsf.fileName}:${ts.getLineAndCharacterOfPosition(nsf, ts.tokenStartOfNode(node)).line + 1}`; }; // Cluster verdict memo (keyed by the component array identity): true // when every member is an ES module passing the inert-top-level bar. @@ -3119,6 +3177,16 @@ function cjsCycleRefusal7( // anything else can change what a module of the cycle observes. let stray: ts.Node | null = null; const settled = new Set(stmts.slice(0, firstEntry).filter(exportStatement)); + // A bundler export table the rewrite certified (npmStaticLiveExportTable) + // stands for the live getter object the original installed before its + // first require: the tail table settles as if it ran there. + const tail = stmts[stmts.length - 1]; + if ( + tail !== undefined && + npmStaticLiveExportTable(m.fileName) && + cjsExportAssignmentOf(tail)?.kind === "table" + ) + settled.add(tail); ts.walkPreorder(m, (n) => { if (stray !== null) return "stop"; if (ts.isStatement(n) && settled.has(n)) return "skip"; @@ -3171,7 +3239,9 @@ function cjsCycleRefusal7( if (req.decl === null) continue; const dep = resolveImport7(program, m, req.spec, "require"); if (dep === null || !members.has(dep)) continue; - const reached = requireBindingEarlyUse7(program, m, stmts.length, req.decl, []); + const reached = requireBindingEarlyUse7(program, m, stmts.length, req.decl, [], (id) => + functionExportRead7(id, dep), + ); if (reached !== null) { return `the top-level code of ${m.fileName} can use its binding '${reached.text}' of a module of the cycle (${lineOf(reached)}) while the cycle initializes`; } @@ -3181,6 +3251,161 @@ function cjsCycleRefusal7( return null; } +/** True when `use` initializes a top-level const whose every other + * reference is a value of an export statement (`module.exports = { … }` + * or `exports.n = …`). */ +function publishedAlias7(use: ts.Node, decl: ts.Node | undefined): boolean { + if ( + decl === undefined || + !ts.isVariableDeclaration(decl) || + decl.initializer !== use || + !ts.isIdentifier(decl.name) + ) + return false; + const list = decl.parent; + const stmt = list?.parent; + const sf = stmt?.parent; + if ( + list === undefined || + (list.flags & ts.NodeFlags.Const) === 0 || + stmt === undefined || + !ts.isVariableStatement(stmt) || + sf === undefined || + !ts.isSourceFile(sf) + ) + return false; + const alias = decl.name; + const published = new Set(); + for (const st of sf.statements.nodes) { + const cjs = cjsExportAssignmentOf(st); + if (cjs === null) continue; + if (cjs.kind === "member") published.add(cjs.value); + else if (cjs.obj !== null) { + for (const p of cjs.obj.properties.nodes) { + if (ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name)) + published.add(p.initializer); + else if (ts.isShorthandPropertyAssignment(p)) published.add(p.name); + } + } + } + let kept = false; + ts.walkPreorder(sf, (n) => { + if (kept) return "stop"; + if (!ts.isIdentifier(n) || n === alias || n.text !== alias.text) return undefined; + const p = n.parent; + if ( + p !== undefined && + ((ts.isPropertyAccessExpression(p) && p.name === n) || + (ts.isPropertyAssignment(p) && p.name === n)) + ) + return undefined; + if (!published.has(n)) kept = true; + return undefined; + }); + return !kept; +} + +/** True for a plain read `binding.name` (not a call, not a write, not a + * value that is stored or passed on) of the cycle member `dep` whose + * export `name` is a top-level function declaration nothing reassigns. + * Every member settles its exports before entering the cycle, so Node + * reads that same function whenever the read runs, and the compiled read + * resolves to the declaration itself. */ +function functionExportRead7(id: ts.Identifier, dep: ts.SourceFile): boolean { + const access = id.parent; + if (!ts.isPropertyAccessExpression(access) || access.expression !== id) return false; + // The value the read yields: through parentheses and the right operand + // of a comma, so `(0, binding.name)(...)` is the call it spells. + let use: ts.Node = access; + for (;;) { + const p = use.parent; + if (ts.isParenthesizedExpression(p)) use = p; + else if ( + ts.isBinaryExpression(p) && + p.operatorToken.kind === ts.SyntaxKind.CommaToken && + p.right === use + ) + use = p; + else break; + } + const parent = use.parent; + // Only a use that neither runs the function nor keeps it is a read: a + // stored alias (`const f = binding.name`, `{ f: binding.name }`) or an + // argument can be called while the cycle initializes. A top-level const + // that only publishes it (`module.exports = { name: f }`, the static + // rewrite's re-export spelling) runs nothing: a module that reads the + // export back checks that read itself. + const observes = + publishedAlias7(use, parent) || + ts.isExpressionStatement(parent) || + ts.isTypeOfExpression(parent) || + ts.isVoidExpression(parent) || + (ts.isPrefixUnaryExpression(parent) && parent.operator === ts.SyntaxKind.ExclamationToken) || + (ts.isBinaryExpression(parent) && + (parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken || + parent.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken)) || + ((ts.isIfStatement(parent) || ts.isWhileStatement(parent) || ts.isDoStatement(parent)) && + parent.expression === use) || + (ts.isConditionalExpression(parent) && parent.condition === use); + if (!observes) return false; + const name = access.name.text; + // The exported value's identifier: a table entry or a member export. + const values: ts.Expression[] = []; + for (const stmt of dep.statements.nodes) { + if (!ts.isExpressionStatement(stmt) || !ts.isBinaryExpression(stmt.expression)) continue; + const e = stmt.expression; + if (e.operatorToken.kind !== ts.SyntaxKind.EqualsToken) continue; + if (isModuleExportsAccess(e.left)) { + if (!ts.isObjectLiteralExpression(e.right)) return false; + for (const p of e.right.properties.nodes) { + if (ts.isSpreadAssignment(p)) return false; + if (ts.isShorthandPropertyAssignment(p) && p.name.text === name) + values.push(p.name as ts.Identifier); + else if ( + ts.isPropertyAssignment(p) && + (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) && + p.name.text === name + ) + values.push(p.initializer); + } + } else if ( + ts.isPropertyAccessExpression(e.left) && + e.left.name.text === name && + ((ts.isIdentifier(e.left.expression) && e.left.expression.text === "exports") || + isModuleExportsAccess(e.left.expression)) + ) { + values.push(e.right); + } + } + const value = values.length === 1 ? values[0]! : null; + if (value === null || !ts.isIdentifier(value)) return false; + const fnName = value.text; + if ( + !dep.statements.nodes.some( + (st) => ts.isFunctionDeclaration(st) && st.name?.text === fnName && st.body !== undefined, + ) + ) + return false; + let rebound = false; + ts.walkPreorder(dep, (n) => { + if (rebound) return "stop"; + if ( + (ts.isBinaryExpression(n) && + ts.isIdentifier(n.left) && + n.left.text === fnName && + n.operatorToken.kind >= ts.SyntaxKind.FirstAssignment && + n.operatorToken.kind <= ts.SyntaxKind.LastAssignment) || + (ts.isVariableDeclaration(n) && ts.isIdentifier(n.name) && n.name.text === fnName) || + (ts.isFunctionDeclaration(n) && n.name?.text === fnName && !ts.isSourceFile(n.parent)) + ) + rebound = true; + return undefined; + }); + return !rebound; +} + /* ── exact cyclic initialization ───────────────────────────────────────── * Cycles that fail the declaration-only rule still evaluate exactly like * Node when their shape is representable: the guarded %init calls already @@ -3230,7 +3455,7 @@ function exactCycleRefusal7( const targetOf = (stmt: ts.ImportDeclaration | ts.ExportDeclaration): ts.SourceFile | null => { const spec = stmt.moduleSpecifier; if (spec === undefined || !ts.isStringLiteral(spec)) return null; - const sf = stmt.getSourceFile(); + const sf = ts.getSourceFileOfNode(stmt); return resolveImport7(program, sf, spec.text) ?? npmStaticDepSf7(program, sf, spec.text); }; for (const m of comp) { @@ -3464,8 +3689,8 @@ function computeCycleEarlyBindings7( module: m, exposed, rank, - start: stmt.getStart(m), - end: stmt.getEnd(), + start: ts.tokenStartOfNode(stmt), + end: stmt.end, reference: null, folded: null, }); @@ -3510,13 +3735,13 @@ function computeCycleEarlyBindings7( if ( c.kind === "class" && sf === c.module && - ref.getStart(sf) >= c.start && - ref.getEnd() <= c.end + ts.tokenStartOfNode(ref) >= c.start && + ref.end <= c.end ) continue; let early: boolean; if (inDeferredPosition7(ref)) early = c.exposed; - else if (sf === c.module) early = ref.getStart(sf) < c.start; + else if (sf === c.module) early = ts.tokenStartOfNode(ref) < c.start; else if (!memberSet.has(sf) || !rankOf.has(sf)) early = false; else early = !known || rankOf.get(sf)! < c.rank; if (early) c.reference = ref; @@ -3655,7 +3880,7 @@ function moduleEarlyBindingsOf7( store(stmt.name, [stmt]); } else if (ts.isClassDeclaration(stmt) && stmt.name !== undefined) { store(stmt.name, [stmt]); - candidates.push({ name: stmt.name, index, start: stmt.getStart(sf), cls: stmt }); + candidates.push({ name: stmt.name, index, start: ts.tokenStartOfNode(stmt), cls: stmt }); candidateNames.add(stmt.name.text); } else if (ts.isVariableStatement(stmt)) { const lexical = (stmt.declarationList.flags & (ts.NodeFlags.Let | ts.NodeFlags.Const)) !== 0; @@ -3667,7 +3892,7 @@ function moduleEarlyBindingsOf7( } if (!lexical) continue; for (const name of boundNames7(d.name)) { - candidates.push({ name, index, start: stmt.getStart(sf) }); + candidates.push({ name, index, start: ts.tokenStartOfNode(stmt) }); candidateNames.add(name.text); } } @@ -3759,9 +3984,10 @@ function moduleEarlyBindingsOf7( c !== undefined && node !== c.name && !reachedRefs.has(symbol) && - !(c.cls !== undefined && node.getStart(sf) >= c.cls.pos && node.end <= c.cls.end) + !(c.cls !== undefined && ts.tokenStartOfNode(node) >= c.cls.pos && node.end <= c.cls.end) ) { - if (inDeferredPosition7(node) || node.getStart(sf) < c.start) reachedRefs.set(symbol, node); + if (inDeferredPosition7(node) || ts.tokenStartOfNode(node) < c.start) + reachedRefs.set(symbol, node); } for (const unit of unitsBySymbol.get(symbol) ?? []) { if (reachedUnits.has(unit)) continue; @@ -3780,8 +4006,8 @@ function moduleEarlyBindingsOf7( const report = (declaration: ts.Node): void => { while ( next < candidates.length && - candidates[next]!.name.getStart(sf) >= declaration.getStart(sf) && - candidates[next]!.name.getEnd() <= declaration.getEnd() + ts.tokenStartOfNode(candidates[next]!.name) >= ts.tokenStartOfNode(declaration) && + candidates[next]!.name.end <= declaration.end ) { const c = candidates[next++]!; const symbol = checker.getSymbolAtLocation(c.name); @@ -3841,7 +4067,7 @@ function returnsOnlyClosuresOver7( const decls = symbol === undefined ? [] : symbol.declarations; if (decls.length !== 1) return false; const decl = decls[0]!; - if (decl.getSourceFile().isDeclarationFile) return false; + if (ts.getSourceFileOfNode(decl).isDeclarationFile) return false; let fn: ts.Node | undefined; if (ts.isFunctionDeclaration(decl)) fn = decl; else if (ts.isVariableDeclaration(decl) && decl.initializer !== undefined) { @@ -3962,12 +4188,13 @@ function laterJobCallee7(checker: ts.TypeChecker, callee: ts.Expression): boolea } if (symbol === undefined) return false; const decls = symbol.declarations; - if (decls.length === 0 || !decls.every((d) => d.getSourceFile().isDeclarationFile)) return false; + if (decls.length === 0 || !decls.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile)) + return false; if (LATER_JOB_BUILTINS7.has(name.text)) return true; // The JavaScript standard library's callbacks mostly run synchronously; // package declarations describe user code. return decls.every((d) => { - const file = d.getSourceFile().fileName; + const file = ts.getSourceFileOfNode(d).fileName; return isNodeTypesPath(file) || file === fallbackDtsPath(); }); } @@ -4110,8 +4337,8 @@ function resolveNpmImport7( } function locOf7(node: ts.Node): { file: string; start: number; end: number } { - const sf = node.getSourceFile(); - return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() }; + const sf = ts.getSourceFileOfNode(node); + return { file: sf.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } /** The program file holding an opted-in package's resolved runtime JS: @@ -4643,7 +4870,7 @@ function preflight7(load: LoadResult): { } if (reDep && !reDep.fileName.endsWith(".json")) { deps.push({ dep: reDep, stmt }); - depPositions.get(sf)!.push({ dep: reDep, pos: stmt.getStart(sf) }); + depPositions.get(sf)!.push({ dep: reDep, pos: ts.tokenStartOfNode(stmt) }); } continue; } @@ -4691,7 +4918,7 @@ function preflight7(load: LoadResult): { refusals.get(sf)!.push({ crash: { message, className, loc: locOf7(stmt) }, fallback: unsupportedDiag("SC1010", locOf7(stmt), fallbackFeature), - pos: stmt.getStart(sf), + pos: ts.tokenStartOfNode(stmt), }); }; // Project modules win before npm fallback: this is the source graph @@ -4904,7 +5131,7 @@ function preflight7(load: LoadResult): { } if (dep && !isJson) { deps.push({ dep, stmt }); - depPositions.get(sf)!.push({ dep, pos: stmt.getStart(sf) }); + depPositions.get(sf)!.push({ dep, pos: ts.tokenStartOfNode(stmt) }); } } { @@ -4944,7 +5171,11 @@ function preflight7(load: LoadResult): { for (let k = 0; k < stmts.length; k++) { const stmt = stmts[k]!; for (const req of requiresOf7(stmt)) { - const loc = { file: sf.fileName, start: req.node.getStart(sf), end: req.node.getEnd() }; + const loc = { + file: sf.fileName, + start: ts.tokenStartOfNode(req.node), + end: req.node.end, + }; const bindingKind = requireCallBindingKind7(program, req.node); // TypeScript source files join this scan only through the // canonical createRequire bridge. Their ordinary require-shaped @@ -5036,6 +5267,9 @@ function preflight7(load: LoadResult): { continue; } } + // An npm package served as a builtin (graceful-fs as fs) is never + // a program-module edge, even when --npm-static names it. + if (npmBuiltinAliasOf(req.spec) !== null) continue; const isRelative = isRelativeSpecifier(req.spec); let dep = resolveImport7(program, sf, req.spec, "require"); if (dep === null && !isRelative) { @@ -5087,7 +5321,7 @@ function preflight7(load: LoadResult): { ); for (const call of nestedBareRequires) { const spec = requireSpecOf7(call)!; - const loc = { file: sf.fileName, start: call.getStart(sf), end: call.getEnd() }; + const loc = { file: sf.fileName, start: ts.tokenStartOfNode(call), end: call.end }; const bindingKind = requireCallBindingKind7(program, call); if (!jsRequireFile && bindingKind !== "createRequire") continue; if (isNodeEsmFile7(sf, program)) { @@ -5099,6 +5333,7 @@ function preflight7(load: LoadResult): { if (load.externalTypes.has(spec)) { continue; } + if (npmBuiltinAliasOf(spec) !== null) continue; const projectDep = resolveImport7(program, sf, spec, "require"); if (projectDep !== null) { // A bare require of a JSON document only parses it: nothing to @@ -5349,12 +5584,18 @@ function cjsNamedImportLinkCheck( npmStaticDepSf7(program, from, spec, "require"); return dep !== null && isCjsJsFile7(dep, program) ? dep : null; }; + // A bundler rewrite spells some lexer-visible names as accessors. + const lexWithAccessorNames = (sf: ts.SourceFile): ReturnType => { + const lexed = cjsLexedExportsOfFile(sf); + for (const name of npmStaticLexerVisibleNames(sf.fileName)) lexed.exports.add(name); + return lexed; + }; const visible = (dep: ts.SourceFile, name: string): boolean => // `default` is the module.exports binding itself — always provided. // The program already owns native source ASTs; reuse them without // starting another syntax session or reparsing their source text. name === "default" || - cjsVisibleNames(dep, cjsLexedExportsOfFile, resolveCjsDep, lexMemo).has(name); + cjsVisibleNames(dep, lexWithAccessorNames, resolveCjsDep, lexMemo).has(name); /** The statement's resolved LOCAL CommonJS dependency, when it is an * import/re-export from one. */ @@ -5496,7 +5737,7 @@ function cjsNamedImportLinkCheck( // swaps ` as ` for `: ` — reproduced exactly, quirks included // (multi-line import statements simply lose the hint). const text = bad.sf.text; - const pos = bad.nameNode.getStart(bad.sf); + const pos = ts.tokenStartOfNode(bad.nameNode); const lineStart = text.lastIndexOf("\n", pos - 1) + 1; const lineEnd = text.indexOf("\n", pos); let line = text.slice(lineStart, lineEnd < 0 ? text.length : lineEnd); @@ -5810,7 +6051,7 @@ function earlierLinkCrash( if (!element.isTypeOnly) { entries.push({ local: element.name.text, - start: (element.propertyName ?? element.name).getStart(sf), + start: ts.tokenStartOfNode(element.propertyName ?? element.name), kind: 0, }); } @@ -5834,7 +6075,7 @@ function earlierLinkCrash( for (const element of exportClause.elements.nodes) { if ( !element.isTypeOnly && - (element.propertyName ?? element.name).getStart(sf) === crash.loc.start + ts.tokenStartOfNode(element.propertyName ?? element.name) === crash.loc.start ) { return [1, reexport]; } @@ -5868,6 +6109,7 @@ export { ambientDtsPath, fallbackDtsPath, isNodeTypesPath, overridesDtsPath } fr export { builtinDefaultImportModule, canonicalBuiltinModule, + npmBuiltinAliasOf, SUPPORTED_BUILTIN_MODULES, } from "./builtin-modules.js"; @@ -5899,7 +6141,11 @@ export function isJsSourceFile(sf: ts.SourceFile): boolean { export function isCheckedJsSourceFile(sf: ts.SourceFile, projectCheckJs: boolean): boolean { if (!isJsSourceFile(sf) || isNodeModulesPath(sf.fileName)) return false; let checked = projectCheckJs; - const head = sf.text.slice(0, sf.statements.nodes[0]?.getStart(sf) ?? sf.text.length); + const head = sf.text.slice( + 0, + (sf.statements.nodes.length === 0 ? undefined : ts.tokenStartOfNode(sf.statements.nodes[0]!)) ?? + sf.text.length, + ); for (const line of head.split("\n")) { const directive = /^\s*\/\/\s*@ts-(no)?check\b/.exec(line); if (directive) checked = directive[1] === undefined; @@ -5930,6 +6176,58 @@ export function orderedImportsOf( return out; } +/** The files a compiled program can load: the module order closed over + * every literal require() and import() call it contains (deferred and + * nested ones included) and, for opted-in package JavaScript, over every + * import and export declaration as well. A package file outside this set + * is reachable only through types, so whatever it spells cannot affect + * the compiled program. Null when a reachable file loads a computed + * specifier: then any file may load. */ +export function npmStaticLoadableFiles( + program: ts.Program, + moduleOrder: readonly ts.SourceFile[], +): Set | null { + const seen = new Set(); + const work: ts.SourceFile[] = []; + const add = (sf: ts.SourceFile | null | undefined): void => { + if (sf === null || sf === undefined || seen.has(sf) || sf.isDeclarationFile) return; + seen.add(sf); + work.push(sf); + }; + for (const sf of moduleOrder) add(sf); + let computed = false; + while (work.length > 0 && !computed) { + const sf = work.pop()!; + const packageJs = + npmStaticPackageOfPath(sf.fileName) !== null && isJsSourceFileName(sf.fileName); + const follow = (spec: string, kind: "import" | "require"): void => { + if (canonicalBuiltinModule(spec) !== null) return; + add(resolveImport7(program, sf, spec, kind) ?? npmStaticDepSf7(program, sf, spec, kind)); + }; + ts.walkPreorder(sf, (n) => { + if (computed) return "stop"; + if (ts.isCallExpression(n)) { + const isImport = n.expression.kind === ts.SyntaxKind.ImportKeyword; + const isRequire = ts.isIdentifier(n.expression) && n.expression.text === "require"; + if (!isImport && !isRequire) return undefined; + const arg = n.arguments.nodes[0]; + if (arg !== undefined && ts.isStringLiteralLike(arg)) + follow(arg.text, isImport ? "import" : "require"); + else if (packageJs || isImport) computed = true; + } else if ( + packageJs && + (ts.isImportDeclaration(n) || ts.isExportDeclaration(n)) && + n.moduleSpecifier !== undefined && + ts.isStringLiteral(n.moduleSpecifier) + ) { + follow(n.moduleSpecifier.text, "import"); + } + return undefined; + }); + } + return computed ? null : new Set([...seen].map((sf) => sf.fileName.split("\\").join("/"))); +} + /** The program source file a bare specifier reaches when it names an * opted-in --npm-static package (the shipped-JS entry preflight admitted * as a module edge), else null. No offender reporting here — preflight @@ -5967,10 +6265,15 @@ export function isModuleExportsAccess(expr: ts.Expression): expr is ts.PropertyA * - member: `exports.f = ` / `module.exports.f = `. * Null for everything else. Callers gate on top-level position in a JS * module file — these forms anywhere else keep their generic fences. */ -export function cjsExportAssignmentOf( - stmt: ts.Statement, -): - | { kind: "table"; obj: ts.ObjectLiteralExpression | null; expr: ts.BinaryExpression } +export function cjsExportAssignmentOf(stmt: ts.Statement): + | { + kind: "table"; + obj: ts.ObjectLiteralExpression | null; + expr: ts.BinaryExpression; + /** `exports = module.exports = value`: the `exports` binding names + * the new value too, so later `exports.f =` members attach to it. */ + rebindsExports?: true; + } | { kind: "member"; name: ts.MemberName; value: ts.Expression; expr: ts.BinaryExpression } | null { if (!ts.isExpressionStatement(stmt)) return null; @@ -5979,6 +6282,20 @@ export function cjsExportAssignmentOf( if (isModuleExportsAccess(e.left)) { return { kind: "table", obj: ts.isObjectLiteralExpression(e.right) ? e.right : null, expr: e }; } + if (ts.isIdentifier(e.left) && e.left.text === "exports") { + let inner: ts.Expression = e.right; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + if ( + ts.isBinaryExpression(inner) && + inner.operatorToken.kind === ts.SyntaxKind.EqualsToken && + isModuleExportsAccess(inner.left) && + // `exports = module.exports = {}` is the fresh-table reset, which the + // export walk handles on its own. + !ts.isObjectLiteralExpression(inner.right) + ) { + return { kind: "table", obj: null, expr: inner, rebindsExports: true }; + } + } if (ts.isPropertyAccessExpression(e.left) && !e.left.questionDotToken) { const recv = e.left.expression; const isExports = ts.isIdentifier(recv) && recv.text === "exports"; @@ -6027,8 +6344,8 @@ export function cjsVoidInitPreambleReals( tail = tail.right; } if (!isVoid(tail)) return null; - const sf = cjs.expr.getSourceFile(); - const myPos = cjs.expr.getStart(sf); + const sf = ts.getSourceFileOfNode(cjs.expr); + const myPos = ts.tokenStartOfNode(cjs.expr); const reals = new Map< string, Extract, { kind: "member" }> @@ -6041,7 +6358,7 @@ export function cjsVoidInitPreambleReals( other?.kind === "member" && ts.isIdentifier(other.name) && other.name.text === name && - other.expr.getStart(sf) > myPos && + ts.tokenStartOfNode(other.expr) > myPos && !isVoid(other.value), ); if (real?.kind !== "member") return null; @@ -6110,6 +6427,8 @@ export function cjsExportDiscardReason(stmt: ts.Statement): string | null { return "export members attached before a 'module.exports =' assignment (Node replaces the export object — importers never see this member; attach it to module.exports AFTER the table)"; } const left = mine.expr.left as ts.PropertyAccessExpression; + const last = cjsExportAssignmentOf(stmts[lastTable]!); + if (last?.kind === "table" && last.rebindsExports === true) return null; return ts.isIdentifier(left.expression) && left.expression.text === "exports" ? "'exports.f =' after a 'module.exports =' assignment ('exports' still references the replaced object — Node discards this member; write module.exports.f instead)" : null; @@ -6197,7 +6516,7 @@ export function isCjsExportTableLiteral(obj: ts.ObjectLiteralExpression): boolea } const decl = obj.parent; if (!ts.isVariableDeclaration(decl) || decl.initializer !== obj) return false; - const sf = obj.getSourceFile(); + const sf = ts.getSourceFileOfNode(obj); if (!isJsSourceFile(sf)) return false; for (const stmt of sf.statements.nodes) { const cjs = cjsExportAssignmentOf(stmt); diff --git a/packages/compiler/src/frontend/resolve.ts b/packages/compiler/src/frontend/resolve.ts index 463400475..36470a643 100644 --- a/packages/compiler/src/frontend/resolve.ts +++ b/packages/compiler/src/frontend/resolve.ts @@ -18,6 +18,7 @@ import { dirname, isAbsolute, join, resolve } from "node:path"; import { isNpmStaticPackage, + npmStaticActive, npmStaticPackageOfPath, npmStaticTransformPkgJson, } from "./npm-static.js"; @@ -868,7 +869,11 @@ export function resolveBareModule( // never consulted — mirroring the shadowed world the tsgo host serves. const runtimeImport = mode === "runtime-js" || mode === "runtime-source"; const npmStatic = - mode === "js-only" || (!runtimeImport && mode !== "types-only" && isNpmStaticPackage(pkgName)); + mode === "js-only" || + (!runtimeImport && + mode !== "types-only" && + (isNpmStaticPackage(pkgName) || + (npmStaticActive() && isNpmStaticPackage(aliasedPackageOf(fromFile, pkgName))))); const conditions = runtimeImport ? RUNTIME_IMPORT_CONDITIONS : npmStatic @@ -970,6 +975,23 @@ export function resolveBareModule( return npmStatic ? passOnce("js") : (passOnce("types") ?? passOnce("js")); } +/** The package an npm alias directory installs (`"x-updated": + * "npm:x@6.3.0"` puts package x under node_modules/x-updated): the name + * the nearest node_modules//package.json declares when it + * differs from the directory name, else null. */ +function aliasedPackageOf(fromFile: string, dirName: string): string | null { + for (let dir = dirname(resolve(fromFile)); ;) { + const pkgDir = join(dir, "node_modules", dirName); + if (isDirectory(pkgDir)) { + const name = pkgJsonOf(pkgDir)?.name; + return typeof name === "string" && name !== dirName ? name : null; + } + const parent = dirname(dir); + if (parent === dir) return null; + dir = parent; + } +} + export function resolveWorkspaceSourceModule( fromFile: string, specifier: string, diff --git a/packages/compiler/src/frontend/services.test.ts b/packages/compiler/src/frontend/services.test.ts index bd172bbc5..e491b0ac5 100644 --- a/packages/compiler/src/frontend/services.test.ts +++ b/packages/compiler/src/frontend/services.test.ts @@ -7,6 +7,7 @@ import { Ts7Api } from "./ts7/rpc-api.js"; import { checkPreflight, isNodeEsmFile, loadProgram } from "./program.js"; import { clearResolveCaches } from "./resolve.js"; import type { Ts7ApiOptions } from "./ts7/program-host.js"; +import { getTextOfNode } from "./ts7/node-functions.js"; const directories: string[] = []; afterEach(() => { @@ -80,7 +81,7 @@ test("syntax sessions reuse one connection and returned ASTs survive edits and c services.close(); services.close(); expect(connections[0]!.close).toHaveBeenCalledTimes(1); - expect(before.statements.nodes[0]!.getText()).toBe(before.text); + expect(getTextOfNode(before.statements.nodes[0]!)).toBe(before.text); }); test("program hosts remain usable after their creating services close", () => { diff --git a/packages/compiler/src/frontend/services.ts b/packages/compiler/src/frontend/services.ts index ca4dd2b36..07bc8ca69 100644 --- a/packages/compiler/src/frontend/services.ts +++ b/packages/compiler/src/frontend/services.ts @@ -1,8 +1,15 @@ import { Ts7SourceParser, type Ts7SourceKind } from "./ts7/source-parser.js"; import { Ts7Host, type Ts7ApiFactory, type Ts7HostOptions } from "./ts7/program-host.js"; -import { rewriteBundlerCjsWithParser } from "./npm-static-rewrite-host.js"; +import { lowerEsmInitsWithParser, rewriteBundlerCjsWithParser } from "./npm-static-rewrite-host.js"; import { isBundlerCjsCandidate } from "./npm-static-rewrite-syntax.js"; +import { normalizeCjsFunctionExports } from "./npm-static-cjs-exports-alias.js"; +import { + mayBundleChunks, + splitBundledChunks, + type BundledChunkSplit, +} from "./npm-static-bundled-chunks.js"; import * as syntax from "./npm-static-declaration-syntax.js"; +import { normalizeCjsExportsVar } from "./npm-static-cjs-exports-var.js"; import type { SourceFile } from "./ts7/ast-types.js"; /** Evaluate an isolated, self-contained TypeScript callback under a finite @@ -55,10 +62,51 @@ export class FrontendServices { this.ensureOpen(); return new Ts7Host(this.createApi, { ...options, cwd: options?.cwd ?? this.cwd }); } - rewriteCjs(source: string, path: string): string | { degrade: string } | null { + rewriteCjs( + source: string, + path: string, + liveExportTable?: (path: string) => void, + lexerVisibleNames?: (path: string, names: readonly string[]) => void, + ): string | { degrade: string } | null { this.ensureOpen(); if (!isBundlerCjsCandidate(source)) return null; - return rewriteBundlerCjsWithParser(this.sourceParser(), source, path); + return rewriteBundlerCjsWithParser( + this.sourceParser(), + source, + path, + liveExportTable, + lexerVisibleNames, + ); + } + /** A CommonJS bundle's __commonJS chunks as their own modules (null: no + * chunks; a degrade reason: chunks the recognizer cannot move). */ + splitCjsChunks(source: string, path: string): BundledChunkSplit | { degrade: string } | null { + this.ensureOpen(); + if (!mayBundleChunks(source)) return null; + return splitBundledChunks(this.parse(path, source, "js"), path); + } + /** A CommonJS bundle's lazily initialized ES modules (__esm) lowered to + * run-once closures (null: none; a degrade reason: shapes the rewrite + * cannot lower). */ + lowerEsmInits(source: string, path: string): string | { degrade: string } | null { + this.ensureOpen(); + if (!source.includes("__esm")) return null; + return lowerEsmInitsWithParser(this.sourceParser(), source, path); + } + /** A CommonJS module whose export object is one of its functions, with + * its member accesses respelled through that function (null: none). */ + cjsFunctionExports(source: string, path: string): string | null { + this.ensureOpen(); + if (!source.includes("module.exports")) return null; + return normalizeCjsFunctionExports(this.parse(path, source, "js")); + } + /** A CommonJS module that names its export object through a variable + * (`var ip = exports`), its member accesses respelled through `exports` + * (null: none). */ + cjsExportsVar(source: string, path: string): string | null { + this.ensureOpen(); + if (!source.includes("exports")) return null; + return normalizeCjsExportsVar(this.parse(path, source, "js")); } nullableClassFields(path: string, source: string): syntax.NpmStaticOverloadRewrite | null { return syntax.applyNpmStaticNullableClassFields(this.parse(path, source, "js"), source); diff --git a/packages/compiler/src/frontend/tier-analysis.ts b/packages/compiler/src/frontend/tier-analysis.ts index 2ed5c8c9b..b75e491a8 100644 --- a/packages/compiler/src/frontend/tier-analysis.ts +++ b/packages/compiler/src/frontend/tier-analysis.ts @@ -107,8 +107,8 @@ function nodeAt(sf: ts.SourceFile, start: number, end: number): ts.Node { let node: ts.Node = sf; for (;;) { let next: ts.Node | undefined; - node.forEachChild((child) => { - if (child.getStart(sf) <= start && end <= child.getEnd()) { + ts.forEachChild(node, (child) => { + if (ts.tokenStartOfNode(child) <= start && end <= child.end) { next = child; return true; } @@ -263,23 +263,23 @@ function functionName(node: ts.Node): string { if (ts.isBinaryExpression(parent) && parent.right === node) { const left = parent.left; if (ts.isIdentifier(left)) return left.text; - if (ts.isPropertyAccessExpression(left)) return left.getText(); + if (ts.isPropertyAccessExpression(left)) return ts.getTextOfNode(left); } } - const sf = node.getSourceFile(); - const pos = ts.getLineAndCharacterOfPosition(sf, node.getStart(sf)); + const sf = ts.getSourceFileOfNode(node); + const pos = ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(node)); return ``; } function locOfNode(node: ts.Node): SrcLoc { - const sf = node.getSourceFile(); - return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() }; + const sf = ts.getSourceFileOfNode(node); + return { file: sf.fileName, start: ts.tokenStartOfNode(node), end: node.end }; } function enclosingFunction(node: ts.Node): ts.Node { let current: ts.Node | undefined = node.parent; while (current !== undefined && !isFunctionLikeOrFile(current)) current = current.parent; - return current ?? node.getSourceFile(); + return current ?? ts.getSourceFileOfNode(node); } function functionUnit(node: ts.Node, instance: string | null): DemotionUnit { @@ -423,7 +423,10 @@ class UnitResolver { return this.declarationUnit(binding); } if (ts.isVariableDeclaration(decl) || ts.isParameter(decl) || ts.isBindingElement(decl)) { - const name = nameText(decl.name) ?? decl.name?.getText() ?? ""; + const name = + nameText(decl.name) ?? + (decl.name === undefined ? undefined : ts.getTextOfNode(decl.name)) ?? + ""; const scope = enclosingFunction(decl); const unit: DemotionUnit = { kind: "binding", name, loc: locOfNode(decl) }; if (ts.isParameter(decl) && ts.isFunctionLikeDeclaration(decl.parent)) @@ -437,7 +440,7 @@ class UnitResolver { /** The unit an operand expression denotes. */ valueUnit(expr: ts.Node): { unit: DemotionUnit; reflection: boolean } | null { const { root, viaPrototype } = rootOf(expr); - const sf = root.getSourceFile(); + const sf = ts.getSourceFileOfNode(root); if (root.kind === ts.SyntaxKind.ThisKeyword) { const owner = enclosingFunction(root); if (ts.isSourceFile(owner)) @@ -475,7 +478,7 @@ class UnitResolver { if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) symbol = this.checker.getAliasedSymbol(symbol); const decls = symbol === undefined ? [] : symbol.declarations; - const decl = decls.find((d) => !d.getSourceFile().isDeclarationFile); + const decl = decls.find((d) => !ts.getSourceFileOfNode(d).isDeclarationFile); if (decl === undefined) { // CommonJS `exports`/`module` as values: the module object. if (isModuleObjectName(root)) diff --git a/packages/compiler/src/frontend/ts7/api-conformance.ts b/packages/compiler/src/frontend/ts7/api-conformance.ts new file mode 100644 index 000000000..304b4f32f --- /dev/null +++ b/packages/compiler/src/frontend/ts7/api-conformance.ts @@ -0,0 +1,297 @@ +/* Compile-time checks that scriptc's AST and semantic objects have the shapes + * of tsc-ts/api's interfaces. The frontend keeps its own types, whose nodes + * extend the AstNode class, because native code reads class fields directly + * and reads through a structural interface dynamically. This module only + * declares types and emits nothing. */ + +import type * as api from "tsc-ts/api"; +import type { AstNode, AstNodeList } from "./ast-node.js"; +import type * as ast from "./ast-types.js"; + +type Conforms = [T, U]; + +/* Members where tsgo's wire, which the RPC model reads, differs from tsc-ts: + * - SourceFile.externalModuleIndicator is `true` where moduleDetection makes a + * file a module without an import or export. + * - JSDocTypeLiteral.jsdocPropertyTags is one tag. + * - SyntaxList.children is a node list. + * - SyntheticExpression's isSpread and type are factory fields that a parsed + * tree never has. */ +type Without = Omit; + +export interface AstConformance { + Node: Conforms; + NodeList: Conforms; + SourceFile: Conforms< + Without, + Without + >; + ArrayBindingPattern: Conforms; + ArrayLiteralExpression: Conforms; + ArrayTypeNode: Conforms; + ArrowFunction: Conforms; + AsExpression: Conforms; + AwaitExpression: Conforms; + BigIntLiteral: Conforms; + BinaryExpression: Conforms; + BindingElement: Conforms; + Block: Conforms; + BodyBase: Conforms; + BreakStatement: Conforms; + CallExpression: Conforms; + CallSignatureDeclaration: Conforms; + CaseBlock: Conforms; + CaseClause: Conforms; + CatchClause: Conforms; + ClassDeclaration: Conforms; + ClassElementBase: Conforms; + ClassExpression: Conforms; + ClassLikeBase: Conforms; + ClassStaticBlockDeclaration: Conforms< + ast.ClassStaticBlockDeclaration, + api.ClassStaticBlockDeclaration + >; + ComputedPropertyName: Conforms; + ConditionalExpression: Conforms; + ConditionalTypeNode: Conforms; + ConstructSignatureDeclaration: Conforms< + ast.ConstructSignatureDeclaration, + api.ConstructSignatureDeclaration + >; + ConstructorDeclaration: Conforms; + ConstructorTypeNode: Conforms; + ContinueStatement: Conforms; + DebuggerStatement: Conforms; + DeclarationBase: Conforms; + Decorator: Conforms; + DefaultClause: Conforms; + DeleteExpression: Conforms; + DoStatement: Conforms; + ElementAccessExpression: Conforms; + EmptyStatement: Conforms; + EnumDeclaration: Conforms; + EnumMember: Conforms; + ExportAssignment: Conforms; + ExportDeclaration: Conforms; + ExportSpecifier: Conforms; + ExpressionBase: Conforms; + ExpressionStatement: Conforms; + ExpressionWithTypeArguments: Conforms< + ast.ExpressionWithTypeArguments, + api.ExpressionWithTypeArguments + >; + ExternalModuleReference: Conforms; + ForInStatement: Conforms; + ForOfStatement: Conforms; + ForStatement: Conforms; + FunctionDeclaration: Conforms; + FunctionExpression: Conforms; + FunctionLikeBase: Conforms; + FunctionLikeWithBodyBase: Conforms; + FunctionTypeNode: Conforms; + GetAccessorDeclaration: Conforms; + HeritageClause: Conforms; + Identifier: Conforms; + IfStatement: Conforms; + ImportAttribute: Conforms; + ImportAttributes: Conforms; + ImportClause: Conforms; + ImportDeclaration: Conforms; + ImportEqualsDeclaration: Conforms; + ImportSpecifier: Conforms; + ImportTypeNode: Conforms; + IndexSignatureDeclaration: Conforms; + IndexedAccessTypeNode: Conforms; + InferTypeNode: Conforms; + InterfaceDeclaration: Conforms; + IntersectionTypeNode: Conforms; + IterationStatementBase: Conforms; + JSDoc: Conforms; + JSDocAllType: Conforms; + JSDocAugmentsTag: Conforms; + JSDocCallbackTag: Conforms; + JSDocCommentBase: Conforms; + JSDocDeprecatedTag: Conforms; + JSDocImplementsTag: Conforms; + JSDocImportTag: Conforms; + JSDocLink: Conforms; + JSDocLinkCode: Conforms; + JSDocLinkPlain: Conforms; + JSDocNameReference: Conforms; + JSDocNonNullableType: Conforms; + JSDocNullableType: Conforms; + JSDocOptionalType: Conforms; + JSDocOverloadTag: Conforms; + JSDocOverrideTag: Conforms; + JSDocParameterTag: Conforms; + JSDocPrivateTag: Conforms; + JSDocPropertyTag: Conforms; + JSDocProtectedTag: Conforms; + JSDocPublicTag: Conforms; + JSDocReadonlyTag: Conforms; + JSDocReturnTag: Conforms; + JSDocSatisfiesTag: Conforms; + JSDocSeeTag: Conforms; + JSDocSignature: Conforms; + JSDocTagBase: Conforms; + JSDocTemplateTag: Conforms; + JSDocText: Conforms; + JSDocThisTag: Conforms; + JSDocThrowsTag: Conforms; + JSDocTypeBase: Conforms; + JSDocTypeExpression: Conforms; + JSDocTypeLiteral: Conforms< + Without, + Without + >; + JSDocTypeTag: Conforms; + JSDocTypedefTag: Conforms; + JSDocUnknownTag: Conforms; + JSDocVariadicType: Conforms; + JsxAttribute: Conforms; + JsxAttributes: Conforms; + JsxClosingElement: Conforms; + JsxClosingFragment: Conforms; + JsxElement: Conforms; + JsxExpression: Conforms; + JsxFragment: Conforms; + JsxNamespacedName: Conforms; + JsxOpeningElement: Conforms; + JsxOpeningFragment: Conforms; + JsxSelfClosingElement: Conforms; + JsxSpreadAttribute: Conforms; + JsxText: Conforms; + KeywordExpression: Conforms; + KeywordTypeNode: Conforms; + LabeledStatement: Conforms; + LeftHandSideExpressionBase: Conforms< + ast.LeftHandSideExpressionBase, + api.LeftHandSideExpressionBase + >; + LiteralExpressionBase: Conforms; + LiteralLikeNodeBase: Conforms; + LiteralTypeNode: Conforms; + MappedTypeNode: Conforms; + MemberExpressionBase: Conforms; + MetaProperty: Conforms; + MethodDeclaration: Conforms; + MethodSignatureDeclaration: Conforms< + ast.MethodSignatureDeclaration, + api.MethodSignatureDeclaration + >; + MissingDeclaration: Conforms; + ModifiersBase: Conforms; + ModuleBlock: Conforms; + ModuleDeclaration: Conforms; + NamedExports: Conforms; + NamedImports: Conforms; + NamedMemberBase: Conforms; + NamedTupleMember: Conforms; + NamespaceExport: Conforms; + NamespaceExportDeclaration: Conforms< + ast.NamespaceExportDeclaration, + api.NamespaceExportDeclaration + >; + NamespaceImport: Conforms; + NewExpression: Conforms; + NoSubstitutionTemplateLiteral: Conforms< + ast.NoSubstitutionTemplateLiteral, + api.NoSubstitutionTemplateLiteral + >; + NodeBase: Conforms; + NodeWithTypeArgumentsBase: Conforms; + NonNullExpression: Conforms; + NotEmittedStatement: Conforms; + NotEmittedTypeElement: Conforms; + NumericLiteral: Conforms; + ObjectBindingPattern: Conforms; + ObjectLiteralElementBase: Conforms; + ObjectLiteralExpression: Conforms; + OmittedExpression: Conforms; + OptionalTypeNode: Conforms; + ParameterDeclaration: Conforms; + ParenthesizedExpression: Conforms; + ParenthesizedTypeNode: Conforms; + PartiallyEmittedExpression: Conforms< + ast.PartiallyEmittedExpression, + api.PartiallyEmittedExpression + >; + PostfixUnaryExpression: Conforms; + PrefixUnaryExpression: Conforms; + PrimaryExpressionBase: Conforms; + PrivateIdentifier: Conforms; + PropertyAccessExpression: Conforms; + PropertyAssignment: Conforms; + PropertyDeclaration: Conforms; + PropertySignatureDeclaration: Conforms< + ast.PropertySignatureDeclaration, + api.PropertySignatureDeclaration + >; + QualifiedName: Conforms; + RegularExpressionLiteral: Conforms; + RestTypeNode: Conforms; + ReturnStatement: Conforms; + SatisfiesExpression: Conforms; + SemicolonClassElement: Conforms; + SetAccessorDeclaration: Conforms; + ShorthandPropertyAssignment: Conforms< + ast.ShorthandPropertyAssignment, + api.ShorthandPropertyAssignment + >; + SpreadAssignment: Conforms; + SpreadElement: Conforms; + StatementBase: Conforms; + StringLiteral: Conforms; + SwitchStatement: Conforms; + SyntaxList: Conforms, Without>; + SyntheticExpression: Conforms< + Without, + Without + >; + SyntheticReferenceExpression: Conforms< + ast.SyntheticReferenceExpression, + api.SyntheticReferenceExpression + >; + TaggedTemplateExpression: Conforms; + TemplateExpression: Conforms; + TemplateHead: Conforms; + TemplateLiteralLikeNodeBase: Conforms< + ast.TemplateLiteralLikeNodeBase, + api.TemplateLiteralLikeNodeBase + >; + TemplateLiteralTypeNode: Conforms; + TemplateLiteralTypeSpan: Conforms; + TemplateMiddle: Conforms; + TemplateSpan: Conforms; + TemplateTail: Conforms; + ThisTypeNode: Conforms; + ThrowStatement: Conforms; + Token: Conforms; + TryStatement: Conforms; + TupleTypeNode: Conforms; + TypeAliasDeclaration: Conforms; + TypeAssertion: Conforms; + TypeElementBase: Conforms; + TypeLiteralNode: Conforms; + TypeNodeBase: Conforms; + TypeOfExpression: Conforms; + TypeOperatorNode: Conforms; + TypeParameterDeclaration: Conforms; + TypePredicateNode: Conforms; + TypeQueryNode: Conforms; + TypeReferenceNode: Conforms; + UnaryExpressionBase: Conforms; + UnionOrIntersectionTypeNodeBase: Conforms< + ast.UnionOrIntersectionTypeNodeBase, + api.UnionOrIntersectionTypeNodeBase + >; + UnionTypeNode: Conforms; + UpdateExpressionBase: Conforms; + VariableDeclaration: Conforms; + VariableDeclarationList: Conforms; + VariableStatement: Conforms; + VoidExpression: Conforms; + WhileStatement: Conforms; + WithStatement: Conforms; + YieldExpression: Conforms; +} diff --git a/packages/compiler/src/frontend/ts7/ast-guards.generated.ts b/packages/compiler/src/frontend/ts7/ast-guards.generated.ts index aeaf56e9a..d163ac18d 100644 --- a/packages/compiler/src/frontend/ts7/ast-guards.generated.ts +++ b/packages/compiler/src/frontend/ts7/ast-guards.generated.ts @@ -4,6 +4,7 @@ import type { AbstractKeyword, AccessExpression, AccessorDeclaration, AccessorKeyword, AdditiveOperator, AdditiveOperatorOrHigher, AnyImportSyntax, ArrayBindingElement, ArrayBindingPattern, ArrayDestructuringAssignment, ArrayLiteralExpression, ArrayTypeNode, ArrowFunction, AsExpression, AssertKeyword, AssertionExpression, AssertsKeyword, AssignmentOperator, AssignmentOperatorOrHigher, AssignmentOperatorToken, AsteriskToken, AsyncKeyword, AwaitExpression, AwaitKeyword, BigIntLiteral, BinaryExpression, BinaryOperator, BinaryOperatorToken, BindingElement, BindingName, BindingPattern, BitwiseOperator, BitwiseOperatorOrHigher, Block, BlockOrExpression, BooleanLiteral, BreakOrContinueStatement, BreakStatement, CallExpression, CallLikeExpression, CallOrNewExpression, CallSignatureDeclaration, CaseBlock, CaseClause, CaseKeyword, CatchClause, ClassDeclaration, ClassExpression, ClassLikeDeclaration, ClassStaticBlockDeclaration, ColonToken, CompoundAssignmentOperator, ComputedPropertyName, ConciseBody, ConditionalExpression, ConditionalTypeNode, ConstKeyword, ConstructSignatureDeclaration, ConstructorDeclaration, ConstructorTypeNode, ContinueStatement, DebuggerStatement, DeclarationName, DeclareKeyword, Decorator, DefaultClause, DefaultKeyword, DeleteExpression, DestructuringAssignment, DoStatement, DotDotDotToken, DotToken, ElementAccessExpression, EmptyStatement, EndOfFile, EntityName, EnumDeclaration, EnumMember, EqualityOperator, EqualityOperatorOrHigher, EqualsGreaterThanToken, EqualsToken, ExclamationToken, ExponentiationOperator, ExportAssignment, ExportDeclaration, ExportKeyword, ExportSpecifier, Expression, ExpressionStatement, ExpressionWithTypeArguments, ExternalModuleReference, FalseLiteral, ForInStatement, ForInitializer, ForOfStatement, ForStatement, FunctionBody, FunctionDeclaration, FunctionExpression, FunctionLikeDeclaration, FunctionTypeNode, GetAccessorDeclaration, HeritageClause, Identifier, IfStatement, ImportAttribute, ImportAttributeName, ImportAttributes, ImportClause, ImportClauseOrBindingPattern, ImportDeclaration, ImportEqualsDeclaration, ImportExpression, ImportPhaseModifierSyntaxKind, ImportSpecifier, ImportTypeNode, InKeyword, IndexSignatureDeclaration, IndexedAccessTypeNode, InferTypeNode, InterfaceDeclaration, IntersectionTypeNode, JSDoc, JSDocAllType, JSDocAugmentsTag, JSDocCallbackTag, JSDocComment, JSDocDeprecatedTag, JSDocFullName, JSDocImplementsTag, JSDocImportTag, JSDocLink, JSDocLinkCode, JSDocLinkPlain, JSDocNameReference, JSDocNonNullableType, JSDocNullableType, JSDocOptionalType, JSDocOverloadTag, JSDocOverrideTag, JSDocParameterTag, JSDocPrivateTag, JSDocPropertyTag, JSDocProtectedTag, JSDocPublicTag, JSDocReadonlyTag, JSDocReturnTag, JSDocSatisfiesTag, JSDocSeeTag, JSDocSignature, JSDocTemplateTag, JSDocText, JSDocThisTag, JSDocThrowsTag, JSDocTypeExpression, JSDocTypeLiteral, JSDocTypeTag, JSDocTypedefTag, JSDocUnknownTag, JSDocVariadicType, JsxAttribute, JsxAttributeLike, JsxAttributeName, JsxAttributeValue, JsxAttributes, JsxChild, JsxClosingElement, JsxClosingFragment, JsxElement, JsxExpression, JsxFragment, JsxNamespacedName, JsxOpeningElement, JsxOpeningFragment, JsxOpeningLikeElement, JsxSelfClosingElement, JsxSpreadAttribute, JsxTagNameExpression, JsxText, JsxTokenSyntaxKind, KeywordExpression, KeywordExpressionSyntaxKind, KeywordTypeNode, KeywordTypeSyntaxKind, LabeledStatement, LeftHandSideExpression, LiteralExpression, LiteralLikeNode, LiteralToken, LiteralTypeNode, LogicalOperator, LogicalOperatorOrHigher, LogicalOrCoalescingAssignmentOperator, MappedTypeNode, MemberName, MetaProperty, MethodDeclaration, MethodSignatureDeclaration, MinusToken, MissingDeclaration, Modifier, ModifierLike, ModifierSyntaxKind, ModuleBlock, ModuleBody, ModuleDeclaration, ModuleExportName, ModuleName, ModuleReference, MultiplicativeOperator, MultiplicativeOperatorOrHigher, NamedExportBindings, NamedExports, NamedImportBindings, NamedImports, NamedImportsOrExports, NamedTupleMember, NamespaceExport, NamespaceExportDeclaration, NamespaceImport, NewExpression, NoSubstitutionTemplateLiteral, Node, NonNullExpression, NotEmittedStatement, NotEmittedTypeElement, NullLiteral, NumericLiteral, NumericOrStringLikeLiteral, ObjectBindingPattern, ObjectDestructuringAssignment, ObjectLiteralElementLike, ObjectLiteralExpression, ObjectLiteralLikeNode, ObjectTypeDeclaration, OmittedExpression, OptionalTypeNode, OutKeyword, OverrideKeyword, ParameterDeclaration, ParenthesizedExpression, ParenthesizedTypeNode, PartiallyEmittedExpression, PlusToken, PostfixUnaryExpression, PostfixUnaryOperator, PrefixUnaryExpression, PrefixUnaryOperator, PrivateIdentifier, PrivateKeyword, PropertyAccessExpression, PropertyAssignment, PropertyDeclaration, PropertyName, PropertyNameLiteral, PropertySignatureDeclaration, ProtectedKeyword, PseudoLiteralSyntaxKind, PseudoLiteralToken, PublicKeyword, QualifiedName, QuestionDotToken, QuestionToken, ReadonlyKeyword, RegularExpressionLiteral, RelationalOperator, RelationalOperatorOrHigher, RestTypeNode, ReturnStatement, SatisfiesExpression, SemicolonClassElement, SetAccessorDeclaration, ShiftOperator, ShiftOperatorOrHigher, ShorthandPropertyAssignment, SignatureDeclaration, SourceFile, SpreadAssignment, SpreadElement, Statement, StaticKeyword, StringLiteral, StringLiteralLikeNode, SuperExpression, SwitchStatement, SyntaxList, SyntheticExpression, SyntheticReferenceExpression, TaggedTemplateExpression, TemplateExpression, TemplateHead, TemplateLiteral, TemplateLiteralLikeNode, TemplateLiteralToken, TemplateLiteralTypeNode, TemplateLiteralTypeSpan, TemplateMiddle, TemplateMiddleOrTail, TemplateSpan, TemplateTail, ThisExpression, ThisTypeNode, ThrowStatement, Token, TriviaSyntaxKind, TrueLiteral, TryStatement, TupleTypeNode, TypeAliasDeclaration, TypeAssertion, TypeLiteralNode, TypeNode, TypeOfExpression, TypeOperatorNode, TypeParameterDeclaration, TypePredicateNode, TypePredicateParameterName, TypeQueryNode, TypeReferenceNode, UnaryExpressionBase, UnionOrIntersectionTypeNode, UnionTypeNode, VariableDeclaration, VariableDeclarationList, VariableOrParameterDeclaration, VariableOrPropertyDeclaration, VariableStatement, VoidExpression, WhileStatement, WithStatement, YieldExpression } from "./ast-types.js"; import { SyntaxKind, NodeFlags, ScriptKind, OuterExpressionKinds } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; export function isToken(node: Node | undefined): node is Token { if (node === undefined) return false; @@ -1913,7 +1914,7 @@ export function skipOuterExpressions(node: Node, kinds: OuterExpressionKinds = O function isJSDocTypeAssertion(node: Node | undefined): boolean { if (node === undefined) return false; - const sourceFile = node.getSourceFile(); + const sourceFile = getSourceFileOfNode(node); if (sourceFile.scriptKind !== ScriptKind.JS && sourceFile.scriptKind !== ScriptKind.JSX) { return false; } diff --git a/packages/compiler/src/frontend/ts7/ast-node.test.ts b/packages/compiler/src/frontend/ts7/ast-node.test.ts index e2440b787..b9b8f0e7b 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.test.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.test.ts @@ -20,7 +20,13 @@ import { walkPreorder } from "./ast.js"; import { ts7Executable } from "./rpc-api.js"; import { Ts7RpcClient } from "./rpc-client.js"; import { spawnTs7Wire } from "./rpc-process.js"; -import { isJSDocTypeLiteral, isJSDocPropertyTag } from "./ast-guards.generated.js"; +import { + isArrayLiteralExpression, + isCallExpression, + isJSDocTypeLiteral, + isJSDocPropertyTag, +} from "./ast-guards.generated.js"; +import { ModifierFlags, SyntaxKind } from "./enums.js"; const require = createRequire(import.meta.url); const sdkRoot = dirname(require.resolve("typescript/package.json")); @@ -95,6 +101,8 @@ const cases: Record = { '/** @typedef {Object} Options\n * @property {string} name\n * @property {number} count\n */\n/** @type {Options} */ const options = { name: "x", count: 2 };', "missing.ts": "const incomplete = ;\nfunction f( {\n", "empty.ts": "", + "lists.ts": + "const trailing = [1, 2 /* last */, ];\nconst plain = [1, 2];\nf(1, 2,);\nexport abstract class Flags { protected static readonly value = 1; }\n", }; const decoded = new Map(); let directory: string; @@ -159,6 +167,26 @@ function ids( return nodes?.map((n) => (n as OracleNode).index); } +test("node lists report a trailing comma and modifier lists their flags", () => { + const { file } = decoded.get("lists.ts")!; + const lists: [SyntaxKind, boolean][] = []; + const modifiers: number[] = []; + walkPreorder(file.root, (node) => { + if (isArrayLiteralExpression(node)) lists.push([node.kind, node.elements.hasTrailingComma()]); + if (isCallExpression(node)) lists.push([node.kind, node.arguments.hasTrailingComma()]); + if (node.modifiers !== undefined) modifiers.push(node.modifiers.modifierFlags); + }); + expect(lists).toEqual([ + [SyntaxKind.ArrayLiteralExpression, true], + [SyntaxKind.ArrayLiteralExpression, false], + [SyntaxKind.CallExpression, true], + ]); + expect(modifiers).toEqual([ + ModifierFlags.Export | ModifierFlags.Abstract, + ModifierFlags.Protected | ModifierFlags.Static | ModifierFlags.Readonly, + ]); +}); + test("JSDoc property refinements describe the pinned native wire", () => { const { file, oracle } = decoded.get("properties.js")!; let checked = 0; diff --git a/packages/compiler/src/frontend/ts7/ast-node.ts b/packages/compiler/src/frontend/ts7/ast-node.ts index 92ab5e4b8..8eaa87c72 100644 --- a/packages/compiler/src/frontend/ts7/ast-node.ts +++ b/packages/compiler/src/frontend/ts7/ast-node.ts @@ -19,13 +19,41 @@ import { SyntaxKind, NodeFlags } from "./enums.js"; /** A list of child nodes with the list's own position, the shape of tsc-ts's * NodeList. The file keeps the nodes array, not this object: each read of a * list field returns a new AstNodeList over the same array, so `nodes` keeps - * its identity and a decoded file holds no list objects. */ + * its identity and a decoded file holds no list objects. The position is + * decoded from the wire when it is read; most readers only use `nodes`. */ export class AstNodeList { constructor( - readonly pos: number, - readonly end: number, + private readonly wire: AstWireFile, + private readonly index: number, readonly nodes: readonly T[], ) {} + + get pos(): number { + return this.wire.pos(this.index) >>> 0; + } + + get end(): number { + return this.wire.end(this.index) >>> 0; + } + + /** Whether a comma follows the last node before the list's end. */ + hasTrailingComma(): boolean { + const last = this.nodes[this.nodes.length - 1]; + if (last === undefined) return false; + const text = last.getSourceFile().text; + const next = astSkipTrivia(text, last.end, false, false); + return next < this.end && text.charCodeAt(next) === 44; + } +} + +/** A declaration's modifiers, with the flags of all of them: tsc-ts's + * ModifierList. */ +export class AstModifierList extends AstNodeList { + get modifierFlags(): number { + let result = 0; + for (const modifier of this.nodes) result |= astModifierFlag(modifier.kind); + return result; + } } /** One immutable AST response owns one object per node and one array per @@ -76,6 +104,12 @@ export class AstFile { } list(index: number): AstNodeList { + return new AstNodeList(this.wire, index, this.listNodes(index)); + } + modifierList(index: number): AstModifierList { + return new AstModifierList(this.wire, index, this.listNodes(index)); + } + private listNodes(index: number): AstNode[] { let nodes = this.lists[index]; if (nodes === undefined) { nodes = []; @@ -83,7 +117,7 @@ export class AstFile { this.lists[index] = nodes; this.materialized?.(); } - return new AstNodeList(this.wire.pos(index) >>> 0, this.wire.end(index) >>> 0, nodes); + return nodes; } namedChild(node: AstNode, name: string): number { @@ -413,6 +447,11 @@ export class AstNode { return result; } + get modifiers(): AstModifierList | undefined { + const index = this.file.namedChild(this, "modifiers"); + return index === 0 ? undefined : this.file.modifierList(index); + } + get body(): AstNode | undefined { const body = this.childNode("body"); // TS7 can serialize a reparsed @type callable before a method's @@ -590,9 +629,6 @@ export class AstNode { get members(): AstNodeList | undefined { return this.childList("members"); } - get modifiers(): AstNodeList | undefined { - return this.childList("modifiers"); - } get moduleReference(): AstNode | undefined { return this.childNode("moduleReference"); } diff --git a/packages/compiler/src/frontend/ts7/ast-types.generated.ts b/packages/compiler/src/frontend/ts7/ast-types.generated.ts index de6ff892f..59f01e9ea 100644 --- a/packages/compiler/src/frontend/ts7/ast-types.generated.ts +++ b/packages/compiler/src/frontend/ts7/ast-types.generated.ts @@ -3,7 +3,7 @@ // Regenerate when changing the TypeScript pin; do not edit by hand. import type { AstNode } from "./ast-node.js"; -import type { JsxTagNamePropertyAccess, NodeArray } from "./ast-types.js"; +import type { JsxTagNamePropertyAccess, ModifierArray, NodeArray } from "./ast-types.js"; import { SyntaxKind, NodeFlags, ModifierFlags, TokenFlags } from "./enums.js"; type Node = AstNode; @@ -142,7 +142,7 @@ export interface DeclarationBase extends Node { } export interface ModifiersBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.VariableStatement | SyntaxKind.Parameter | SyntaxKind.MissingDeclaration | SyntaxKind.FunctionDeclaration | SyntaxKind.ClassDeclaration | SyntaxKind.ClassExpression | SyntaxKind.InterfaceDeclaration | SyntaxKind.TypeAliasDeclaration | SyntaxKind.EnumMember | SyntaxKind.EnumDeclaration | SyntaxKind.ImportDeclaration | SyntaxKind.ExportAssignment | SyntaxKind.NamespaceExportDeclaration | SyntaxKind.Constructor | SyntaxKind.GetAccessor | SyntaxKind.SetAccessor | SyntaxKind.IndexSignature | SyntaxKind.MethodSignature | SyntaxKind.MethodDeclaration | SyntaxKind.PropertySignature | SyntaxKind.PropertyDeclaration | SyntaxKind.ClassStaticBlockDeclaration | SyntaxKind.BinaryExpression | SyntaxKind.ArrowFunction | SyntaxKind.FunctionExpression | SyntaxKind.PropertyAssignment | SyntaxKind.ShorthandPropertyAssignment | SyntaxKind.FunctionType | SyntaxKind.ConstructorType | SyntaxKind.ModuleDeclaration | SyntaxKind.ImportEqualsDeclaration | SyntaxKind.ExportDeclaration | SyntaxKind.TypeParameter; } @@ -170,7 +170,7 @@ export interface FunctionLikeWithBodyBase extends Node { } export interface ClassLikeBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -209,7 +209,7 @@ export interface ClassElementBase extends Node { } export interface NamedMemberBase extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -397,7 +397,7 @@ export interface Block extends Node { export interface VariableStatement extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.VariableStatement; readonly declarationList: VariableDeclarationList; @@ -420,7 +420,7 @@ export interface VariableDeclarationList extends Node { export interface ParameterDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.Parameter; readonly dotDotDotToken: DotDotDotToken | undefined; @@ -441,14 +441,14 @@ export interface BindingElement extends Node { export interface MissingDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.MissingDeclaration; } export interface FunctionDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -461,7 +461,7 @@ export interface FunctionDeclaration extends Node { export interface ClassDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -472,7 +472,7 @@ export interface ClassDeclaration extends Node { export interface ClassExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: Identifier | undefined; readonly typeParameters: NodeArray | undefined; @@ -490,7 +490,7 @@ export interface HeritageClause extends Node { export interface InterfaceDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.InterfaceDeclaration; readonly name: Identifier; @@ -501,7 +501,7 @@ export interface InterfaceDeclaration extends Node { export interface TypeAliasDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.TypeAliasDeclaration; readonly name: Identifier; @@ -511,7 +511,7 @@ export interface TypeAliasDeclaration extends Node { export interface EnumMember extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -521,7 +521,7 @@ export interface EnumMember extends Node { export interface EnumDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.EnumDeclaration; readonly name: Identifier; @@ -546,7 +546,7 @@ export interface NotEmittedTypeElement extends Node { export interface ImportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ImportDeclaration; readonly importClause: ImportClause | undefined; @@ -574,7 +574,7 @@ export interface NamedImports extends Node { export interface ExportAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ExportAssignment; readonly isExportEquals: boolean; @@ -584,7 +584,7 @@ export interface ExportAssignment extends Node { export interface NamespaceExportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.NamespaceExportDeclaration; readonly name: Identifier; @@ -628,7 +628,7 @@ export interface ConstructSignatureDeclaration extends Node { export interface ConstructorDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -639,7 +639,7 @@ export interface ConstructorDeclaration extends Node { } export interface GetAccessorDeclaration extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -653,7 +653,7 @@ export interface GetAccessorDeclaration extends Node { } export interface SetAccessorDeclaration extends Node { - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -668,7 +668,7 @@ export interface SetAccessorDeclaration extends Node { export interface IndexSignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -678,7 +678,7 @@ export interface IndexSignatureDeclaration extends Node { export interface MethodSignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -690,7 +690,7 @@ export interface MethodSignatureDeclaration extends Node { export interface MethodDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -704,7 +704,7 @@ export interface MethodDeclaration extends Node { export interface PropertySignatureDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -715,7 +715,7 @@ export interface PropertySignatureDeclaration extends Node { export interface PropertyDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -731,7 +731,7 @@ export interface SemicolonClassElement extends Node { export interface ClassStaticBlockDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ClassStaticBlockDeclaration; readonly body: Block; @@ -786,7 +786,7 @@ export interface NoSubstitutionTemplateLiteral extends Node { export interface BinaryExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.BinaryExpression; readonly left: Expression; @@ -818,7 +818,7 @@ export interface YieldExpression extends Node { export interface ArrowFunction extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -831,7 +831,7 @@ export interface ArrowFunction extends Node { export interface FunctionExpression extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -969,7 +969,7 @@ export interface SpreadAssignment extends Node { export interface PropertyAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -980,7 +980,7 @@ export interface PropertyAssignment extends Node { export interface ShorthandPropertyAssignment extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly name: PropertyName; readonly postfixToken: QuestionToken | ExclamationToken | undefined; @@ -1180,7 +1180,7 @@ export interface ParenthesizedTypeNode extends Node { export interface FunctionTypeNode extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -1190,7 +1190,7 @@ export interface FunctionTypeNode extends Node { export interface ConstructorTypeNode extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly typeParameters: NodeArray | undefined; readonly parameters: NodeArray; @@ -1562,7 +1562,7 @@ export interface JSDocNameReference extends Node { export interface ModuleDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly asteriskToken: AsteriskToken | undefined; readonly body: ModuleBody | undefined; @@ -1573,7 +1573,7 @@ export interface ModuleDeclaration extends Node { export interface ImportEqualsDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ImportEqualsDeclaration; readonly isTypeOnly: boolean; @@ -1583,7 +1583,7 @@ export interface ImportEqualsDeclaration extends Node { export interface ExportDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.ExportDeclaration; readonly isTypeOnly: boolean; @@ -1647,7 +1647,7 @@ export interface JSDocLinkCode extends Node { export interface TypeParameterDeclaration extends Node { readonly flags: NodeFlags; - readonly modifiers: NodeArray | undefined; + readonly modifiers: ModifierArray | undefined; readonly modifierFlags: ModifierFlags; readonly kind: SyntaxKind.TypeParameter; readonly name: Identifier; @@ -1842,7 +1842,7 @@ export interface CaseClause extends Node { export interface DefaultClause extends Node { readonly flags: NodeFlags; readonly kind: SyntaxKind.DefaultClause; - readonly expression: Expression; + readonly expression: undefined; readonly statements: NodeArray; } diff --git a/packages/compiler/src/frontend/ts7/ast-types.ts b/packages/compiler/src/frontend/ts7/ast-types.ts index 6d421e58d..e40e40e30 100644 --- a/packages/compiler/src/frontend/ts7/ast-types.ts +++ b/packages/compiler/src/frontend/ts7/ast-types.ts @@ -1,4 +1,4 @@ -import type { AstNode, AstNodeList } from "./ast-node.js"; +import type { AstModifierList, AstNode, AstNodeList } from "./ast-node.js"; import { SyntaxKind } from "./enums.js"; import type { EndOfFile, @@ -14,8 +14,11 @@ export type Node = AstNode; /** A node list. The type is the list class itself, not a structural shape: * native code represents a class and a record type differently. */ export type NodeArray = AstNodeList; +/** A declaration's modifiers: a node list with the flags of all of them. */ +export type ModifierArray = AstModifierList; export type Path = string; -export type __String = string; +/** An escaped symbol name, the key of member and export tables (TypeScript 7's `__String`). */ +export type __String = string & { readonly __escapedIdentifier: void }; export interface TextRange { pos: number; end: number; diff --git a/packages/compiler/src/frontend/ts7/ast.ts b/packages/compiler/src/frontend/ts7/ast.ts index ec26aa0b0..a92cfafd8 100644 --- a/packages/compiler/src/frontend/ts7/ast.ts +++ b/packages/compiler/src/frontend/ts7/ast.ts @@ -53,6 +53,7 @@ import { ModifierFlags, SyntaxKind } from "./enums.js"; // The frontend's node types, guards and token spellings share the concrete // AstNode layout. Parser and transform factories remain outside this client. export * from "./ast-types.js"; +export * from "./node-functions.js"; export * from "./ast-guards.generated.js"; export { tokenToString } from "./ast-tokens.generated.js"; diff --git a/packages/compiler/src/frontend/ts7/checker-backend.test.ts b/packages/compiler/src/frontend/ts7/checker-backend.test.ts new file mode 100644 index 000000000..e37357afa --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-backend.test.ts @@ -0,0 +1,79 @@ +import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; +import { checkerBackend, tscTsExecutable, tscTsPlatformPackage } from "./checker-backend.js"; + +const roots: string[] = []; + +afterEach(() => { + for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }); +}); + +function tempRoot(): string { + const root = realpathSync(mkdtempSync(join(tmpdir(), "scriptc-checker-"))); + roots.push(root); + return root; +} + +function writePackage(directory: string, name: string, executable?: string): void { + mkdirSync(directory, { recursive: true }); + writeFileSync(join(directory, "package.json"), JSON.stringify({ name })); + if (executable === undefined) return; + mkdirSync(join(directory, "lib"), { recursive: true }); + writeFileSync(join(directory, "lib", executable), ""); +} + +test("tsc-ts is the default checker except on Windows, and SCRIPTC_CHECKER selects one", () => { + expect(checkerBackend({}, "linux")).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "" }, "linux")).toBe("tsc-ts"); + expect(checkerBackend({}, "darwin")).toBe("tsc-ts"); + expect(checkerBackend({}, "win32")).toBe("tsgo"); + expect(checkerBackend({ SCRIPTC_CHECKER: "" }, "win32")).toBe("tsgo"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" }, "linux")).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsc-ts" }, "win32")).toBe("tsc-ts"); + expect(checkerBackend({ SCRIPTC_CHECKER: "tsgo" }, "linux")).toBe("tsgo"); + expect(() => checkerBackend({ SCRIPTC_CHECKER: "tsc" })).toThrow( + 'SCRIPTC_CHECKER must be tsgo or tsc-ts, not "tsc"', + ); +}); + +test("tsc-ts platform packages are named for the host", () => { + expect(tscTsPlatformPackage("linux", "x64", false)).toBe("@tsc-ts/linux-x64-gnu"); + expect(tscTsPlatformPackage("linux", "arm64", true)).toBe("@tsc-ts/linux-arm64-musl"); + expect(tscTsPlatformPackage("darwin", "arm64", false)).toBe("@tsc-ts/darwin-arm64"); + expect(tscTsPlatformPackage("win32", "x64", false)).toBe("@tsc-ts/win32-x64-msvc"); +}); + +test("the tsc-ts executable is found next to an npm install of tsc-ts", () => { + const root = tempRoot(); + writePackage(join(root, "node_modules/tsc-ts"), "tsc-ts"); + writePackage(join(root, "node_modules/@tsc-ts/linux-x64-gnu"), "@tsc-ts/linux-x64-gnu", "tsc-ts"); + mkdirSync(join(root, "src/deep"), { recursive: true }); + expect(tscTsExecutable([join(root, "src/deep")], "@tsc-ts/linux-x64-gnu", "linux")).toBe( + join(root, "node_modules/@tsc-ts/linux-x64-gnu/lib/tsc-ts"), + ); +}); + +test("the tsc-ts executable is found in a pnpm install of tsc-ts", () => { + const root = tempRoot(); + const store = join(root, "node_modules/.pnpm/tsc-ts@0.0.3/node_modules"); + writePackage(join(store, "tsc-ts"), "tsc-ts"); + writePackage(join(store, "@tsc-ts/win32-x64-msvc"), "@tsc-ts/win32-x64-msvc", "tsc-ts.exe"); + mkdirSync(join(root, "app/node_modules"), { recursive: true }); + symlinkSync(join(store, "tsc-ts"), join(root, "app/node_modules/tsc-ts")); + expect(tscTsExecutable([join(root, "app")], "@tsc-ts/win32-x64-msvc", "win32")).toBe( + join(store, "@tsc-ts/win32-x64-msvc/lib/tsc-ts.exe"), + ); +}); + +test("a missing tsc-ts install or platform package is an error", () => { + const root = tempRoot(); + expect(() => tscTsExecutable([root], "@tsc-ts/linux-x64-gnu", "linux")).toThrow( + "the tsc-ts package is not installed", + ); + writePackage(join(root, "node_modules/tsc-ts"), "tsc-ts"); + expect(() => tscTsExecutable([root], "@tsc-ts/linux-x64-gnu", "linux")).toThrow( + "has no @tsc-ts/linux-x64-gnu executable", + ); +}); diff --git a/packages/compiler/src/frontend/ts7/checker-backend.ts b/packages/compiler/src/frontend/ts7/checker-backend.ts new file mode 100644 index 000000000..14418cc94 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/checker-backend.ts @@ -0,0 +1,111 @@ +import { existsSync, realpathSync, statSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** The TypeScript 7 API servers a compile can check with: tsc-ts, the default, + * and tsgo from the `typescript` package, selected with `SCRIPTC_CHECKER=tsgo`. + * Both serve the same API and give the same results. On Windows the default is + * tsgo: the compiler connects to the server over a named pipe there, and tsc-ts's + * API server has no named-pipe transport yet. */ +export type CheckerBackend = "tsgo" | "tsc-ts"; + +export function checkerBackend( + env: NodeJS.ProcessEnv = process.env, + platform: string = process.platform, +): CheckerBackend { + const value = env["SCRIPTC_CHECKER"]; + if (value === undefined || value === "") return platform === "win32" ? "tsgo" : "tsc-ts"; + if (value === "tsc-ts") return "tsc-ts"; + if (value === "tsgo") return "tsgo"; + throw new Error(`SCRIPTC_CHECKER must be tsgo or tsc-ts, not ${JSON.stringify(value)}`); +} + +/** tsc-ts's platform package for a host: `@tsc-ts/linux-x64-gnu`, + * `@tsc-ts/darwin-arm64`, `@tsc-ts/win32-x64-msvc`, and so on. */ +export function tscTsPlatformPackage( + platform: string, + architecture: string, + musl: boolean, +): string { + const abi = + platform === "linux" ? (musl ? "-musl" : "-gnu") : platform === "win32" ? "-msvc" : ""; + return `@tsc-ts/${platform}-${architecture}${abi}`; +} + +let hostMusl: boolean | undefined; + +/** Whether this process runs on musl, the libc of tsc-ts's `-musl` Linux packages: Node reports no glibc + * runtime version there. The libc cannot change within a process, and a report is costly to generate. */ +export function hostIsMusl(): boolean { + if (process.platform !== "linux") return false; + if (hostMusl === undefined) { + const report = process.report?.getReport() as + | { header?: { glibcVersionRuntime?: unknown } } + | undefined; + hostMusl = typeof report?.header?.glibcVersionRuntime !== "string"; + } + return hostMusl; +} + +/** The checker's part of an executable cache key: the identity of the tsc-ts + * executable, or undefined for tsgo, whose keys predate the choice. */ +export function checkerCacheIdentity(env: NodeJS.ProcessEnv = process.env): string | undefined { + if (checkerBackend(env) === "tsgo") return undefined; + const path = hostTscTsExecutable(); + const info = statSync(path); + return JSON.stringify({ + path, + canonical: realpathSync(path), + dev: info.dev, + ino: info.ino, + size: info.size, + mtime: info.mtimeMs, + ctime: info.ctimeMs, + }); +} + +/** The Node host's tsc-ts executable: that of the `tsc-ts` package installed + * with this compiler, or else for the working directory. */ +export function hostTscTsExecutable(): string { + return tscTsExecutable( + [dirname(fileURLToPath(import.meta.url)), process.cwd()], + tscTsPlatformPackage(process.platform, process.arch, hostIsMusl()), + process.platform, + ); +} + +/** The tsc-ts executable of the `tsc-ts` package in the nearest + * `node_modules` above each of `starts`, in order. The platform package is + * installed next to `tsc-ts` (npm) or in the directory that holds it (pnpm). */ +export function tscTsExecutable( + starts: readonly string[], + platformPackage: string, + platform: string, +): string { + const name = platform === "win32" ? "tsc-ts.exe" : "tsc-ts"; + for (const start of starts) { + const packageRoot = installedPackage(start, "tsc-ts"); + if (packageRoot === undefined) continue; + const root = realpathSync(packageRoot); + for (const candidate of [ + join(root, "node_modules", platformPackage), + join(dirname(root), platformPackage), + ]) { + const executable = join(candidate, "lib", name); + if (existsSync(executable)) return executable; + } + throw new Error(`tsc-ts checker: ${root} has no ${platformPackage} executable`); + } + throw new Error("tsc-ts checker: the tsc-ts package is not installed"); +} + +function installedPackage(start: string, name: string): string | undefined { + let directory = resolve(start); + for (;;) { + const candidate = join(directory, "node_modules", name); + if (existsSync(join(candidate, "package.json"))) return candidate; + const parent = dirname(directory); + if (parent === directory) return undefined; + directory = parent; + } +} diff --git a/packages/compiler/src/frontend/ts7/checker.test.ts b/packages/compiler/src/frontend/ts7/checker.test.ts index 7b3647b2d..ac062b283 100644 --- a/packages/compiler/src/frontend/ts7/checker.test.ts +++ b/packages/compiler/src/frontend/ts7/checker.test.ts @@ -10,10 +10,10 @@ import { ts7Executable } from "./rpc-api.js"; import { Ts7Session } from "./session.js"; import { SemanticChecker } from "./semantic-checker.js"; import { SemanticSnapshot } from "./semantic-model.js"; -import { TypeFlags } from "./enums.js"; +import { SignatureKind, SymbolFlags, TypeFlags } from "./enums.js"; import { createProgram, Ts7Host } from "./program-adapter.js"; import type { AstNode } from "./ast-node.js"; -import type { SourceFile } from "./ast-types.js"; +import type { Identifier, SourceFile } from "./ast-types.js"; const tempRoot = process.platform === "win32" ? tmpdir() : "/tmp"; const protocolPath = (path: string) => path.replaceAll("\\", "/"); @@ -167,6 +167,12 @@ test("all public query and prefetch entry points reject a disposed facade before const calls: Record unknown> = { getCallSignatures: () => facade.getCallSignatures(type), getConstructSignatures: () => facade.getConstructSignatures(type), + getSignaturesOfType: () => facade.getSignaturesOfType(type, SignatureKind.Call), + resolveName: () => facade.resolveName("value", SymbolFlags.Value, name), + getResolvedSymbol: () => facade.getResolvedSymbol(name as Identifier), + getAnyType: () => facade.getAnyType(), + getNeverType: () => facade.getNeverType(), + getBigIntType: () => facade.getBigIntType(), getPropertyOfType: () => facade.getPropertyOfType(type, "missing"), prefetchSourceFile: () => facade.prefetchSourceFile(root), prefetchSourceFileStructures: () => facade.prefetchSourceFileStructures([]), diff --git a/packages/compiler/src/frontend/ts7/checker.ts b/packages/compiler/src/frontend/ts7/checker.ts index 41eb0d0b9..4f2d60294 100644 --- a/packages/compiler/src/frontend/ts7/checker.ts +++ b/packages/compiler/src/frontend/ts7/checker.ts @@ -35,7 +35,7 @@ import { CheckerCache } from "./checker-cache.js"; * repeats an IPC request. All paths are verified against the raw checker * and against 5.9.3 by the adapter's suites. */ -import type { Node, SourceFile } from "./ast-types.js"; +import type { Identifier, Node, SourceFile } from "./ast-types.js"; import type { IndexInfo, InterfaceType, @@ -46,9 +46,10 @@ import type { TypeReference, } from "./semantic-types.js"; import type { SemanticChecker as Checker } from "./semantic-checker.js"; -import type { SemanticProject as Project } from "./semantic-model.js"; +import { symbolNameOf, type SemanticProject as Project } from "./semantic-model.js"; import { isTypeNode, walkPreorder } from "./ast.js"; -import { SignatureKind, SyntaxKind, TypeFlags } from "./enums.js"; +import { SignatureKind, SyntaxKind, TypeFlags, type SymbolFlags } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; /** Array-overload chunk size: large enough that per-request overhead * vanishes, small enough to keep any single JSON-RPC payload modest. */ @@ -259,6 +260,14 @@ export class CheckerFacade { return sigs; } + /** The call or construct signatures of `type`, as getCallSignatures and getConstructSignatures + * answer them. */ + getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] { + return kind === SignatureKind.Call + ? this.getCallSignatures(type) + : this.getConstructSignatures(type); + } + /** 5.9.3's type.getProperty(name). */ getPropertyOfType(type: Type, name: string): Ts7Symbol | undefined { this.ensureActive(); @@ -370,7 +379,7 @@ export class CheckerFacade { private markManaged(roots: readonly Node[]): void { for (const root of roots) { - const sf = root.getSourceFile(); + const sf = getSourceFileOfNode(root); this.cache.managedTypes.add(sf); this.cache.managedSymbols.add(sf); } @@ -447,7 +456,7 @@ export class CheckerFacade { private autoPrefetch(node: Node, kind: "types" | "symbols"): void { if (this.options.autoPrefetch === false) return; - const sf = node.getSourceFile(); + const sf = getSourceFileOfNode(node); // A referenced declaration rarely needs the rest of its library's // metadata. Keep those queries direct; explicit whole-file prefetch // remains available to consumers that do need the complete surface. @@ -786,6 +795,38 @@ export class CheckerFacade { return this.raw.getTypeOfSymbolAtLocation(symbol, location); } + resolveName( + name: string, + meaning: SymbolFlags, + location?: Node, + excludeGlobals?: boolean, + ): Ts7Symbol | undefined { + this.ensureActive(); + return this.raw.resolveName(name, meaning, location, excludeGlobals); + } + + /** The value `node` names where it is: resolveName of its text with SymbolFlags.Value and + * SymbolFlags.ExportValue. */ + getResolvedSymbol(node: Identifier): Ts7Symbol | undefined { + this.ensureActive(); + return this.raw.getResolvedSymbol(node); + } + + getAnyType(): Type { + this.ensureActive(); + return this.anyType(); + } + + getNeverType(): Type { + this.ensureActive(); + return this.intrinsic("never", () => this.raw.getNeverType()); + } + + getBigIntType(): Type { + this.ensureActive(); + return this.intrinsic("bigint", () => this.raw.getBigIntType()); + } + getUnknownType(): Type { this.ensureActive(); if (this.cache.unknownType === null) this.cache.unknownType = this.raw.getUnknownType(); @@ -851,7 +892,7 @@ export class CheckerFacade { * programs see the es2025 lib's Promise (the ambient world forces it). */ private promiseArgumentOf(type: Type): Type | null { if (!type.isTypeReference()) return null; - const name = type.getTarget()?.getSymbol()?.name; + const name = symbolNameOf(type.getTarget()?.getSymbol()); if (name !== "Promise" && name !== "PromiseLike") return null; const args = this.getTypeArguments(type); return args[0] ?? null; diff --git a/packages/compiler/src/frontend/ts7/diagnostic.ts b/packages/compiler/src/frontend/ts7/diagnostic.ts new file mode 100644 index 000000000..db3ff0e63 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/diagnostic.ts @@ -0,0 +1,38 @@ +import type { DiagnosticCategory } from "./enums.generated.js"; +import type { Ts7DiagnosticData } from "./session-schema.generated.js"; + +/** A diagnostic with every member set, as tsc-ts/api's Diagnostic has them. The server leaves out + * the members a diagnostic doesn't have. `fileName` is undefined for global and program + * diagnostics. */ +export interface Ts7Diagnostic { + readonly fileName: string | undefined; + readonly pos: number; + readonly end: number; + readonly code: number; + readonly category: DiagnosticCategory; + readonly text: string; + readonly reportsUnnecessary: boolean; + readonly reportsDeprecated: boolean; + readonly messageChain: readonly Ts7Diagnostic[]; + readonly relatedInformation: readonly Ts7Diagnostic[]; +} + +/** `data` with its absent members set: undefined, false, or no chained or related diagnostics. */ +export function ts7Diagnostic(data: Ts7DiagnosticData): Ts7Diagnostic { + const chain: Ts7Diagnostic[] = []; + for (const item of data.messageChain ?? []) chain.push(ts7Diagnostic(item)); + const related: Ts7Diagnostic[] = []; + for (const item of data.relatedInformation ?? []) related.push(ts7Diagnostic(item)); + return { + fileName: data.fileName, + pos: data.pos, + end: data.end, + code: data.code, + category: data.category, + text: data.text, + reportsUnnecessary: data.reportsUnnecessary ?? false, + reportsDeprecated: data.reportsDeprecated ?? false, + messageChain: chain, + relatedInformation: related, + }; +} diff --git a/packages/compiler/src/frontend/ts7/node-functions.ts b/packages/compiler/src/frontend/ts7/node-functions.ts new file mode 100644 index 000000000..3b37779e8 --- /dev/null +++ b/packages/compiler/src/frontend/ts7/node-functions.ts @@ -0,0 +1,23 @@ +/* Position and text functions over nodes, with tsc-ts/api's names. The + * frontend calls these rather than node methods, so that its node types can + * be tsc-ts/api's interfaces, which have no methods. Today every node is an + * AstNode, and each function forwards to the AstNode method. */ + +import type { AstNode } from "./ast-node.js"; +import type { Node, SourceFile } from "./ast-types.js"; + +/** The start of the node's first token, after its leading trivia, and before + * its JSDoc comments when `includeJSDoc` is true. */ +export function tokenStartOfNode(node: Node, includeJSDoc = false): number { + return (node as AstNode).getStart(undefined, includeJSDoc); +} + +/** The node's text from its first token to its end. */ +export function getTextOfNode(node: Node): string { + return (node as AstNode).getText(); +} + +/** The source file that contains the node. */ +export function getSourceFileOfNode(node: Node): SourceFile { + return (node as AstNode).getSourceFile(); +} diff --git a/packages/compiler/src/frontend/ts7/program-host.ts b/packages/compiler/src/frontend/ts7/program-host.ts index 2f2fdf76b..dbc0804d5 100644 --- a/packages/compiler/src/frontend/ts7/program-host.ts +++ b/packages/compiler/src/frontend/ts7/program-host.ts @@ -21,7 +21,7 @@ import { dirname, join, resolve } from "node:path"; import { Ts7Api } from "./api.js"; import type { Ts7TimingInfo } from "./session-timing.js"; import type { Ts7FileSystem } from "./rpc-filesystem.js"; -import type { CompilerOptions, Diagnostic } from "./semantic-types.js"; +import type { CompilerOptions, Diagnostic, SourceFileMetadata } from "./semantic-types.js"; import type { SourceFile } from "./ast-types.js"; import type { Ts7SessionProject as Project, Ts7SessionSnapshot as Snapshot } from "./session.js"; import { CheckerFacade } from "./checker.js"; @@ -343,10 +343,22 @@ export class Ts7Program { return this.project.program.getSyntacticDiagnostics(sourceFile?.fileName); } + getBindDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getBindDiagnostics(sourceFile?.fileName); + } + getSemanticDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { return this.project.program.getSemanticDiagnostics(sourceFile?.fileName); } + getSuggestionDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getSuggestionDiagnostics(sourceFile?.fileName); + } + + getDeclarationDiagnostics(sourceFile?: SourceFile): readonly Diagnostic[] { + return this.project.program.getDeclarationDiagnostics(sourceFile?.fileName); + } + getGlobalDiagnostics(): readonly Diagnostic[] { return this.project.program.getGlobalDiagnostics(); } @@ -359,6 +371,10 @@ export class Ts7Program { return this.project.program.getConfigFileParsingDiagnostics(); } + getSourceFileMetadata(fileName: string): SourceFileMetadata | undefined { + return this.project.program.getSourceFileMetadata(resolve(fileName)); + } + isSourceFileDefaultLibrary(file: SourceFile): boolean { return this.project.program.isSourceFileDefaultLibrary(file); } diff --git a/packages/compiler/src/frontend/ts7/rpc-api.ts b/packages/compiler/src/frontend/ts7/rpc-api.ts index a6ae609d4..a21ba6279 100644 --- a/packages/compiler/src/frontend/ts7/rpc-api.ts +++ b/packages/compiler/src/frontend/ts7/rpc-api.ts @@ -1,6 +1,7 @@ import { createRequire } from "node:module"; import { dirname, join } from "node:path"; import { Ts7Api as ConnectedTs7Api } from "./api.js"; +import { checkerBackend, hostTscTsExecutable } from "./checker-backend.js"; import { Ts7RpcClient } from "./rpc-client.js"; import { TS7_FILE_SYSTEM_CALLBACKS, type Ts7FileSystem } from "./rpc-filesystem.js"; import { spawnTs7Wire } from "./rpc-process.js"; @@ -12,8 +13,15 @@ const { default: getExePath } = require(join(packageRoot, "lib/getExePath.js")) }; /** Platform-package discovery and process creation remain at the host - * boundary. The session itself uses no SDK implementation or Node process. */ + * boundary. The session itself uses no SDK implementation or Node process. + * The executable is tsc-ts's (`hostTscTsExecutable`), or tsgo's with + * `SCRIPTC_CHECKER=tsgo`. */ export function ts7Executable(): string { + return checkerBackend() === "tsgo" ? getExePath() : hostTscTsExecutable(); +} + +/** tsgo's executable, from the `typescript` package, whichever checker compiles. */ +export function tsgoExecutable(): string { return getExePath(); } diff --git a/packages/compiler/src/frontend/ts7/semantic-checker.test.ts b/packages/compiler/src/frontend/ts7/semantic-checker.test.ts index cb05db50c..90a7a25cf 100644 --- a/packages/compiler/src/frontend/ts7/semantic-checker.test.ts +++ b/packages/compiler/src/frontend/ts7/semantic-checker.test.ts @@ -6,10 +6,12 @@ import { API } from "typescript/unstable/sync"; import type { Node as SdkNode } from "typescript/unstable/ast"; import { AstNode } from "./ast-node.js"; import { Ts7RpcClient } from "./rpc-client.js"; -import { ts7Executable } from "./rpc-api.js"; +import { tsgoExecutable } from "./rpc-api.js"; import { spawnTs7Wire } from "./rpc-process.js"; import { Ts7Session } from "./session.js"; import { SyntaxKind } from "./enums.js"; +import { getSourceFileOfNode } from "./node-functions.js"; +import { forEachChild } from "./ast.js"; const source = [ "export class Box { value = 1; }", @@ -45,8 +47,9 @@ beforeAll(() => { files: [file], }), ); + // Compared with the pinned SDK, which runs tsgo. session = new Ts7Session( - new Ts7RpcClient(spawnTs7Wire(ts7Executable(), ["--api", "--cwd", directory])), + new Ts7RpcClient(spawnTs7Wire(tsgoExecutable(), ["--api", "--cwd", directory])), ); oracle = new API({ cwd: directory }); }); @@ -97,15 +100,15 @@ test("synthetic type roots and descendants match the pinned SDK without claiming expect(actual.file.node(1)).toBe(actual); expect(actual.parent).toBeUndefined(); expect(() => actual.file.sourceFile).toThrow("expected a source file root"); - expect(() => actual.getSourceFile()).toThrow("expected a source file root"); - actual.forEachChild((child) => { + expect(() => getSourceFileOfNode(actual)).toThrow("expected a source file root"); + forEachChild(actual, (child) => { expect(child.parent).toBe(actual); }); checked++; } expect(checked).toBe(6); expect(root.file.sourceFile).toBe(root); - expect(root.getSourceFile()).toBe(root); + expect(getSourceFileOfNode(root)).toBe(root); } finally { snapshot.dispose(); sdkSnapshot.dispose(); diff --git a/packages/compiler/src/frontend/ts7/semantic-checker.ts b/packages/compiler/src/frontend/ts7/semantic-checker.ts index 17ca43bf8..92aa77333 100644 --- a/packages/compiler/src/frontend/ts7/semantic-checker.ts +++ b/packages/compiler/src/frontend/ts7/semantic-checker.ts @@ -16,16 +16,32 @@ import { type SemanticQuery, } from "./semantic-model.js"; import { SemanticSymbolFlags } from "./semantic-schema.generated.js"; +import type { TypePredicateKind } from "./enums.generated.js"; -export interface SemanticIndexInfo { - keyType: SemanticType; - valueType: SemanticType; - isReadonly: boolean; - declaration: SemanticNodeHandle | undefined; +/** An index signature of a type. Its declaration resolves on first read: most queries only count a + * type's index infos. */ +export class SemanticIndexInfo { + private declarationNode: AstNode | undefined; + private declarationResolved = false; + + constructor( + readonly keyType: SemanticType, + readonly type: SemanticType, + readonly isReadonly: boolean, + private readonly declarationHandle: SemanticNodeHandle | undefined, + ) {} + + get declaration(): AstNode | undefined { + if (!this.declarationResolved) { + this.declarationNode = this.declarationHandle?.resolve(); + this.declarationResolved = true; + } + return this.declarationNode; + } } export interface SemanticTypePredicate { - kind: number; + kind: TypePredicateKind; parameterIndex: number; parameterName: string | undefined; type: SemanticType | undefined; @@ -382,15 +398,17 @@ export class SemanticChecker { const data = this.project.request("getIndexInfosOfType", { type: type.id, }); - return (data ?? []).map((item) => ({ - keyType: this.project.type(item.keyType), - valueType: this.project.type(item.valueType), - isReadonly: item.isReadonly ?? false, - declaration: - item.declaration === undefined - ? undefined - : new SemanticNodeHandle(item.declaration, this.project), - })); + return (data ?? []).map( + (item) => + new SemanticIndexInfo( + this.project.type(item.keyType), + this.project.type(item.valueType), + item.isReadonly ?? false, + item.declaration === undefined + ? undefined + : new SemanticNodeHandle(item.declaration, this.project), + ), + ); } private getWellKnownSymbols(): WellKnownSymbols { this.project.ensureActive(); diff --git a/packages/compiler/src/frontend/ts7/semantic-model.test.ts b/packages/compiler/src/frontend/ts7/semantic-model.test.ts index e0230e8fe..36a445bc8 100644 --- a/packages/compiler/src/frontend/ts7/semantic-model.test.ts +++ b/packages/compiler/src/frontend/ts7/semantic-model.test.ts @@ -23,7 +23,13 @@ import { isUnionType, type Type, } from "typescript/unstable/sync"; -import { SemanticNodeHandle, SemanticSnapshot, SemanticType } from "./semantic-model.js"; +import { + SemanticNodeHandle, + SemanticSnapshot, + SemanticType, + symbolNameOf, +} from "./semantic-model.js"; +import type { __String } from "./ast-types.js"; import { SemanticObjectFlags as ObjectFlags, SemanticSignatureFlags as SignatureFlags, @@ -97,7 +103,7 @@ test("absent handles and empty known lists do not issue requests", () => { expect(type.getExtendsType()).toBeUndefined(); expect(type.getBaseType()).toBeUndefined(); expect(type.getConstraint()).toBeUndefined(); - expect(type.getTypes()).toBeUndefined(); + expect(type.getTypes()).toEqual([]); expect(type.getBaseTypes()).toBeUndefined(); expect(type.getTypeParameters()).toEqual([]); expect(type.getOuterTypeParameters()).toEqual([]); @@ -304,8 +310,8 @@ test("symbol tables keep escaped keys, display names, canonical context and cach answers.set("getExportSymbolOfSymbol", symbol(3)); const members = parent.getMembers(); const exports = parent.getExports(); - expect(members.get("___x")?.name).toBe("__x"); - expect(exports.get("__x")?.name).toBe("__x"); + expect(symbolNameOf(members.get("___x" as __String))).toBe("__x"); + expect(symbolNameOf(exports.get("__x" as __String))).toBe("__x"); expect(parent.getMembers()).toBe(members); expect(parent.getExports()).toBe(exports); expect(parent.getParent()).toBe(first.symbol(symbol(2))); diff --git a/packages/compiler/src/frontend/ts7/semantic-model.ts b/packages/compiler/src/frontend/ts7/semantic-model.ts index 4920c96ae..5d1bab78e 100644 --- a/packages/compiler/src/frontend/ts7/semantic-model.ts +++ b/packages/compiler/src/frontend/ts7/semantic-model.ts @@ -4,6 +4,8 @@ import type { SemanticTypeData as TypeResponse, } from "./semantic-schema.generated.js"; import { AstNode } from "./ast-node.js"; +import type { __String } from "./ast-types.js"; +import type * as enums from "./enums.generated.js"; import { parseAstNodeHandle } from "./ast-wire.js"; import type { SemanticChecker, SemanticJsDocTag } from "./semantic-checker.js"; import { parseSemanticJson } from "./semantic-json.js"; @@ -290,9 +292,9 @@ export class SemanticNodeHandle { export class SemanticSymbol { readonly id: number; - readonly escapedName: string; - readonly name: string; - readonly flags: number; + /** The key of the member and export tables the symbol is in; `symbolName` gives its name. */ + readonly escapedName: __String; + readonly flags: enums.SymbolFlags; readonly checkFlags: number; /** The declarations' node handles, as the server sends them. */ readonly declarationHandles: SemanticNodeHandle[]; @@ -302,16 +304,15 @@ export class SemanticSymbol { private valueDeclarationResolved = false; private readonly parent: number | undefined; private readonly exportSymbol: number | undefined; - private membersCache: Map | undefined; - private exportsCache: Map | undefined; + private membersCache: Map<__String, SemanticSymbol> | undefined; + private exportsCache: Map<__String, SemanticSymbol> | undefined; constructor( data: SymbolResponse, readonly canonicalProject: SemanticProject, ) { this.id = data.id; - this.escapedName = data.name; - this.name = data.name.startsWith("___") ? data.name.slice(1) : data.name; + this.escapedName = data.name as __String; this.flags = data.flags; this.checkFlags = data.checkFlags; this.declarationHandles = (data.declarations ?? []).map( @@ -362,18 +363,18 @@ export class SemanticSymbol { if (result === undefined) throw new Error(`Missing export symbol for ${this.id}`); return result; } - private table(method: string): Map { - const table = new Map(); + private table(method: string): Map<__String, SemanticSymbol> { + const table = new Map<__String, SemanticSymbol>(); for (const symbol of this.canonicalProject.fetchSymbols(this.id, method)) table.set(symbol.escapedName, symbol); return table; } - getMembers(): Map { + getMembers(): Map<__String, SemanticSymbol> { this.canonicalProject.ensureActive(); if (this.membersCache === undefined) this.membersCache = this.table("getMembersOfSymbol"); return this.membersCache; } - getExports(): Map { + getExports(): Map<__String, SemanticSymbol> { this.canonicalProject.ensureActive(); if (this.exportsCache === undefined) this.exportsCache = this.table("getExportsOfSymbol"); return this.exportsCache; @@ -392,10 +393,26 @@ export class SemanticSymbol { } } +/** The name of `symbol`, as TypeScript 7's API gives it: its escaped name, with the extra `_` of a + * name that starts with `__` removed. */ +export function symbolName(symbol: SemanticSymbol): string { + const name: string = symbol.escapedName; + return name.startsWith("___") ? name.slice(1) : name; +} + +/** `symbolName(symbol)`, or undefined without a symbol. */ +export function symbolNameOf(symbol: SemanticSymbol | null | undefined): string | undefined { + return symbol === undefined || symbol === null ? undefined : symbolName(symbol); +} + /** Type refinements keep the same native object, including its project and * lazy handle caches. Narrowed fields are read from their original storage. */ -export interface SemanticObjectType extends SemanticType { - readonly objectFlags: number; +export type SemanticObjectType = SemanticType; +export interface SemanticTypeParameter extends SemanticType { + readonly isThisType: boolean; +} +export interface SemanticTemplateLiteralType extends SemanticType { + readonly texts: string[]; } export interface SemanticIntrinsicType extends SemanticType { readonly intrinsicName: string; @@ -416,7 +433,7 @@ export interface SemanticBooleanLiteralType extends SemanticLiteralType { readonly value: boolean; } export interface SemanticTupleType extends SemanticObjectType { - readonly elementFlags: number[]; + readonly elementFlags: enums.ElementFlags[]; readonly fixedLength: number; readonly readonly: boolean; } @@ -424,12 +441,14 @@ export interface SemanticTupleType extends SemanticObjectType { /** Kind-specific accessors share one nominal layout. */ export class SemanticType { readonly id: number; - readonly flags: number; - readonly objectFlags: number | undefined; + readonly flags: enums.TypeFlags; + /** The object flags of an object type; 0 for other types, as TypeScript 7's API gives them. */ + readonly objectFlags: enums.ObjectFlags; readonly value: string | number | boolean | bigint | undefined; readonly intrinsicName: string | undefined; + /** Whether a type parameter is the `this` type; undefined for other types. */ readonly isThisType: boolean | undefined; - readonly elementFlags: number[] | undefined; + readonly elementFlags: enums.ElementFlags[] | undefined; readonly fixedLength: number | undefined; readonly readonly: boolean | undefined; readonly texts: string[] | undefined; @@ -444,7 +463,7 @@ export class SemanticType { ) { this.id = data.id; this.flags = data.flags; - this.objectFlags = data.objectFlags; + this.objectFlags = data.objectFlags ?? 0; const value = data.value; if ((data.flags & TypeFlags.BigIntLiteral) !== 0 && value != null) { if (typeof value !== "string") @@ -454,7 +473,8 @@ export class SemanticType { this.value = value ?? undefined; } this.intrinsicName = data.intrinsicName; - this.isThisType = data.isThisType; + this.isThisType = + (data.flags & TypeFlags.TypeParameter) !== 0 ? (data.isThisType ?? false) : undefined; this.elementFlags = data.elementFlags; this.fixedLength = data.fixedLength; this.readonly = data.readonly; @@ -494,26 +514,26 @@ export class SemanticType { getConstraint(): SemanticType | undefined { return this.project.fetchType(this.id, "getConstraintOfType", this.data.substConstraint); } - getTypeParameters(): SemanticType[] { + getTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getTypeParametersOfType", this.data.typeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } - getOuterTypeParameters(): SemanticType[] { + getOuterTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getOuterTypeParametersOfType", this.data.outerTypeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } - getLocalTypeParameters(): SemanticType[] { + getLocalTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getLocalTypeParametersOfType", this.data.localTypeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } getAliasTypeArguments(): SemanticType[] { return this.project.fetchTypes( @@ -522,10 +542,11 @@ export class SemanticType { this.data.aliasTypeArguments ?? [], ); } - getTypes(): SemanticType[] | undefined { + /** The members of a union or intersection, or the types of a template literal type; empty for + * other types. */ + getTypes(): SemanticType[] { this.project.ensureActive(); - if ((this.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) === 0) - return undefined; + if ((this.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral)) === 0) return []; if (this.constituentsPanic !== undefined) throw this.constituentsPanic; if (this.constituents === undefined) { try { @@ -567,7 +588,7 @@ export class SemanticType { return result; } isClassOrInterface(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.ClassOrInterface) !== 0; + return this.isObjectType() && (this.objectFlags & ObjectFlags.ClassOrInterface) !== 0; } isUnionType(): boolean { return (this.flags & TypeFlags.Union) !== 0; @@ -575,7 +596,7 @@ export class SemanticType { isIntersectionType(): boolean { return (this.flags & TypeFlags.Intersection) !== 0; } - isObjectType(): this is SemanticObjectType { + isObjectType(): boolean { return (this.flags & TypeFlags.Object) !== 0; } isIntrinsicType(): this is SemanticIntrinsicType { @@ -600,10 +621,10 @@ export class SemanticType { return (this.flags & TypeFlags.BooleanLiteral) !== 0; } isTypeReference(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.Reference) !== 0; + return this.isObjectType() && (this.objectFlags & ObjectFlags.Reference) !== 0; } - isTupleType(): boolean { - return this.isObjectType() && ((this.objectFlags ?? 0) & ObjectFlags.Tuple) !== 0; + isTupleType(): this is SemanticTupleType { + return this.isObjectType() && (this.objectFlags & ObjectFlags.Tuple) !== 0; } isIndexType(): boolean { return (this.flags & TypeFlags.Index) !== 0; @@ -617,20 +638,20 @@ export class SemanticType { isSubstitutionType(): boolean { return (this.flags & TypeFlags.Substitution) !== 0; } - isTemplateLiteralType(): boolean { + isTemplateLiteralType(): this is SemanticTemplateLiteralType { return (this.flags & TypeFlags.TemplateLiteral) !== 0; } isStringMappingType(): boolean { return (this.flags & TypeFlags.StringMapping) !== 0; } - isTypeParameter(): boolean { + isTypeParameter(): this is SemanticTypeParameter { return (this.flags & TypeFlags.TypeParameter) !== 0; } } export class SemanticSignature { readonly id: number; - readonly flags: number; + readonly flags: enums.SignatureFlags; /** The declaration's node handle, as the server sends it. */ readonly declarationHandle: SemanticNodeHandle | undefined; private declarationNode: AstNode | undefined; @@ -658,12 +679,12 @@ export class SemanticSignature { return this.declarationNode; } - getTypeParameters(): SemanticType[] { + getTypeParameters(): SemanticTypeParameter[] { return this.project.fetchTypes( this.id, "getTypeParametersOfSignature", this.data.typeParameters ?? [], - ); + ) as SemanticTypeParameter[]; } getParameters(): SemanticSymbol[] { return this.project.fetchSymbols( diff --git a/packages/compiler/src/frontend/ts7/semantic-types.ts b/packages/compiler/src/frontend/ts7/semantic-types.ts index b4f3d5f2e..ea43e3885 100644 --- a/packages/compiler/src/frontend/ts7/semantic-types.ts +++ b/packages/compiler/src/frontend/ts7/semantic-types.ts @@ -1,27 +1,40 @@ /** Frontend names over the concrete native client. No SDK object or - * structural cast participates in the production checker boundary. */ + * structural cast participates in the production checker boundary. The + * names follow tsc-ts/api's. */ export type { SemanticType as Type, - SemanticType as InterfaceType, - SemanticType as TypeReference, SemanticType as UnionOrIntersectionType, SemanticType as UnionType, + SemanticType as IntersectionType, + SemanticType as FreshableType, + SemanticType as InterfaceType, + SemanticType as TypeReference, SemanticTupleType as TupleType, // A reference carries its element flags on the target, not necessarily on // itself. Claiming a tuple shape here makes a native presence test constant. - SemanticObjectType as TupleTypeReference, + SemanticType as TupleTypeReference, + SemanticLiteralType as LiteralType, SemanticStringLiteralType as StringLiteralType, SemanticNumberLiteralType as NumberLiteralType, + SemanticBigIntLiteralType as BigIntLiteralType, SemanticBooleanLiteralType as BooleanLiteralType, + SemanticIntrinsicType as IntrinsicType, SemanticObjectType as ObjectType, + SemanticTypeParameter as TypeParameter, + SemanticType as IndexType, + SemanticType as IndexedAccessType, + SemanticType as ConditionalType, + SemanticType as SubstitutionType, + SemanticTemplateLiteralType as TemplateLiteralType, + SemanticType as StringMappingType, SemanticSignature as Signature, SemanticSymbol as Symbol, } from "./semantic-model.js"; +export { symbolName, symbolNameOf } from "./semantic-model.js"; export type { SemanticIndexInfo as IndexInfo, SemanticTypePredicate as TypePredicate, } from "./semantic-checker.js"; -export type { - Ts7CompilerOptionsData as CompilerOptions, - Ts7DiagnosticData as Diagnostic, -} from "./session-schema.generated.js"; +export type { Ts7CompilerOptionsData as CompilerOptions } from "./session-schema.generated.js"; +export type { Ts7Diagnostic as Diagnostic } from "./diagnostic.js"; +export type { Ts7SourceMetadata as SourceFileMetadata } from "./session-schema.generated.js"; diff --git a/packages/compiler/src/frontend/ts7/session.test.ts b/packages/compiler/src/frontend/ts7/session.test.ts index ceaceb113..d957fe0dd 100644 --- a/packages/compiler/src/frontend/ts7/session.test.ts +++ b/packages/compiler/src/frontend/ts7/session.test.ts @@ -11,9 +11,11 @@ import { type Ts7FileSystem, } from "./rpc-filesystem.js"; import { spawnTs7Wire } from "./rpc-process.js"; -import { ts7Executable } from "./rpc-api.js"; +import { tsgoExecutable } from "./rpc-api.js"; import { Ts7Session, Ts7SessionProject, Ts7SessionSnapshot } from "./session.js"; import { AstNode } from "./ast-node.js"; +import { ts7Diagnostic } from "./diagnostic.js"; +import { getTextOfNode } from "./node-functions.js"; const tempRoot = process.platform === "win32" ? tmpdir() : "/tmp"; const protocolPath = (path: string) => path.replaceAll("\\", "/"); @@ -25,9 +27,10 @@ const fallback: Ts7FileSystem = { getAccessibleEntries: () => undefined, }; +// These tests compare the session with the pinned SDK, which runs tsgo, so the session runs tsgo too. function transport(cwd: string, fs: Ts7FileSystem = fallback, timing = false) { const rpc = new Ts7RpcClient( - spawnTs7Wire(ts7Executable(), [ + spawnTs7Wire(tsgoExecutable(), [ "--api", "--cwd", cwd, @@ -103,8 +106,11 @@ test("owned snapshots preserve SDK options, diagnostics, metadata and default-pr "getSuggestionDiagnostics", "getDeclarationDiagnostics", ] as const) { - expect(project.program[name](file), name).toEqual(expected.program[name](file)); - expect(project.program[name](), name).toEqual(expected.program[name]()); + // scriptc's diagnostics have every member set (ts7Diagnostic); the SDK's leave absent ones out. + expect(project.program[name](file), name).toEqual( + expected.program[name](file).map(ts7Diagnostic), + ); + expect(project.program[name](), name).toEqual(expected.program[name]().map(ts7Diagnostic)); } for (const name of [ "getProgramDiagnostics", @@ -288,7 +294,7 @@ test("a released latest snapshot cannot lend stale ASTs to the same project", () source = "export const third = true;"; const third = session.updateSnapshot({ fileChanges: { created: [file] } }); expect(third.getProject(config)!.program.getSourceFile(file)!.text).toBe(source); - expect(old.statements.nodes[0]!.getText()).toBe("export const value = 1;"); + expect(getTextOfNode(old.statements.nodes[0]!)).toBe("export const value = 1;"); } finally { session.close(); rmSync(dir, { recursive: true, force: true }); diff --git a/packages/compiler/src/frontend/ts7/session.ts b/packages/compiler/src/frontend/ts7/session.ts index 4933da9d6..87b97d25e 100644 --- a/packages/compiler/src/frontend/ts7/session.ts +++ b/packages/compiler/src/frontend/ts7/session.ts @@ -1,3 +1,4 @@ +import { ts7Diagnostic, type Ts7Diagnostic } from "./diagnostic.js"; import type { SourceFile } from "./ast-types.js"; import { AstFile, AstNode } from "./ast-node.js"; import { Ts7RpcClient } from "./rpc-client.js"; @@ -314,31 +315,35 @@ export class Ts7SessionProgram { isSourceFileDefaultLibrary(file: AstNode): boolean { return this.getSourceFileMetadataByPath(file.path)?.isDefaultLibrary ?? false; } - private diagnostics(method: string, file?: SemanticDocument): Ts7DiagnosticData[] { - return this.session.request(method, this.query(file)) ?? []; + private diagnostics(method: string, file?: SemanticDocument): Ts7Diagnostic[] { + const result: Ts7Diagnostic[] = []; + for (const data of this.session.request(method, this.query(file)) ?? + []) + result.push(ts7Diagnostic(data)); + return result; } - getSyntacticDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSyntacticDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSyntacticDiagnostics", file); } - getBindDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getBindDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getBindDiagnostics", file); } - getSemanticDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSemanticDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSemanticDiagnostics", file); } - getSuggestionDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getSuggestionDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getSuggestionDiagnostics", file); } - getDeclarationDiagnostics(file?: SemanticDocument): Ts7DiagnosticData[] { + getDeclarationDiagnostics(file?: SemanticDocument): Ts7Diagnostic[] { return this.diagnostics("getDeclarationDiagnostics", file); } - getProgramDiagnostics(): Ts7DiagnosticData[] { + getProgramDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getProgramDiagnostics"); } - getGlobalDiagnostics(): Ts7DiagnosticData[] { + getGlobalDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getGlobalDiagnostics"); } - getConfigFileParsingDiagnostics(): Ts7DiagnosticData[] { + getConfigFileParsingDiagnostics(): Ts7Diagnostic[] { return this.diagnostics("getConfigFileParsingDiagnostics"); } dispose(): void { diff --git a/packages/compiler/src/frontend/ts7/source-parser.test.ts b/packages/compiler/src/frontend/ts7/source-parser.test.ts index 1fae05ff6..b6950b3d5 100644 --- a/packages/compiler/src/frontend/ts7/source-parser.test.ts +++ b/packages/compiler/src/frontend/ts7/source-parser.test.ts @@ -8,6 +8,7 @@ import type { Ts7FileSystem } from "./rpc-filesystem.js"; import { Ts7SourceParser, type Ts7SourceKind } from "./source-parser.js"; import { closeSourceParser, parseSourceFile } from "./source-parser-node.js"; import * as ts from "./syntax.js"; +import { getTextOfNode, tokenStartOfNode } from "./node-functions.js"; const parsers: Ts7SourceParser[] = []; const directories: string[] = []; @@ -46,9 +47,9 @@ test("each parse owns its text and nodes across same-path edits, deletion and cl expect(restored.text).toBe(first.text); parser.close(); parser.close(); - expect(first.statements.nodes[0]!.getText()).toBe("export const first = 1;"); + expect(getTextOfNode(first.statements.nodes[0]!)).toBe("export const first = 1;"); expect(second.statements.nodes[0]!.parent).toBe(second); - expect(other.statements.nodes[0]!.getText()).toBe("exports.third = 3;"); + expect(getTextOfNode(other.statements.nodes[0]!)).toBe("exports.third = 3;"); expect(() => parser.parse("late.ts", "", "ts")).toThrow("closed"); }); @@ -114,9 +115,9 @@ for (const { name, kind, source } of syntaxCases) { expect(file.isDeclarationFile).toBe(oracle.isDeclarationFile); expect( file.statements.nodes.map((statement) => ({ - start: statement.getStart(), + start: tokenStartOfNode(statement), end: statement.end, - text: statement.getText(), + text: getTextOfNode(statement), })), ).toEqual( oracle.statements.map((statement) => ({ @@ -198,7 +199,11 @@ test("JSDoc callable types cannot replace method body views", () => { const expected = oracle.statements[0]!; if (!ts.isClassDeclaration(native) || !ts5.isClassDeclaration(expected)) throw new Error("expected class"); - expect(native.members.nodes.map((member) => member.body?.getText())).toEqual( + expect( + native.members.nodes.map((member) => + member.body === undefined ? undefined : getTextOfNode(member.body), + ), + ).toEqual( expected.members.map((member) => ts5.isMethodDeclaration(member) ? member.body?.getText() : undefined, ), @@ -208,3 +213,30 @@ test("JSDoc callable types cannot replace method body views", () => { expect(member.body?.parent).toBe(member); } }); + +test("a parser starts a new server once it has parsed its source budget, and earlier results stay usable", () => { + const directory = mkdtempSync( + join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-source-parser-"), + ); + directories.push(directory); + const servers: Ts7Api[] = []; + const parser = new Ts7SourceParser( + (options) => { + const api = new Ts7Api(options); + servers.push(api); + return api; + }, + directory, + 64, + ); + parsers.push(parser); + const first = parser.parse("a.ts", `export const a = ${"1 + ".repeat(20)}1;`, "ts"); + expect(servers).toHaveLength(1); + const second = parser.parse("b.ts", "export const b = 2;", "ts"); + expect(servers).toHaveLength(2); + const third = parser.parse("b.ts", "export const b = 3;", "ts"); + expect(servers).toHaveLength(2); + expect(getTextOfNode(first.statements.nodes[0]!)).toBe(`export const a = ${"1 + ".repeat(20)}1;`); + expect(getTextOfNode(second.statements.nodes[0]!)).toBe("export const b = 2;"); + expect(getTextOfNode(third.statements.nodes[0]!)).toBe("export const b = 3;"); +}); diff --git a/packages/compiler/src/frontend/ts7/source-parser.ts b/packages/compiler/src/frontend/ts7/source-parser.ts index 38605cd64..f9029774c 100644 --- a/packages/compiler/src/frontend/ts7/source-parser.ts +++ b/packages/compiler/src/frontend/ts7/source-parser.ts @@ -7,12 +7,21 @@ import { tsgoPath } from "./session-path.js"; export type Ts7SourceKind = "js" | "jsx" | "ts" | "tsx"; export type Ts7SourceApiFactory = (options: { cwd: string; fs: Ts7FileSystem }) => Ts7Api; +/** The source text, in UTF-16 code units, one parser server parses before a + * new server replaces it. tsc-ts keeps the syntax trees it builds for the + * life of its process, so a server that parses many files grows with them; a + * new one starts empty. Results are detached ASTs and outlive the server that + * made them. */ +const SERVER_SOURCE_BUDGET = 256 * 1024; + /** A syntax-only session with a closed, in-memory filesystem. Parsing a * source string never reads imports, project configuration, or libraries * from disk. The returned AST owns its wire data and outlives the session. * Only one project and one input are retained between requests. */ export class Ts7SourceParser { - private readonly api: Ts7Api; + private api: Ts7Api; + private readonly createApi: Ts7SourceApiFactory; + private parsedSource = 0; private readonly files = new Map(); private readonly configPath: string; private previousPath: string | undefined; @@ -21,10 +30,19 @@ export class Ts7SourceParser { private closed = false; private readonly cwd: string; - constructor(createApi: Ts7SourceApiFactory, cwd = process.cwd()) { + constructor( + createApi: Ts7SourceApiFactory, + cwd = process.cwd(), + private readonly sourceBudget = SERVER_SOURCE_BUDGET, + ) { this.cwd = resolve(cwd); this.configPath = tsgoPath(resolve(this.cwd, "__scriptc-source-parser.tsconfig.json")); - this.api = createApi({ + this.createApi = createApi; + this.api = this.connect(); + } + + private connect(): Ts7Api { + return this.createApi({ cwd: this.cwd, fs: { readFile: (path) => this.files.get(tsgoPath(path)) ?? null, @@ -61,7 +79,14 @@ export class Ts7SourceParser { this.previousFile !== undefined ) return this.previousFile; - const previousPath = this.previousPath; + let previousPath = this.previousPath; + if (this.parsedSource >= this.sourceBudget) { + this.api.close(); + this.api = this.connect(); + this.parsedSource = 0; + previousPath = undefined; + } + this.parsedSource += source.length; this.files.clear(); this.files.set(path, source); this.files.set( diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 2f054fb71..02f2f4cfb 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -6,6 +6,7 @@ import type { IrRecordShape, IrType, IrUnionDef, IrUnionDiscriminant } from "../ import { BYTES_ELEMENT_NAME, arrayOf, + isDynTypedRefType, BOOL, bytesOf, canConvertToDyn, @@ -68,7 +69,7 @@ export function jsOpenObjectType( shapes: ShapeRegistry, unions: UnionRegistry, ): IrType { - if (!decl || !isJsSourceFile(decl.getSourceFile())) return type; + if (!decl || !isJsSourceFile(ts.getSourceFileOfNode(decl))) return type; const arms = type.kind === "union" ? unions.get(type.unionId)?.arms : [type]; const present = arms?.filter((arm) => arm.kind !== "undefinedT"); const parameter = ts.isParameter(decl) @@ -125,6 +126,14 @@ export function jsOpenObjectType( : type; } +/** The modules whose RequestOptions interface maps to the checked dynamic. */ +const HTTP_REQUEST_OPTION_MODULES: ReadonlySet = new Set([ + "http", + "node:http", + "https", + "node:https", +]); + /** The WebCrypto interfaces whose values are checked-dynamic: the crypto * global, SubtleCrypto, CryptoKey handles and the algorithm dictionaries * (scr_webcrypto.c). */ @@ -325,7 +334,7 @@ export class ShapeRegistry { ) { return; } - shape.declaredOrder = declaredOrder; + shape.declaredOrder = this.withExtendedNames(shape.id, declaredOrder); this.declaredOrderPriority.set(shape.id, this.currentDeclaredOrderPriority); } @@ -341,7 +350,7 @@ export class ShapeRegistry { } const shape = this.byId.get(shapeId); if (!shape) continue; - shape.declaredOrder = best.order; + shape.declaredOrder = this.withExtendedNames(shapeId, best.order); this.declaredOrderPriority.set(shapeId, best.priority); } for (const finalize of this.derivedDeclaredOrderFinalizers) finalize(); @@ -374,7 +383,7 @@ export class ShapeRegistry { // still obeys first-writer-wins. if (shape?.declaredOrder !== originalOrder) return; const next = order(); - if (next !== undefined) shape.declaredOrder = next; + if (next !== undefined) shape.declaredOrder = this.withExtendedNames(shape.id, next); }; } @@ -482,6 +491,53 @@ export class ShapeRegistry { get(shapeId: string): IrRecordShape | undefined { return this.byId.get(shapeId); } + + /** Names extendShape added to a shape, which every key-order table the + * shape adopts lists after its declared names. */ + private readonly extendedNames = new Map(); + + /** The members extendShape attached to a shape (empty for most). */ + attachedNames(shapeId: string): readonly string[] { + return this.extendedNames.get(shapeId) ?? []; + } + + private withExtendedNames(shapeId: string, order: string[]): string[] { + const extra = this.extendedNames.get(shapeId); + if (!extra) return order; + const missing = extra.filter((name) => !order.includes(name)); + return missing.length === 0 ? order : [...order, ...missing]; + } + + /** Adds optional fields to an interned shape in place: members a type + * assertion attaches to the shape's values (`r as R & { tag: string }` + * followed by `view.tag = ...`; see assertion-members.ts). Every value + * of the shape holds them absent until a write through such a view, so + * the shape keeps its id, both structural keys resolve to it, and its + * key-order table lists the new names after the declared ones. Runs + * before any body lowers; existing layouts change, so the revision + * advances like a closing recursive placeholder. */ + extendShape(shapeId: string, extra: readonly { name: string; type: IrType }[]): void { + const shape = this.byId.get(shapeId); + if (!shape || shape.tuple || extra.length === 0) return; + const fresh = extra.filter((f) => !shape.fields.some((x) => x.name === f.name)); + if (fresh.length === 0) return; + shape.fields = [...shape.fields, ...fresh].sort((a, b) => + a.name < b.name ? -1 : a.name > b.name ? 1 : 0, + ); + this.extendedNames.set(shapeId, [ + ...(this.extendedNames.get(shapeId) ?? []), + ...fresh.map((f) => f.name), + ]); + if (shape.declaredOrder) + shape.declaredOrder = this.withExtendedNames(shapeId, shape.declaredOrder); + const candidates = this.declaredOrderCandidates.get(shapeId); + if (candidates) { + for (const c of candidates) c.order = this.withExtendedNames(shapeId, c.order); + } + this.revision++; + const key = this.keyOf(shape.fields, false, shape.indexValue); + if (!this.byKey.has(key)) this.byKey.set(key, shapeId); + } } /** The frontend's union interner — mirrors ShapeRegistry. A union's @@ -924,7 +980,7 @@ function isDeclaredInAmbientModule(d: ts.Declaration, name: string): boolean { /** True when the declaration's nearest enclosing namespace is `name` * (`declare global { namespace NodeJS { ... } }` — the NodeJS-global * interfaces @types/node declares outside any ambient module). */ -function isDeclaredInAmbientNamespace(d: ts.Declaration, name: string): boolean { +export function isDeclaredInAmbientNamespace(d: ts.Declaration, name: string): boolean { let node: ts.Node | undefined = d.parent; while (node) { if (ts.isModuleDeclaration(node) && ts.isIdentifier(node.name)) { @@ -935,6 +991,31 @@ function isDeclaredInAmbientNamespace(d: ts.Declaration, name: string): boolean return false; } +/** Whether `type` is one of the stream spellings the general stream + * representation (TypeMapperCtx.generalNodeStreams) maps differently: + * NodeJS.ReadableStream / NodeJS.WritableStream, @types/node's + * stream.Readable / stream.Writable, or the fallback declarations' + * ChildStdin. */ +export function isGeneralNodeStreamInterface( + type: ts.Type, + ctx: Pick, +): boolean { + const psym = type.getSymbol() ?? type.getAliasSymbol(); + if (psym === undefined) return false; + return psym.declarations.some((d) => { + if (!(ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d))) return false; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(d))) return false; + if (ts.symbolName(psym) === "ReadableStream" || ts.symbolName(psym) === "WritableStream") + return ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"); + if (ts.symbolName(psym) === "ChildStdin") return isDeclaredInAmbientModule(d, "child_process"); + return ( + (ts.symbolName(psym) === "Readable" || ts.symbolName(psym) === "Writable") && + isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) && + isDeclaredInAmbientModule(d, "stream") + ); + }); +} + /** A type declared in the shipped WebAssembly namespace (scriptc.d.ts): * its modules, instances, memories, tables, globals, error classes and the * Exports/Imports records. */ @@ -946,7 +1027,7 @@ export function isWebAssemblyType( if (!symbol) return false; return symbol.declarations.some( (d) => - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d as ts.Declaration, "WebAssembly"), ); } @@ -1090,6 +1171,19 @@ export interface TypeMapperCtx { * type descriptions for project-owned values, but their imported runtime * bindings are fenced separately by the Lowerer. */ isExternalTypeFile: (sf: ts.SourceFile) => boolean; + /** The program's general stream representation (lowerToIr decides it + * per program): NodeJS.ReadableStream / NodeJS.WritableStream and + * @types/node's stream.Readable / stream.Writable map to the runtime + * stream classes instead of the child/process stdio handles, so values + * from every stream producer share their slots. Off (the default), the + * stdio handles keep those spellings. */ + generalNodeStreams?: boolean; + /** With generalNodeStreams, the program's checked-dynamic stream slots + * (lowerToIr decides it per program, dyn-streams.ts): NodeJS.ReadableStream + * and NodeJS.WritableStream map to the checked-dynamic representation, so + * a slot holds a native stream (a boxed runtime stream, identity kept) or + * a JavaScript stream object. Members dispatch per value at run time. */ + dynNodeStreams?: boolean; /** Successful context-free mappings, keyed by the * TypeScript server's stable type id. A generated facade can reference the * same large record/function type thousands of times; remapping its full @@ -1221,7 +1315,7 @@ export function isLegacyUrlSymbol( ): boolean { if (!symbol) return false; for (const d of symbol.declarations) { - if (!ctx.isStdlibFile(d.getSourceFile())) continue; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(d))) continue; let family = false; for (let n: ts.Node | undefined = d; n; n = n.parent) { if (ts.isInterfaceDeclaration(n) && LEGACY_URL_TYPES.has(n.name.text)) family = true; @@ -1261,7 +1355,10 @@ export function isOsConstantsSymbol( const alias = types && ts.isTypeAliasDeclaration(d) && d.name.text === "SignalConstants"; const signals = !types && ts.isVariableDeclaration(d) && ts.isIdentifier(d.name) && d.name.text === "signals"; - if (!(alias || signals || ts.isModuleDeclaration(d)) || !ctx.isStdlibFile(d.getSourceFile())) + if ( + !(alias || signals || ts.isModuleDeclaration(d)) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(d)) + ) continue; let inConstants = ts.isModuleDeclaration(d) && d.name.text === "constants"; for (let n: ts.Node | undefined = d.parent; n; n = n.parent) { @@ -1284,14 +1381,14 @@ function isWorkerHandleSymbol( ctx: Pick, ): boolean { return ( - (symbol?.name === "Worker" || - symbol?.name === "MessagePort" || - symbol?.name === "MessageChannel" || - symbol?.name === "BroadcastChannel") && + ((symbol !== undefined && ts.symbolName(symbol) === "Worker") || + (symbol !== undefined && ts.symbolName(symbol) === "MessagePort") || + (symbol !== undefined && ts.symbolName(symbol) === "MessageChannel") || + (symbol !== undefined && ts.symbolName(symbol) === "BroadcastChannel")) && symbol.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "worker_threads"), ) ); @@ -1299,6 +1396,137 @@ function isWorkerHandleSymbol( /** The `{ message: any }` object type (or its union with undefined) that * worker_threads declares as receiveMessageOnPort's result. */ +/** A union of dns lookup option bags with numbers and nullish values that + * includes LookupAllOptions (whose `all: true` arm has no record form). */ +function isLookupOptionsUnion(type: ts.Type, ctx: Pick): boolean { + if (!type.isUnionType()) return false; + let all = false; + for (const member of ts.constituentTypes(type)) { + if (member.flags & (ts.TypeFlags.NumberLike | ts.TypeFlags.Null | ts.TypeFlags.Undefined)) + continue; + const symbol = member.getSymbol(); + if ( + symbol === undefined || + !/^Lookup(One|All)?Options$/.test(ts.symbolName(symbol)) || + !symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) + ) + return false; + if (ts.symbolName(symbol) === "LookupAllOptions") all = true; + } + return all; +} + +/** The binary and stream members of fetch's BodyInit (undici-types): + * NodeJS.ArrayBufferView expands to the typed arrays and DataView. */ +const FETCH_BODY_MEMBERS: ReadonlySet = new Set([ + "ArrayBuffer", + "SharedArrayBuffer", + "AsyncIterable", + "Iterable", + "Blob", + "FormData", + "URLSearchParams", + "ReadableStream", + "DataView", + "Buffer", + "Int8Array", + "Uint8Array", + "Uint8ClampedArray", + "Int16Array", + "Uint16Array", + "Int32Array", + "Uint32Array", + "Float16Array", + "Float32Array", + "Float64Array", + "BigInt64Array", + "BigUint64Array", +]); + +/** fetch's request body type, BodyInit, however TypeScript spells it + * (`RequestInit["body"]`, `NonNullable<...>`, an optional member): a + * union of strings, the stdlib binary and stream types and nullish + * values that includes both FormData and URLSearchParams. Native fetch + * reads such a body at run time, so it rides checked-dynamic storage. */ +function isFetchBodyUnion(type: ts.Type, ctx: Pick): boolean { + if (!type.isUnionType()) return false; + let formData = false; + let searchParams = false; + for (const member of ts.constituentTypes(type)) { + if (member.flags & (ts.TypeFlags.StringLike | ts.TypeFlags.Null | ts.TypeFlags.Undefined)) + continue; + const symbol = member.getSymbol(); + if ( + symbol === undefined || + !FETCH_BODY_MEMBERS.has(ts.symbolName(symbol)) || + !symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) + ) + return false; + if (ts.symbolName(symbol) === "FormData") formData = true; + if (ts.symbolName(symbol) === "URLSearchParams") searchParams = true; + } + return formData && searchParams; +} + +/** A mapped object type (Omit, Pick, Partial) whose members all come from + * one stdlib interface that maps to the checked dynamic. */ +function isDynInterfaceSubset(type: ts.Type, ctx: TypeMapperCtx): boolean { + if (!(type.flags & ts.TypeFlags.Object) || type.isClassOrInterface()) return false; + const alias = type.getAliasSymbol(); + if (alias === undefined) return false; + const props = ctx.checker.getPropertiesOfType(type); + if (props.length === 0) return false; + let owner: ts.InterfaceDeclaration | undefined; + for (const prop of props) { + const decl = prop.declarations[0]; + const parent = decl?.parent; + if ( + parent === undefined || + !ts.isInterfaceDeclaration(parent) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(parent)) || + (owner !== undefined && owner !== parent) + ) + return false; + owner = parent; + } + return ( + owner !== undefined && mapType(ctx.checker.getTypeAtLocation(owner.name), ctx)?.kind === "dyn" + ); +} + +/** fetch's HeadersInit (undici-types): a union of the stdlib Headers + * class with header-pair arrays and/or a header record, plus nullish + * values. Native fetch reads it at run time like the init around it. */ +function isFetchHeadersUnion(type: ts.Type, ctx: Pick): boolean { + if (!type.isUnionType()) return false; + let headers = false; + let other = false; + for (const member of ts.constituentTypes(type)) { + if (member.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) continue; + const symbol = member.getSymbol(); + if ( + symbol !== undefined && + ts.symbolName(symbol) === "Headers" && + symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) + ) { + headers = true; + continue; + } + const alias = ts.symbolNameOf(member.getAliasSymbol()); + if ( + (symbol !== undefined && ts.symbolName(symbol) === "Array") || + (symbol !== undefined && ts.symbolName(symbol) === "ReadonlyArray") || + alias === "Record" || + alias === "HeaderRecord" + ) { + other = true; + continue; + } + return false; + } + return headers && other; +} + function isReceiveMessageResult( type: ts.Type, ctx: Pick, @@ -1316,7 +1544,7 @@ function isReceiveMessageResult( !fn || !ts.isFunctionDeclaration(fn) || fn.name?.text !== "receiveMessageOnPort" || - !ctx.isStdlibFile(decl.getSourceFile()) || + !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) || !isDeclaredInAmbientModule(fn, "worker_threads") ) { return false; @@ -1360,9 +1588,9 @@ export function mapAsyncGeneratorAnnotation(type: ts.Type, ctx: TypeMapperCtx): const symbol = type.getSymbol(); if ( !symbol || - !["AsyncIterator", "AsyncIterableIterator", "AsyncIterable"].includes(symbol.name) || + !["AsyncIterator", "AsyncIterableIterator", "AsyncIterable"].includes(ts.symbolName(symbol)) || !symbol.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return null; @@ -1391,11 +1619,13 @@ export function isFetchHandleType( const symbol = member.getSymbol(); if ( !symbol || - (symbol.name !== "Headers" && symbol.name !== "Request" && symbol.name !== "Response") || + (ts.symbolName(symbol) !== "Headers" && + ts.symbolName(symbol) !== "Request" && + ts.symbolName(symbol) !== "Response") || !symbol.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return false; @@ -1466,12 +1696,36 @@ function classExprNeedsContext(decl: ts.ClassLikeDeclaration): boolean { return false; } +/** True for the type of a TypeScript object literal with no contextual + * type (`const options = { ...location, path }`) that spreads a value + * mapping to the checked dynamic (a stdlib options type such as + * http.RequestOptions). A literal with a contextual type is built in that + * type's representation instead, and JavaScript literals keep their own + * rules. */ +function spreadsCheckedDynamic(symbol: ts.Symbol | undefined, ctx: TypeMapperCtx): boolean { + if (symbol === undefined || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return false; + const decl = symbol.declarations[0]; + if (decl === undefined || !ts.isObjectLiteralExpression(decl)) return false; + if (isJsSourceFile(ts.getSourceFileOfNode(decl))) return false; + if (ctx.checker.getContextualType(decl)) return false; + return decl.properties.nodes.some( + (prop) => + ts.isSpreadAssignment(prop) && + mapType(ctx.checker.getTypeAtLocation(prop.expression), ctx)?.kind === "dyn", + ); +} + function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const { checker, unions, classNamer, resolveTypeParam } = ctx; // TypeScript's intrinsic globalThis symbol has no declaration. A stored // reference needs native object identity, including var's undefined state. const intrinsic = type.getSymbol(); - if (intrinsic?.name === "globalThis" && intrinsic.declarations.length === 0) return DYN; + if ( + intrinsic !== undefined && + ts.symbolName(intrinsic) === "globalThis" && + intrinsic.declarations.length === 0 + ) + return DYN; if (ctx.jsDynamicDeclaration !== undefined && intrinsic !== undefined) { const objectLiteral = (intrinsic.flags & ts.SymbolFlags.ObjectLiteral) !== 0; const classInstance = @@ -1486,17 +1740,33 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const moduleId = ctx.moduleNamespaceId?.(type) ?? null; if (moduleId !== null) return { kind: "moduleNs", moduleId }; } + // A TypeScript object literal that spreads a checked-dynamic value + // (`{ ...requestOptions, path, timeout }`): its keys are the source's + // runtime keys, which no record shape holds, so the literal rides + // checked-dynamic storage like its source. + if (spreadsCheckedDynamic(intrinsic, ctx)) return DYN; // worker_threads.receiveMessageOnPort's `{ message: any } | undefined`: // the runtime's { message } object, a checked-dynamic value. if (isReceiveMessageResult(type, ctx)) return DYN; + if (isFetchBodyUnion(type, ctx) || isFetchHeadersUnion(type, ctx)) return DYN; + // Omit/Pick/Partial of a stdlib interface that rides checked-dynamic + // storage (`Omit`): every member comes + // from that interface, so the value is a subset of the same runtime-read + // options object and rides the same storage. + if (isDynInterfaceSubset(type, ctx)) return DYN; + // The lookup-function option union (`LookupOneOptions | LookupAllOptions + // | number | undefined`): with a LookupAllOptions arm it is an option bag + // dns.lookup reads at run time, so it rides checked-dynamic storage. + if (isLookupOptionsUnion(type, ctx)) return DYN; // @types/node spells child_process.ExecFileException as an intersection // type alias over two Error-derived metadata interfaces. The value is // still one ordinary Error; recognize the owned alias before generic // intersection mapping decomposes it into unsupported utility types. const directAlias = type.getAliasSymbol(); if ( - directAlias?.name === "ExecFileException" && - directAlias.declarations.some((decl) => ctx.isStdlibFile(decl.getSourceFile())) + directAlias !== undefined && + ts.symbolName(directAlias) === "ExecFileException" && + directAlias.declarations.some((decl) => ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) ) { return { kind: "object", className: "%Error" }; } @@ -1505,11 +1775,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // ordinary Error as well. const execException = type.getSymbol(); if ( - execException?.name === "ExecException" && + execException !== undefined && + ts.symbolName(execException) === "ExecException" && execException.declarations.some( (decl) => ts.isInterfaceDeclaration(decl) && - ctx.isStdlibFile(decl.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) && isDeclaredInAmbientModule(decl, "child_process"), ) ) { @@ -1524,6 +1795,18 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // option) holds that dynamic value too, so the whole union is dynamic. if (type.isUnionType() && ts.constituentTypes(type).some((t) => isWebAssemblyType(t, ctx))) return DYN; + // JavaScript inference merges WebAssembly.instantiate's overloads into + // `Promise & Promise`: a promise + // of a WebAssembly value either way. + if ( + type.isIntersectionType() && + ts.constituentTypes(type).every((t) => { + if (ts.symbolNameOf(t.getSymbol()) !== "Promise") return false; + const arg = ctx.checker.getTypeArguments(t as ts.TypeReference)[0]; + return arg !== undefined && isWebAssemblyType(arg, ctx); + }) + ) + return { kind: "promise", inner: DYN }; // Immutable (@scriptc/types) is a deep-readonly view: the value is // still the T it maps. const immutableSource = immutableSourceType(type, ctx); @@ -1537,12 +1820,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (ctx.erasingSignature || ctx.isErasing?.()) && !(type.flags & ts.TypeFlags.TypeParameter) && !checker.getCallSignatures(type).some((sig) => sig === ctx.erasingSignature) && - !ERASURE_KEPT_CONTAINERS.has(type.getSymbol()?.name ?? "") && + !ERASURE_KEPT_CONTAINERS.has(ts.symbolNameOf(type.getSymbol()) ?? "") && mentionsErasedTypeParam(type, ctx, 0) ) { contextResolutions++; return DYN; } + // Readonly over a program class (Object.freeze's result type) is the + // same instance. + const readonlyClass = readonlyClassType(type, ctx); + if (readonlyClass !== null) return readonlyClass; if (resolveTypeParam && type.flags & ts.TypeFlags.TypeParameter) { const bound = resolveTypeParam(type); if (bound) { @@ -1581,6 +1868,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { contextResolutions++; return viaIndexed; } + // `Parameters` / `ReturnType` over a bound function-typed + // parameter (variadic higher-order wrappers): the checker keeps both + // conditionals symbolic inside the body, and their `infer` positions + // match a call signature, which matchInferReference cannot decide. + const viaSignature = + mapBoundSignatureAlias(type, ctx) ?? mapBoundKeyofNeverConditional(type, ctx); + if (viaSignature !== null) { + contextResolutions++; + return viaSignature; + } // A conditional type over a bound parameter (`Infer`, a // mapped type's member): evaluate it against the bindings. if (type.isConditionalType()) { @@ -1606,8 +1903,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // constructors. Only the standard library's interface gets this ABI. const errorOptionsSymbol = widened.getSymbol(); if ( - errorOptionsSymbol?.name === "ErrorOptions" && - errorOptionsSymbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + errorOptionsSymbol !== undefined && + ts.symbolName(errorOptionsSymbol) === "ErrorOptions" && + errorOptionsSymbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return DYN; } @@ -1622,7 +1920,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const parseArgsSym = widened.getAliasSymbol() ?? widened.getSymbol(); const belongs = (declaration: ts.Node): boolean => { - if (!ctx.isStdlibFile(declaration.getSourceFile())) return false; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(declaration))) return false; let family = false; for (let node: ts.Node | undefined = declaration; node; node = node.parent) { if ( @@ -1638,7 +1936,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { }; if ( parseArgsSym && - PARSE_ARGS_DYN_TYPES.has(parseArgsSym.name) && + PARSE_ARGS_DYN_TYPES.has(ts.symbolName(parseArgsSym)) && parseArgsSym.declarations.some(belongs) ) return DYN; @@ -1658,10 +1956,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const boxSym = widened.getSymbol(); if ( boxSym && - (boxSym.name === "Number" || boxSym.name === "String" || boxSym.name === "Boolean") && - boxSym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + (ts.symbolName(boxSym) === "Number" || + ts.symbolName(boxSym) === "String" || + ts.symbolName(boxSym) === "Boolean") && + boxSym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { - return boxSym.name === "Number" ? F64 : boxSym.name === "String" ? STRING : BOOL; + return ts.symbolName(boxSym) === "Number" + ? F64 + : ts.symbolName(boxSym) === "String" + ? STRING + : BOOL; } } // The lib's PRIMITIVE-CONSTRUCTOR interfaces as TYPES (`typeof String`, @@ -1679,16 +1983,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const ctorSym = widened.getSymbol(); if ( ctorSym && - (ctorSym.name === "StringConstructor" || - ctorSym.name === "NumberConstructor" || - ctorSym.name === "BooleanConstructor") && - ctorSym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + (ts.symbolName(ctorSym) === "StringConstructor" || + ts.symbolName(ctorSym) === "NumberConstructor" || + ts.symbolName(ctorSym) === "BooleanConstructor") && + ctorSym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return funcOf( [STRING], - ctorSym.name === "StringConstructor" + ts.symbolName(ctorSym) === "StringConstructor" ? STRING - : ctorSym.name === "NumberConstructor" + : ts.symbolName(ctorSym) === "NumberConstructor" ? F64 : BOOL, ); @@ -1792,7 +2096,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declaration file. { const classSym = widened.getSymbol(); - const name = classSym?.name; + const name = ts.symbolNameOf(classSym); if (name === "LifetimeRegion" || name === "InternTable" || name === "ThreadSlots") { for (const d of classSym!.declarations) { if (!ts.isClassDeclaration(d)) continue; @@ -1820,7 +2124,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return decls && decls.length > 0 && decls.every((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isStdlibFile(sf) && !ctx.isExternalTypeFile(sf); }) ? decls @@ -1830,7 +2134,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { decls === null || decls.every( (d) => - packageTypeFile(d.getSourceFile(), ctx) && + packageTypeFile(ts.getSourceFileOfNode(d), ctx) && (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d) || ts.isTypeLiteralNode(d) || @@ -2039,14 +2343,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Nameless class DECLARATIONS are legal only as `export default class // {}` — classNamer spells them "%anon" (unique per file), the same // name their collection registered. - !classDecl.getSourceFile().isDeclarationFile && - checker.getConstructSignatures(widened).length === 0 + !ts.getSourceFileOfNode(classDecl).isDeclarationFile && + checker.getConstructSignatures(widened).length === 0 && + // A TypeScript class declared OUTSIDE the lowered program (reached + // through `import type` alone) never registers, and the program can + // hold no instance of it: its instance type maps below as the + // structural type it describes, like an interface. + (ctx.isProgramFile(ts.getSourceFileOfNode(classDecl)) || + isJsSourceFile(ts.getSourceFileOfNode(classDecl))) ) { - // Declared OUTSIDE the lowered program (reached through the type world - // alone — jsdoc `typeof import()` over a never-required module): the - // class will never register, so the instance type is unmappable, not - // an object type naming a struct that cannot exist. - if (!ctx.isProgramFile(classDecl.getSourceFile())) return null; + // A JavaScript class outside the lowered program (jsdoc `typeof + // import()` over a never-required module) stays unmappable: JSDoc + // types are not checked, so its slots fall back to the dynamic value. + if (!ctx.isProgramFile(ts.getSourceFileOfNode(classDecl))) return null; // The class inside a MIXIN function: per-call-site instantiations // share this one node — the CURRENT instantiation (collecting or // lowering) is the answer; outside any mixin context the node's type @@ -2090,10 +2399,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if ( classDecl && (ts.isClassDeclaration(classDecl) || ts.isClassExpression(classDecl)) && - !classDecl.getSourceFile().isDeclarationFile && + !ts.getSourceFileOfNode(classDecl).isDeclarationFile && checker.getConstructSignatures(widened).length > 0 ) { - if (!ctx.isProgramFile(classDecl.getSourceFile())) return null; + if (!ctx.isProgramFile(ts.getSourceFileOfNode(classDecl))) return null; // The MIXIN class node's static side (`typeof C` inside the mixin): // the current instantiation's classval, like the instance type above. { @@ -2159,9 +2468,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // naming the offending half, as does the `new Map` lowering per site. const psym = widened.getSymbol(); const isStdlibInterface = (name: string): boolean => - psym?.name === name && + psym !== undefined && + ts.symbolName(psym) === name && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); // The builtin Error classes: references to the lib's Error interfaces map to the runtime-provided class // hierarchy (provenance, not the name — a user's own `class Error` @@ -2175,8 +2485,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // node:assert's AssertionError is declared as a class (in the shipped // fallback and in @types/node), not as a lib interface. if ( - psym?.name === "AssertionError" && - psym.declarations.some((d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(d.getSourceFile())) + psym !== undefined && + ts.symbolName(psym) === "AssertionError" && + psym.declarations.some( + (d) => ts.isClassDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), + ) ) return { kind: "object", className: "%AssertionError" }; // The lib's `Object` interface is a TOP type: tsc admits every @@ -2193,12 +2506,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // representation. The node:module class's merged instance interface is // included, while unrelated user interfaces named Module stay structural. if ( - (psym?.name === "ScriptcModule" || psym?.name === "Module") && + ((psym !== undefined && ts.symbolName(psym) === "ScriptcModule") || + (psym !== undefined && ts.symbolName(psym) === "Module")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && - (psym.name === "ScriptcModule" || + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + (ts.symbolName(psym) === "ScriptcModule" || isDeclaredInAmbientNamespace(d, "NodeJS") || isDeclaredInAmbientModule(d, "module")), ) @@ -2213,11 +2527,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // NodeJS.EventEmitter interface (the structural surface net.Socket // et al. extend) is the same runtime value. Provenance-checked. if ( - psym?.name === "EventEmitter" && + psym !== undefined && + ts.symbolName(psym) === "EventEmitter" && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "events") || isDeclaredInAmbientNamespace(d, "NodeJS")), ) ) { @@ -2228,11 +2543,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // precedent). The NAME is generic, so the provenance check adds the // enclosing ambient module, like net.Server. if ( - psym?.name === "Interface" && + psym !== undefined && + ts.symbolName(psym) === "Interface" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "readline") || isDeclaredInAmbientModule(d, "readline/promises")), ) @@ -2243,11 +2559,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // it keeps Node's own shape (an undefined name, the escape sequences' // code member). if ( - psym?.name === "Key" && + psym !== undefined && + ts.symbolName(psym) === "Key" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "readline") || isDeclaredInAmbientModule(d, "readline/promises")), ) @@ -2256,11 +2573,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { } // readline/promises.Readline: an f64 handle the same way. if ( - psym?.name === "Readline" && + psym !== undefined && + ts.symbolName(psym) === "Readline" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "readline/promises"), ) ) { @@ -2273,11 +2591,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // plus the ambient module suffices for both the fallback declarations // and @types/node's class Channel. if ( - psym?.name === "Channel" && + psym !== undefined && + ts.symbolName(psym) === "Channel" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "diagnostics_channel"), ) ) { @@ -2287,11 +2606,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // the runtime's store id space (the Channel story — stores are // process-lived; contexts ride the fiber machinery). if ( - psym?.name === "AsyncLocalStorage" && + psym !== undefined && + ts.symbolName(psym) === "AsyncLocalStorage" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "async_hooks"), ) ) { @@ -2300,11 +2620,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // diagnostics_channel.TracingChannel: the same f64-handle story over the // runtime's tracing registry (five event channels per entry). if ( - psym?.name === "TracingChannel" && + psym !== undefined && + ts.symbolName(psym) === "TracingChannel" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "diagnostics_channel"), ) ) { @@ -2314,7 +2635,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const encodeResult = psym && psym.declarations.some((decl) => { - if (!ctx.isStdlibFile(decl.getSourceFile())) return false; + if (!ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) return false; if (ts.isInterfaceDeclaration(decl) && decl.name.text === "EncodeIntoResult") return true; const parent = decl.parent; return ( @@ -2339,19 +2660,20 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // WHATWG codec instances have ordinary reference identity and ownership. // Their private encoding slot is omitted from enumeration and JSON. if ( - (psym?.name === "TextEncoder" || psym?.name === "TextDecoder") && + ((psym !== undefined && ts.symbolName(psym) === "TextEncoder") || + (psym !== undefined && ts.symbolName(psym) === "TextDecoder")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "record", shapeId: ctx.shapes.intern( [ - { name: `%${psym.name}`, type: F64 }, - ...(psym.name === "TextDecoder" + { name: `%${ts.symbolName(psym)}`, type: F64 }, + ...(ts.symbolName(psym) === "TextDecoder" ? [ { name: "%fatal", type: BOOL }, { name: "%ignoreBOM", type: BOOL }, @@ -2373,11 +2695,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // special-cased in lowerNew / lowerStringDecoderMethodCall, so the // record shape never surfaces. if ( - psym?.name === "StringDecoder" && + psym !== undefined && + ts.symbolName(psym) === "StringDecoder" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { @@ -2402,14 +2725,14 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { if ( flags & ts.TypeFlags.Object && promiseDeclarations.length > 0 && - promiseDeclarations.every((d) => d.getSourceFile().isDeclarationFile) + promiseDeclarations.every((d) => ts.getSourceFileOfNode(d).isDeclarationFile) ) { const props = checker.getPropertiesOfType(widened); if ( props.length === 3 && - ["promise", "resolve", "reject"].every((n) => props.some((p) => p.name === n)) + ["promise", "resolve", "reject"].every((n) => props.some((p) => ts.symbolName(p) === n)) ) { - const promProp = props.find((p) => p.name === "promise")!; + const promProp = props.find((p) => ts.symbolName(p) === "promise")!; const promT = mapType(checker.getTypeOfSymbol(promProp), ctx); if (promT?.kind === "promise") { const inner = promT.inner; @@ -2449,6 +2772,28 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // spawn consumes a presence-sensitive option bag, including optional // stdio and env values. Keep it checked-dynamic for native normalization. if (isStdlibInterface("SpawnOptions")) return DYN; + // dns.LookupAllOptions (`all: true`, the overload whose callback takes + // an address list): a lookup option bag dns.lookup reads at run time. + // It rides checked-dynamic storage, so the lookup function type that + // takes `LookupOneOptions | LookupAllOptions | number` compiles. + if (isStdlibInterface("LookupAllOptions")) return DYN; + // http.RequestOptions / https.RequestOptions as a declared type: an + // option record whose members include functions, agents and signals. + // Values ride checked-dynamic storage, like SpawnOptions. + if ( + psym !== undefined && + ts.symbolName(psym) === "RequestOptions" && + psym.declarations.some((d) => { + if (!ts.isInterfaceDeclaration(d) || !ctx.isStdlibFile(ts.getSourceFileOfNode(d))) + return false; + for (let p: ts.Node | undefined = d.parent; p !== undefined; p = p.parent) { + if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) + return HTTP_REQUEST_OPTION_MODULES.has(p.name.text); + } + return false; + }) + ) + return DYN; // exactly like npm-declared types. Static fetch has one deliberately // narrower native representation: Response is an opaque checked- @@ -2463,9 +2808,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const alias = widened.getAliasSymbol(); if ( - alias?.name === "ReadableStreamReadResult" && + alias !== undefined && + ts.symbolName(alias) === "ReadableStreamReadResult" && alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const elemTs = widened.getAliasTypeArguments()?.[0]; @@ -2477,10 +2823,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { } if ( psym && - (psym.name === "ReadableStreamReadValueResult" || - psym.name === "ReadableStreamReadDoneResult") && + (ts.symbolName(psym) === "ReadableStreamReadValueResult" || + ts.symbolName(psym) === "ReadableStreamReadDoneResult") && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const elemTs = checker.getTypeArguments(widened as ts.TypeReference)[0]; @@ -2492,11 +2838,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // node:net's SocketAddress and BlockList: native handles (scr_net.c) // whose getters and methods dispatch through the handle. if ( - (psym?.name === "SocketAddress" || psym?.name === "BlockList") && + ((psym !== undefined && ts.symbolName(psym) === "SocketAddress") || + (psym !== undefined && ts.symbolName(psym) === "BlockList")) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2507,11 +2854,11 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // checked-dynamic objects. if ( psym && - WEBCRYPTO_DYN_TYPES.has(psym.name) && + WEBCRYPTO_DYN_TYPES.has(ts.symbolName(psym)) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2520,20 +2867,21 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // whose getters and methods dispatch through the handle. The JsonWebKey // that export() returns is a plain checked-dynamic object. if ( - (psym?.name === "KeyObject" || psym?.name === "JsonWebKey") && + ((psym !== undefined && ts.symbolName(psym) === "KeyObject") || + (psym !== undefined && ts.symbolName(psym) === "JsonWebKey")) && psym.declarations.some( (d) => (ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; } if ( psym && - (FETCH_AMBIENT_TYPES as readonly string[]).includes(psym.name) && + (FETCH_AMBIENT_TYPES as readonly string[]).includes(ts.symbolName(psym)) && !( - psym.name === "ReadableStream" && + ts.symbolName(psym) === "ReadableStream" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && isDeclaredInAmbientNamespace(d, "NodeJS"), ) @@ -2541,7 +2889,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2554,12 +2902,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Symbol.asyncDispose method. A DisposableStack is a native handle whose // methods are function values of its checked-dynamic view. if ( - (psym?.name === "Console" || - psym?.name === "Disposable" || - psym?.name === "AsyncDisposable" || - psym?.name === "DisposableStack") && + ((psym !== undefined && ts.symbolName(psym) === "Console") || + (psym !== undefined && ts.symbolName(psym) === "Disposable") || + (psym !== undefined && ts.symbolName(psym) === "AsyncDisposable") || + (psym !== undefined && ts.symbolName(psym) === "DisposableStack")) && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return DYN; @@ -2569,8 +2917,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // representation; lowerCipherMethodCall dispatches their methods. if ( psym !== undefined && - CIPHER_TYPE_NAMES.has(psym.name) && - psym.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) + CIPHER_TYPE_NAMES.has(ts.symbolName(psym)) && + psym.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) ) { return DYN; } @@ -2697,11 +3045,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fallback and by @types/node (whose NonSharedBuffer alias resolves to // the same symbol); provenance-checked like URL. if ( - psym?.name === "Buffer" && + psym !== undefined && + ts.symbolName(psym) === "Buffer" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return bytesOf("u8"); @@ -2712,11 +3061,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // instance type). Provenance, not the name: a user's own URL maps as a // record/class like any other. if ( - psym?.name === "URL" && + psym !== undefined && + ts.symbolName(psym) === "URL" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "url" }; @@ -2725,11 +3075,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // interface by the shipped fallback and by @types/node (plus `class // URLSearchParams` in the "url" module). Provenance-checked like URL. if ( - psym?.name === "URLSearchParams" && + psym !== undefined && + ts.symbolName(psym) === "URLSearchParams" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "searchParams" }; @@ -2737,11 +3088,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fs.Stats: @types/node's `class Stats` (module "fs") or the fallback // declarations' interface. Provenance-checked like URL. if ( - psym?.name === "Stats" && + psym !== undefined && + ts.symbolName(psym) === "Stats" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "stats" }; @@ -2749,13 +3101,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Capacity snapshots and options keep their live object representation // across numeric/BigInt overloads, runtime options, and callbacks. if ( - (psym?.name === "StatsFs" || - psym?.name === "BigIntStatsFs" || - psym?.name === "StatFsOptions") && + ((psym !== undefined && ts.symbolName(psym) === "StatsFs") || + (psym !== undefined && ts.symbolName(psym) === "BigIntStatsFs") || + (psym !== undefined && ts.symbolName(psym) === "StatFsOptions")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) @@ -2765,11 +3117,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // distinguishes it from user interfaces with the same name; both the // fallback and @types/node declare it in "fs/promises". if ( - psym?.name === "FileHandle" && + psym !== undefined && + ts.symbolName(psym) === "FileHandle" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs/promises"), ) ) { @@ -2778,11 +3131,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // fs.Dir: an open directory stream, declared in "fs" by both the // fallback and @types/node. if ( - psym?.name === "Dir" && + psym !== undefined && + ts.symbolName(psym) === "Dir" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { @@ -2793,11 +3147,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // reads work either way) or the fallback declarations' plain interface. // Provenance-checked like Stats. if ( - psym?.name === "SpawnSyncReturns" && + psym !== undefined && + ts.symbolName(psym) === "SpawnSyncReturns" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "spawnRes" }; @@ -2807,13 +3162,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // refinements only tell the checker which stdio properties are null. if ( psym !== undefined && - (psym.name === "ChildProcess" || - psym.name === "ChildProcessWithoutNullStreams" || - psym.name === "ChildProcessByStdio") && + (ts.symbolName(psym) === "ChildProcess" || + ts.symbolName(psym) === "ChildProcessWithoutNullStreams" || + ts.symbolName(psym) === "ChildProcessByStdio") && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "child_process"), ) ) { @@ -2827,25 +3182,27 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // that module (http's Server) stays unmapped and fences at its use // site. if ( - (psym?.name === "Server" || psym?.name === "Socket" || psym?.name === "TLSSocket") && + ((psym !== undefined && ts.symbolName(psym) === "Server") || + (psym !== undefined && ts.symbolName(psym) === "Socket") || + (psym !== undefined && ts.symbolName(psym) === "TLSSocket")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "net") || // http.Server extends net.Server in @types/node — and tls.Server // / https.Server extend it transitively: same lowered surface // (listen/close/address/'error'), same handle kind. tls.TLSSocket // extends net.Socket the same way: post-handshake it IS a net // socket to every lowered member. - (psym.name === "Server" && + (ts.symbolName(psym) === "Server" && (isDeclaredInAmbientModule(d, "http") || isDeclaredInAmbientModule(d, "tls") || isDeclaredInAmbientModule(d, "https"))) || - (psym.name === "TLSSocket" && isDeclaredInAmbientModule(d, "tls"))), + (ts.symbolName(psym) === "TLSSocket" && isDeclaredInAmbientModule(d, "tls"))), ) ) { - return psym.name === "Server" ? { kind: "netServer" } : { kind: "netSocket" }; + return ts.symbolName(psym) === "Server" ? { kind: "netServer" } : { kind: "netSocket" }; } // tls.SecureContext: the opaque parsed-cert/key handle SNI callbacks // answer with (tls.createSecureContext({ cert, key }) mints it; the TLS @@ -2853,11 +3210,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node declares `interface SecureContext` inside `declare module // "tls"`, and the shipped fallback mirrors it. if ( - psym?.name === "SecureContext" && + psym !== undefined && + ts.symbolName(psym) === "SecureContext" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "tls"), ) ) { @@ -2866,11 +3224,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Scrypt's runtime options retain property accessors and aliases. Use // checked values so presence probes and repeated getter reads match Node. if ( - psym?.name === "ScryptOptions" && + psym !== undefined && + ts.symbolName(psym) === "ScryptOptions" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) @@ -2879,26 +3238,28 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // provenance module-qualified so user classes with these common names // retain their ordinary structural/class representation. if ( - (psym?.name === "Hash" || psym?.name === "Hmac") && + ((psym !== undefined && ts.symbolName(psym) === "Hash") || + (psym !== undefined && ts.symbolName(psym) === "Hmac")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) { - return psym.name === "Hash" ? CRYPTOHASH_T : CRYPTOHMAC_T; + return ts.symbolName(psym) === "Hash" ? CRYPTOHASH_T : CRYPTOHMAC_T; } // fs.FSWatcher: the fs.watch handle (scr_watch.c). Module-checked like // net.Server — @types/node declares `interface FSWatcher extends // EventEmitter` inside `declare module "fs"`, and the shipped fallback // mirrors it. if ( - psym?.name === "FSWatcher" && + psym !== undefined && + ts.symbolName(psym) === "FSWatcher" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { @@ -2921,22 +3282,54 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // global-namespace interface real code writes in its own signatures — // prefixStream's `output` param). The representation is the raw fd // scalar; the lowered surface is write(string). + // Under the program's general stream representation (generalNodeStreams) + // NodeJS.WritableStream is the runtime Writable instead: the process + // streams convert into it where they flow (process.stdioWritable). + // Checked-dynamic stream slots: an intersection over a NodeJS stream + // interface (`NodeJS.ReadableStream & { destroy?(): void }`) holds the + // same values as the interface. if ( - (psym?.name === "WriteStream" && + ctx.dynNodeStreams === true && + (type.flags & ts.TypeFlags.Intersection) !== 0 && + ts.constituentTypes(type).some((part) => { + const sym = part.getSymbol(); + return ( + ((sym !== undefined && ts.symbolName(sym) === "ReadableStream") || + (sym !== undefined && ts.symbolName(sym) === "WritableStream")) && + sym.declarations.some( + (d) => + ts.isInterfaceDeclaration(d) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + isDeclaredInAmbientNamespace(d, "NodeJS"), + ) + ); + }) + ) { + return DYN; + } + const nodeWritableInterface = + psym !== undefined && + ts.symbolName(psym) === "WritableStream" && + psym.declarations.some( + (d) => + ts.isInterfaceDeclaration(d) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + isDeclaredInAmbientNamespace(d, "NodeJS"), + ); + if (nodeWritableInterface && ctx.generalNodeStreams === true) { + return ctx.dynNodeStreams === true ? DYN : { kind: "object", className: "%Writable" }; + } + if ( + (psym !== undefined && + ts.symbolName(psym) === "WriteStream" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "tty") || (isDeclaredInAmbientModule(d, "process") && isDeclaredInAmbientNamespace(d, "NodeJS"))), )) || - (psym?.name === "WritableStream" && - psym.declarations.some( - (d) => - ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && - isDeclaredInAmbientNamespace(d, "NodeJS"), - )) + nodeWritableInterface ) { return PROCSTREAM_T; } @@ -2945,24 +3338,43 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // stream.Writable. As with stream.Readable below, unsupported producers // of the broad @types class fence where they are constructed. if ( - (psym?.name === "ChildStdin" && + (psym !== undefined && + ts.symbolName(psym) === "ChildStdin" && + ctx.generalNodeStreams !== true && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "child_process"), )) || - (psym?.name === "Writable" && + (psym !== undefined && + ts.symbolName(psym) === "Writable" && + ctx.generalNodeStreams !== true && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && - isNodeTypesPath(d.getSourceFile().fileName) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) && isDeclaredInAmbientModule(d, "stream"), )) ) { return { kind: "childWriter" }; } + // Under the general stream representation the fallback declarations' + // ChildStdin is the runtime Writable too (child.stdioStream bridges it). + if ( + ctx.generalNodeStreams === true && + psym !== undefined && + ts.symbolName(psym) === "ChildStdin" && + psym.declarations.some( + (d) => + ts.isInterfaceDeclaration(d) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + isDeclaredInAmbientModule(d, "child_process"), + ) + ) { + return { kind: "object", className: "%Writable" }; + } // The static node:stream classes (the shipped fallback declarations — // NOT @types/node, whose stream.Readable also types child stdio; under // @types/node the childStream mapping below keeps priority and the @@ -2970,66 +3382,85 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // branch: the fallback's Readable must map to the runtime class, and // under the fallback child.stdout is NodeJS.ReadableStream (its own // branch below), so the two never collide. + // @types/node's Duplex/Transform/PassThrough classes have no child-stdio + // reading, so they map to the runtime classes too; its Readable and + // Writable join them under the general representation. for (const [irName, rec] of RUNTIME_STREAM_CLASSES) { if ( - psym?.name === rec.lib && + psym !== undefined && + ts.symbolName(psym) === rec.lib && psym.declarations.some( (d) => ts.isClassDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && - !isNodeTypesPath(d.getSourceFile().fileName) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && + (!isNodeTypesPath(ts.getSourceFileOfNode(d).fileName) || + ctx.generalNodeStreams === true || + (rec.lib !== "Readable" && rec.lib !== "Writable")) && isDeclaredInAmbientModule(d, "stream"), ) ) { return { kind: "object", className: irName }; } } - // node:zlib's stream classes (the shipped fallback declarations): their - // instances are runtime Transform streams. + // node:zlib's stream classes (the shipped fallback declarations and + // @types/node's): their instances are runtime Transform streams. if ( psym !== undefined && - ZLIB_STREAM_MODES.has(psym.name) && + ZLIB_STREAM_MODES.has(ts.symbolName(psym)) && psym.declarations.some( (d) => - ts.isClassDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && - !isNodeTypesPath(d.getSourceFile().fileName) && + (ts.isClassDeclaration(d) || + (ts.isInterfaceDeclaration(d) && isNodeTypesPath(ts.getSourceFileOfNode(d).fileName))) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "zlib"), ) ) { return { kind: "object", className: "%Transform" }; } - // fs.ReadStream / fs.WriteStream (the shipped fallback declarations): - // their instances are runtime Readable / Writable streams - // (scr_fs_stream.c). + // fs.ReadStream / fs.WriteStream (the shipped fallback declarations and + // @types/node's): their instances are runtime Readable / Writable + // streams (scr_fs_stream.c). if ( - (psym?.name === "ReadStream" || psym?.name === "WriteStream") && + ((psym !== undefined && ts.symbolName(psym) === "ReadStream") || + (psym !== undefined && ts.symbolName(psym) === "WriteStream")) && psym.declarations.some( (d) => ts.isClassDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && - !isNodeTypesPath(d.getSourceFile().fileName) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "fs"), ) ) { - return { kind: "object", className: psym.name === "ReadStream" ? "%Readable" : "%Writable" }; + return { + kind: "object", + className: ts.symbolName(psym) === "ReadStream" ? "%Readable" : "%Writable", + }; } if ( - (psym?.name === "Readable" && + (psym !== undefined && + ts.symbolName(psym) === "Readable" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "stream"), )) || - (psym?.name === "ReadableStream" && + (psym !== undefined && + ts.symbolName(psym) === "ReadableStream" && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientNamespace(d, "NodeJS"), )) ) { + // The general representation: the runtime Readable, which child + // stdio reads produce through the child stream bridge. With + // checked-dynamic stream slots, NodeJS.ReadableStream holds any + // stream value (dyn-streams.ts); stream.Readable keeps the class. + if (ctx.generalNodeStreams === true) { + if (ctx.dynNodeStreams === true && ts.symbolName(psym) === "ReadableStream") return DYN; + return { kind: "object", className: "%Readable" }; + } return { kind: "childStream" }; } // dgram.Socket: the same ambiguous "Socket" name disambiguated by its @@ -3037,11 +3468,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // EventEmitter` and the fallback declarations' interface both live // inside `declare module "dgram"`. if ( - psym?.name === "Socket" && + psym !== undefined && + ts.symbolName(psym) === "Socket" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "dgram"), ) ) { @@ -3054,11 +3486,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // `declare module "node:test"` (isDeclaredInAmbientModule answers for // both spellings). if ( - (psym?.name === "TestContext" || psym?.name === "SuiteContext") && + ((psym !== undefined && ts.symbolName(psym) === "TestContext") || + (psym !== undefined && ts.symbolName(psym) === "SuiteContext")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "test"), ) ) { @@ -3068,18 +3501,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // records built from F (mock-shapes.ts). Mock then maps as the hybrid // { %call: F, mock } record through mapHybridCallableIntersection. if ( - (psym?.name === "MockFunctionContext" || psym?.name === "MockFunctionCall") && + ((psym !== undefined && ts.symbolName(psym) === "MockFunctionContext") || + (psym !== undefined && ts.symbolName(psym) === "MockFunctionCall")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "test"), ) ) { const fnTs = checker.getTypeArguments(widened as ts.TypeReference)[0]; const fn = fnTs ? mapType(fnTs, ctx) : null; if (!fn || fn.kind !== "func") return null; - return psym.name === "MockFunctionContext" + return ts.symbolName(psym) === "MockFunctionContext" ? mockContextType(fn, ctx) : mockCallRecordType(fn, ctx); } @@ -3089,9 +3523,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // above; optional metadata uses the same checked property view. // Provenance-checked like the rest. if ( - psym?.name === "ErrnoException" && + psym !== undefined && + ts.symbolName(psym) === "ErrnoException" && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "object", className: "%Error" }; @@ -3103,11 +3538,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // of fencing at the declaration. Module-checked like the rest (https' // Agent extends http's; both live in their ambient modules). if ( - psym?.name === "Agent" && + psym !== undefined && + ts.symbolName(psym) === "Agent" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "http") || isDeclaredInAmbientModule(d, "https")), ) ) { @@ -3117,15 +3553,16 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // net.Server (the names repeat nowhere today, but provenance stays the // rule). if ( - (psym?.name === "IncomingMessage" || psym?.name === "ServerResponse") && + ((psym !== undefined && ts.symbolName(psym) === "IncomingMessage") || + (psym !== undefined && ts.symbolName(psym) === "ServerResponse")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http"), ) ) { - return psym.name === "IncomingMessage" ? { kind: "httpReq" } : { kind: "httpRes" }; + return ts.symbolName(psym) === "IncomingMessage" ? { kind: "httpReq" } : { kind: "httpRes" }; } // http2's compatibility surface (the allowHTTP1 lowering): the secure // server IS a net server (listen/close/address/emit ride the same @@ -3136,19 +3573,19 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // member at their use sites (lower-server.ts). Module-checked like the // rest. if ( - (psym?.name === "Http2SecureServer" || - psym?.name === "Http2ServerRequest" || - psym?.name === "Http2ServerResponse") && + ((psym !== undefined && ts.symbolName(psym) === "Http2SecureServer") || + (psym !== undefined && ts.symbolName(psym) === "Http2ServerRequest") || + (psym !== undefined && ts.symbolName(psym) === "Http2ServerResponse")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http2"), ) ) { - return psym.name === "Http2SecureServer" + return ts.symbolName(psym) === "Http2SecureServer" ? { kind: "netServer" } - : psym.name === "Http2ServerRequest" + : ts.symbolName(psym) === "Http2ServerRequest" ? { kind: "httpReq" } : { kind: "httpRes" }; } @@ -3157,33 +3594,34 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // kinds (client and server flavors share a kind — the runtime handle // carries the role). Module-checked like the rest. if ( - (psym?.name === "Http2Server" || - psym?.name === "Http2Session" || - psym?.name === "ClientHttp2Session" || - psym?.name === "ServerHttp2Session" || - psym?.name === "ClientHttp2Stream" || - psym?.name === "ServerHttp2Stream") && + ((psym !== undefined && ts.symbolName(psym) === "Http2Server") || + (psym !== undefined && ts.symbolName(psym) === "Http2Session") || + (psym !== undefined && ts.symbolName(psym) === "ClientHttp2Session") || + (psym !== undefined && ts.symbolName(psym) === "ServerHttp2Session") || + (psym !== undefined && ts.symbolName(psym) === "ClientHttp2Stream") || + (psym !== undefined && ts.symbolName(psym) === "ServerHttp2Stream")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http2"), ) ) { - return psym.name === "Http2Server" + return ts.symbolName(psym) === "Http2Server" ? { kind: "netServer" } - : psym.name === "ClientHttp2Stream" || psym.name === "ServerHttp2Stream" + : ts.symbolName(psym) === "ClientHttp2Stream" || ts.symbolName(psym) === "ServerHttp2Stream" ? { kind: "http2Stream" } : { kind: "http2Session" }; } // http.ClientRequest (http.request/http.get results) — module-checked // like IncomingMessage. if ( - psym?.name === "ClientRequest" && + psym !== undefined && + ts.symbolName(psym) === "ClientRequest" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "http"), ) ) { @@ -3198,9 +3636,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declarations' anonymous env type takes the pureIndexShape path below // and interns the SAME shape. if ( - psym?.name === "ProcessEnv" && + psym !== undefined && + ts.symbolName(psym) === "ProcessEnv" && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const value = withUndefinedArm(STRING, ctx.unions); @@ -3290,9 +3729,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { { const alias = widened.getAliasSymbol(); if ( - alias?.name === "IteratorResult" && + alias !== undefined && + ts.symbolName(alias) === "IteratorResult" && alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const targs = widened.getAliasTypeArguments() ?? []; @@ -3412,10 +3852,10 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ]; const fields: { name: string; type: IrType }[] = []; for (const property of checker.getPropertiesOfType(widened)) { - if (!known.includes(property.name)) return null; + if (!known.includes(ts.symbolName(property))) return null; const type = mapType(checker.getTypeOfSymbol(property), ctx); if (!type) return null; - fields.push({ name: property.name, type }); + fields.push({ name: ts.symbolName(property), type }); } const order = fields.map((field) => field.name); fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -3424,23 +3864,24 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // Filesystem options are data records. Keep their declared optional fields // instead of allowing arbitrary standard-library interfaces into records. if ( - (psym?.name === "FileReadOptions" || - psym?.name === "ReadOptions" || - psym?.name === "ReadOptionsWithBuffer" || - psym?.name === "WriteOptions") && + ((psym !== undefined && ts.symbolName(psym) === "FileReadOptions") || + (psym !== undefined && ts.symbolName(psym) === "ReadOptions") || + (psym !== undefined && ts.symbolName(psym) === "ReadOptionsWithBuffer") || + (psym !== undefined && ts.symbolName(psym) === "WriteOptions")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "fs/promises") || isDeclaredInAmbientModule(d, "fs")), ) ) { const fields: { name: string; type: IrType }[] = []; for (const property of checker.getPropertiesOfType(widened)) { - if (!["buffer", "offset", "length", "position"].includes(property.name)) return null; + if (!["buffer", "offset", "length", "position"].includes(ts.symbolName(property))) + return null; const type = mapType(checker.getTypeOfSymbol(property), ctx); if (!type) return null; - fields.push({ name: property.name, type }); + fields.push({ name: ts.symbolName(property), type }); } const order = fields.map((field) => field.name); fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -3451,28 +3892,28 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // record does not model the prototype (unobservable through the lowered // surface); it preserves the caller buffer/string by reference. if ( - (psym?.name === "FileReadResult" || - psym?.name === "FileWriteResult" || - psym?.name === "FileReadvResult" || - psym?.name === "FileWritevResult" || - psym?.name === "ReadVResult" || - psym?.name === "WriteVResult") && + ((psym !== undefined && ts.symbolName(psym) === "FileReadResult") || + (psym !== undefined && ts.symbolName(psym) === "FileWriteResult") || + (psym !== undefined && ts.symbolName(psym) === "FileReadvResult") || + (psym !== undefined && ts.symbolName(psym) === "FileWritevResult") || + (psym !== undefined && ts.symbolName(psym) === "ReadVResult") || + (psym !== undefined && ts.symbolName(psym) === "WriteVResult")) && psym.declarations.some( (d) => ts.isInterfaceDeclaration(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "fs/promises") || - ((psym.name === "ReadVResult" || psym.name === "WriteVResult") && + ((ts.symbolName(psym) === "ReadVResult" || ts.symbolName(psym) === "WriteVResult") && isDeclaredInAmbientModule(d, "fs"))), ) ) { const args = checker.getTypeArguments(widened as ts.TypeReference); if (args.length !== 1) return null; const vector = - psym.name === "FileReadvResult" || - psym.name === "FileWritevResult" || - psym.name === "ReadVResult" || - psym.name === "WriteVResult"; + ts.symbolName(psym) === "FileReadvResult" || + ts.symbolName(psym) === "FileWritevResult" || + ts.symbolName(psym) === "ReadVResult" || + ts.symbolName(psym) === "WriteVResult"; const emptyVector = vector && checker.isArrayType(args[0]!) && @@ -3487,9 +3928,9 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { return null; } const count = - psym.name === "FileReadResult" || - psym.name === "FileReadvResult" || - psym.name === "ReadVResult" + ts.symbolName(psym) === "FileReadResult" || + ts.symbolName(psym) === "FileReadvResult" || + ts.symbolName(psym) === "ReadVResult" ? "bytesRead" : "bytesWritten"; const payloadName = vector ? "buffers" : "buffer"; @@ -3521,11 +3962,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // declares it concrete (an interface; @types/node uses a class — both // provenance-checked, the Buffer/URL pattern). if ( - psym?.name === "Dirent" && + psym !== undefined && + ts.symbolName(psym) === "Dirent" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const args = checker.getTypeArguments(widened as ts.TypeReference); @@ -3582,7 +4024,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { d.parent.name.text === "times" && ts.isInterfaceDeclaration(d.parent.parent) && d.parent.parent.name.text === "CpuInfo" && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) return cpuTimes(); @@ -3603,7 +4045,8 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { }; } if (isStdlibInterface("ParsedPath") || isStdlibInterface("FormatInputPathObject")) { - const field = psym!.name === "ParsedPath" ? STRING : withUndefinedArm(STRING, ctx.unions); + const field = + ts.symbolName(psym!) === "ParsedPath" ? STRING : withUndefinedArm(STRING, ctx.unions); if (!field) return null; return { kind: "record", @@ -3654,7 +4097,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym.declarations.some( (d) => ts.isTypeLiteralNode(d) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && (isDeclaredInAmbientModule(d, "child_process") || isDeclaredInAmbientModule(d, "util") || isDeclaredInAmbientModule(d, "dns")), @@ -3669,7 +4112,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.String) !== 0, ) ? props - .map((p) => p.name) + .map((p) => ts.symbolName(p)) .sort() .join(",") : ""; @@ -3697,11 +4140,12 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // JSON.stringify of one shows the three fields where Node's // internal-slot object shows {} — the Dirent %dtype edge's cousin. if ( - psym?.name === "X509Certificate" && + psym !== undefined && + ts.symbolName(psym) === "X509Certificate" && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()) && + ctx.isStdlibFile(ts.getSourceFileOfNode(d)) && isDeclaredInAmbientModule(d, "crypto"), ) ) { @@ -3728,18 +4172,39 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { // @types/node declares the classes in perf_hooks and global interfaces // over them; the shipped fallback declares the classes. if ( - (psym?.name === "PerformanceEntry" || - psym?.name === "PerformanceMark" || - psym?.name === "PerformanceMeasure") && + ((psym !== undefined && ts.symbolName(psym) === "PerformanceEntry") || + (psym !== undefined && ts.symbolName(psym) === "PerformanceMark") || + (psym !== undefined && ts.symbolName(psym) === "PerformanceMeasure")) && psym.declarations.some( (d) => (ts.isInterfaceDeclaration(d) || ts.isClassDeclaration(d)) && - ctx.isStdlibFile(d.getSourceFile()), + ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { return { kind: "record", shapeId: perfEntryShape(ctx.shapes, ctx.unions) }; } // readline's getCursorPos() record, in Node's key order ({ cols, rows }). + // perf_hooks' EventLoopUtilization (performance.eventLoopUtilization's + // result): a plain data record in Node's key order. + if (isStdlibInterface("EventLoopUtilization")) { + return { + kind: "record", + shapeId: ctx.shapes.intern( + [ + { name: "active", type: F64 }, + { name: "idle", type: F64 }, + { name: "utilization", type: F64 }, + ], + false, + undefined, + ["idle", "active", "utilization"], + ), + }; + } + // monitorEventLoopDelay's IntervalHistogram: a numeric handle into the + // runtime's histogram table, like a timer handle. Its members lower over + // the handle (builtins/performance.ts). + if (isStdlibInterface("IntervalHistogram")) return F64; if (isStdlibInterface("CursorPos")) { return { kind: "record", @@ -3767,6 +4232,34 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { ), }; } + // v8.getHeapStatistics()'s record, in Node's key order (builtins/v8.ts). + if (isStdlibInterface("HeapInfo")) { + const names = [ + "total_heap_size", + "total_heap_size_executable", + "total_physical_size", + "total_available_size", + "used_heap_size", + "heap_size_limit", + "malloced_memory", + "peak_malloced_memory", + "does_zap_garbage", + "number_of_native_contexts", + "number_of_detached_contexts", + "total_global_handles_size", + "used_global_handles_size", + "external_memory", + ]; + return { + kind: "record", + shapeId: ctx.shapes.intern( + [...names].sort().map((name) => ({ name, type: F64 })), + false, + undefined, + names, + ), + }; + } // process.cpuUsage()/threadCpuUsage()'s {user, system} microsecond // record and resourceUsage()'s getrusage record — stdlib data // interfaces interned explicitly (the RemoteInfo pattern; declared @@ -3842,7 +4335,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { isStdlibInterface("NetworkInterfaceInfoIPv4") || isStdlibInterface("NetworkInterfaceInfoIPv6") ) { - const v6 = psym!.name === "NetworkInterfaceInfoIPv6"; + const v6 = ts.symbolName(psym!) === "NetworkInterfaceInfoIPv6"; const cidrArms = [NULL_T, STRING].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); const cidr: IrType = { kind: "union", unionId: ctx.unions.intern(cidrArms) }; const scopeid = v6 ? F64 : withUndefinedArm(F64, ctx.unions); @@ -3990,7 +4483,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { psym !== undefined && psym.declarations.length > 0 && psym.declarations.every( - (d) => ts.isInterfaceDeclaration(d) && !ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && !ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) ) { const CHILD_SURFACE = new Set([ @@ -4025,40 +4518,55 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null { const props = checker.getPropertiesOfType(widened); let core = 0; const childMemberType = (p: ts.Symbol): boolean => { - if (["kill", "on", "once", "off", "removeListener", "unref", "ref"].includes(p.name)) { + if ( + ["kill", "on", "once", "off", "removeListener", "unref", "ref"].includes(ts.symbolName(p)) + ) { return checker.getCallSignatures(checker.getTypeOfSymbol(p)).length > 0; } const expected = - p.name === "stdout" || p.name === "stderr" + ts.symbolName(p) === "stdout" || ts.symbolName(p) === "stderr" ? "childStream" - : p.name === "stdin" + : ts.symbolName(p) === "stdin" ? "childWriter" - : p.name === "killed" + : ts.symbolName(p) === "killed" ? "bool" : "f64"; const unit = - p.name === "pid" + ts.symbolName(p) === "pid" ? "undefinedT" - : p.name === "exitCode" || - p.name === "stdin" || - p.name === "stdout" || - p.name === "stderr" + : ts.symbolName(p) === "exitCode" || + ts.symbolName(p) === "stdin" || + ts.symbolName(p) === "stdout" || + ts.symbolName(p) === "stderr" ? "nullT" : null; const mapped = mapType(checker.getTypeOfSymbol(p), ctx); if (!mapped) return false; const arms = mapped.kind === "union" ? ctx.unions.get(mapped.unionId)?.arms : [mapped]; + // Under the general stream representation the stdio members are the + // runtime stream classes. + const isExpected = (arm: IrType): boolean => + arm.kind === expected || + (ctx.generalNodeStreams === true && + arm.kind === "object" && + ((expected === "childStream" && arm.className === "%Readable") || + (expected === "childWriter" && arm.className === "%Writable"))) || + // Checked-dynamic stream slots: `NodeJS.ReadableStream | null` + // collapses to dyn (the reads still produce the bridged stream). + (ctx.dynNodeStreams === true && + arm.kind === "dyn" && + (expected === "childStream" || expected === "childWriter")); return ( arms !== undefined && - arms.some((arm) => arm.kind === expected) && - arms.every((arm) => arm.kind === expected || arm.kind === unit) + arms.some(isExpected) && + arms.every((arm) => isExpected(arm) || arm.kind === unit) ); }; const childShaped = props.length > 0 && props.every((p) => { - if (!CHILD_SURFACE.has(p.name) || !childMemberType(p)) return false; - if (CHILD_CORE.has(p.name)) core++; + if (!CHILD_SURFACE.has(ts.symbolName(p)) || !childMemberType(p)) return false; + if (CHILD_CORE.has(ts.symbolName(p))) core++; return true; }); if (childShaped && core >= 2) return CHILD_T; @@ -4263,17 +4771,50 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // arity ABI selected by lambdaSignature. Treating an inferred any[] // declaration as a typed packed-array closure would bypass its boxed // spread/error semantics and change coverage classification. - if (rest && decl !== undefined && isJsSourceFile(decl.getSourceFile())) return null; + if (rest && decl !== undefined && isJsSourceFile(ts.getSourceFileOfNode(decl))) return null; + // A fixed-tuple rest (`...args: [A, B]`, `...args: Parameters` over + // a bound F) flattens to its positional parameters. Pattern rests keep + // their packed-record ABI and stay unmapped as values. + if (rest && decl !== undefined && ts.isParameter(decl) && ts.isIdentifier(decl.name)) { + const elems = restTupleParamTypes(checker.getTypeOfSymbol(p), ctx); + if (elems) { + params.push(...elems); + continue; + } + } const optional = decl !== undefined && ts.isParameter(decl) && (decl.questionToken !== undefined || decl.initializer !== undefined); let pt = mapType(checker.getTypeOfSymbol(p), ctx); + // A parameter a TypeScript program types `any` takes whatever the + // caller passes: the checked-dynamic slot, as an `any`-typed binding + // already is (dynFallbackType). Declaration files keep their paths. + // Only function declarations, spelled function types and lambda + // parameters whose default is the only source of their type (`(n = + // lookup() ?? 5) => ...` with an `any` lookup): other lambdas and + // methods in object literals keep their existing checked-dynamic story. + if ( + !pt && + !rest && + decl !== undefined && + ts.isParameter(decl) && + decl.parent !== undefined && + (ts.isFunctionDeclaration(decl.parent) || + ts.isFunctionTypeNode(decl.parent) || + ((ts.isArrowFunction(decl.parent) || ts.isFunctionExpression(decl.parent)) && + decl.initializer !== undefined && + decl.type === undefined && + !isJsSourceFile(ts.getSourceFileOfNode(decl)))) && + !ts.getSourceFileOfNode(decl).isDeclarationFile && + (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 + ) + pt = DYN; if ( decl && ts.isParameter(decl) && ts.isSetAccessorDeclaration(decl.parent) && - isJsSourceFile(decl.getSourceFile()) + isJsSourceFile(ts.getSourceFileOfNode(decl)) ) pt = DYN; // An inferred JavaScript binding pattern consumes an iterable; it @@ -4293,20 +4834,43 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null ts.isIdentifier(construction.expression) && construction.expression.text === "Promise" && constructorSymbol && - constructorSymbol.declarations.some((site) => ctx.isStdlibFile(site.getSourceFile())); + constructorSymbol.declarations.some((site) => ctx.isStdlibFile(ts.getSourceFileOfNode(site))); if ( !nativeExecutor && decl && ts.isParameter(decl) && !decl.dotDotDotToken && !ts.isSetAccessorDeclaration(decl.parent) && - isJsSourceFile(decl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(decl)) && !decl.type && !/@(?:param|type)\b/.test( - decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()), + ts + .getSourceFileOfNode(decl) + .text.slice(decl.parent?.pos ?? decl.pos, ts.tokenStartOfNode(decl)), ) ) pt = DYN; + // A DEFAULTED program-source parameter of a function, arrow or function + // expression that the checker types `any` — a default under an + // `(...args: any[]) => any` wrapper slot (`wrap((limit = 5) => ...)`), + // where the contextual `any` wins over the default's type: its binding + // is already the checked-dynamic kind inside the body + // (dynFallbackType), so the callable's ABI slot is the same dyn value. + // Other `any` parameters keep their existing paths (an object literal + // holding such a callback stays a checked-dynamic object). + if ( + pt === null && + !rest && + decl !== undefined && + ts.isParameter(decl) && + decl.initializer !== undefined && + (ts.isArrowFunction(decl.parent) || + ts.isFunctionExpression(decl.parent) || + ts.isFunctionDeclaration(decl.parent)) && + !ts.getSourceFileOfNode(decl).isDeclarationFile && + (checker.getTypeOfSymbol(p).flags & ts.TypeFlags.Any) !== 0 + ) + pt = DYN; if (pt) pt = jsOpenObjectType(decl, pt, ctx.shapes, ctx.unions); // Belt and braces for non-strict type worlds: an optional param's // ABI slot is always the undefined-armed union (strictNullChecks @@ -4322,10 +4886,14 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null // params). Declaration files keep their existing paths: library // callback slots such as String#replace's `(substring, ...args: any[])` // have intrinsic lowerings that inspect the unmapped type. + // node:test's default mock function type, `(...args: any[]) => + // undefined` (the type parameter default of mock.fn), takes the same + // ABI: mock.fn() without an original records whatever its callers pass. if ( rest && decl !== undefined && - !decl.getSourceFile().isDeclarationFile && + (!ts.getSourceFileOfNode(decl).isDeclarationFile || + isNodeTestDefaultFunctionParam(decl, ctx)) && (pt?.kind === "dyn" || (!pt && isAnyArrayType(checker.getTypeOfSymbol(p), checker))) ) { dynRest = true; @@ -4350,7 +4918,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null const sigDecl = sig.declaration; const jsUnitReturn = sigDecl !== undefined && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && (retT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; let ret = jsUnitReturn || (retT.flags & ts.TypeFlags.Any) !== 0 @@ -4358,24 +4926,33 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null : retT.flags & ts.TypeFlags.Never ? VOID : mapType(retT, ctx); - if (sigDecl && ts.isGetAccessorDeclaration(sigDecl) && isJsSourceFile(sigDecl.getSourceFile())) + if ( + sigDecl && + ts.isGetAccessorDeclaration(sigDecl) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) + ) + ret = DYN; + if (sigDecl && isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && ret?.kind === "array") ret = DYN; - if (sigDecl && isJsSourceFile(sigDecl.getSourceFile()) && ret?.kind === "array") ret = DYN; if ( sigDecl && ts.isFunctionLike(sigDecl) && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && !sigDecl.type && - !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && + !/@returns?\b/.test( + ts.getSourceFileOfNode(sigDecl).text.slice(sigDecl.pos, ts.tokenStartOfNode(sigDecl)), + ) && ret?.kind === "record" ) ret = DYN; if ( sigDecl && ts.isFunctionLike(sigDecl) && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && !sigDecl.type && - !/@returns?\b/.test(sigDecl.getSourceFile().text.slice(sigDecl.pos, sigDecl.getStart())) && + !/@returns?\b/.test( + ts.getSourceFileOfNode(sigDecl).text.slice(sigDecl.pos, ts.tokenStartOfNode(sigDecl)), + ) && (ret?.kind === "date" || (ret?.kind === "union" && ctx.unions.get(ret.unionId)?.arms.some((arm) => arm.kind === "func"))) @@ -4384,7 +4961,7 @@ function mapCallSignature(sig: ts.Signature, ctx: TypeMapperCtx): IrType | null if ( ret && sigDecl && - isJsSourceFile(sigDecl.getSourceFile()) && + isJsSourceFile(ts.getSourceFileOfNode(sigDecl)) && functionCanReturnUndefined(sigDecl) ) ret = withUndefinedArm(ret, ctx.unions) ?? ret; @@ -4413,13 +4990,16 @@ function unionDiscriminant( ): IrUnionDiscriminant | undefined { if (arms.filter((arm) => arm.kind === "record").length < 2 || parts.length < 2) return undefined; const { checker } = ctx; + // Tuple shapes keep positions only: their checker `length` literal has + // no slot to compare (discriminantField refuses tuple shapes too). + if (parts.some((part) => ctx.shapes.get(part.mapped.shapeId)?.tuple)) return undefined; for (const candidate of checker.getPropertiesOfType(parts[0]!.source)) { - if (candidate.name.startsWith("__@")) continue; + if (ts.symbolName(candidate).startsWith("__@")) continue; const byTag = new Map(); const owner = new Map(); let complete = true; for (const part of parts) { - const prop = checker.getPropertyOfType(part.source, candidate.name); + const prop = checker.getPropertyOfType(part.source, ts.symbolName(candidate)); if ( !prop || prop.flags & @@ -4450,7 +5030,7 @@ function unionDiscriminant( } if (complete) { return { - field: candidate.name, + field: ts.symbolName(candidate), cases: [...byTag] .sort(([a], [b]) => a - b) .map(([tag, values]) => ({ @@ -4492,7 +5072,8 @@ function mapClassView( if ( declarations.length === 0 || !declarations.every( - (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(decl.getSourceFile()), + (decl) => + ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ) ) return undefined; @@ -4530,7 +5111,7 @@ function mapClassView( const classBase = declaration && (ts.isClassDeclaration(declaration) || ts.isClassExpression(declaration)) && - !declaration.getSourceFile().isDeclarationFile; + !ts.getSourceFileOfNode(declaration).isDeclarationFile; const mapped = classBase ? mapType(base, viewCtx) : mapClassView(base, viewCtx, seen); if (mapped === null) return null; if (mapped === undefined) continue; @@ -4547,7 +5128,7 @@ function mapClassView( ) return null; for (const property of checker.getPropertiesOfType(type)) { - const original = checker.getPropertyOfType(baseType, property.name); + const original = checker.getPropertyOfType(baseType, ts.symbolName(property)); if (!original) return null; if (original === property) continue; const originalDeclarations = original.declarations; @@ -4593,7 +5174,7 @@ function mapCollectionView( if ( declarations.length === 0 || !declarations.every( - (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(decl.getSourceFile()), + (decl) => ts.isInterfaceDeclaration(decl) && !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ) ) return null; @@ -4641,18 +5222,20 @@ function mapCollectionView( const baseSymbol = base.getSymbol(); const native = baseSymbol && - ["Map", "ReadonlyMap", "Set", "ReadonlySet"].includes(baseSymbol.name) && + ["Map", "ReadonlyMap", "Set", "ReadonlySet"].includes(ts.symbolName(baseSymbol)) && baseSymbol.declarations.some( - (decl) => ts.isInterfaceDeclaration(decl) && ctx.isStdlibFile(decl.getSourceFile()), + (decl) => ts.isInterfaceDeclaration(decl) && ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), ); const mapped = native ? mapType(base, baseCtx) : mapCollectionView(base, baseCtx, seen); if (mapped?.kind !== "map" && mapped?.kind !== "set") return null; const hasType = funcOf([mapped.kind === "map" ? mapped.key : mapped.elem], BOOL); for (const property of checker.getPropertiesOfType(type)) { - const own = property.declarations.filter((decl) => !ctx.isStdlibFile(decl.getSourceFile())); + const own = property.declarations.filter( + (decl) => !ctx.isStdlibFile(ts.getSourceFileOfNode(decl)), + ); if (own.length === 0) continue; if ( - property.name !== "has" || + ts.symbolName(property) !== "has" || property.flags & ts.SymbolFlags.Optional || !own.every((decl) => decl.kind === ts.SyntaxKind.MethodSignature) ) @@ -4750,6 +5333,168 @@ function mapBoundIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | null return mapType(checker.getTypeOfSymbol(prop), ctx); } +/** The standard library's `Parameters` / `ReturnType` over a type + * parameter the current instantiation binds to a callable checker type: + * that type's one call signature. Concrete arguments never reach here (the + * checker evaluates those conditionals itself); generic bodies keep them + * symbolic because their `infer` sits in a call signature. */ +function boundSignatureAlias( + type: ts.Type, + ctx: TypeMapperCtx, +): { name: "Parameters" | "ReturnType"; sig: ts.Signature } | null { + const { checker, resolveTypeParamTs } = ctx; + if (!resolveTypeParamTs) return null; + const alias = type.getAliasSymbol(); + const aliasName = alias === undefined ? undefined : ts.symbolName(alias); + if (!alias || (aliasName !== "Parameters" && aliasName !== "ReturnType")) return null; + const aliasArgs = type.getAliasTypeArguments(); + const arg = aliasArgs[0]; + if (aliasArgs.length !== 1 || !arg || !(arg.flags & ts.TypeFlags.TypeParameter)) return null; + const isLibAlias = alias.declarations.some( + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), + ); + if (!isLibAlias) return null; + const bound = resolveTypeParamTs(arg); + if (!bound || bound.flags & ts.TypeFlags.TypeParameter) return null; + const sigs = checker.getCallSignatures(bound); + const sig = sigs[0]; + if (sigs.length !== 1 || !sig || sig.getTypeParameters().length > 0) return null; + return { name: aliasName, sig }; +} + +/** One position of a fixed-arity parameter tuple. An optional position + * still has a native slot (the function type's `T | undefined` slot). */ +interface TupleElement { + type: ts.Type; + optional: boolean; +} + +/** The positional parameters of a signature as a fixed tuple: a trailing + * rest flattened when its own type is a fixed tuple. Null for a variadic + * position, which has no fixed native arity. */ +function signatureParameters(sig: ts.Signature, ctx: TypeMapperCtx): TupleElement[] | null { + const { checker } = ctx; + const out: TupleElement[] = []; + for (const p of sig.getParameters()) { + const decl = p.valueDeclaration; + const type = checker.getTypeOfSymbol(p); + if (decl !== undefined && ts.isParameter(decl)) { + if (decl.dotDotDotToken !== undefined) { + const inner = restTupleElements(type, ctx); + if (!inner) return null; + out.push(...inner); + continue; + } + out.push({ + type, + optional: decl.questionToken !== undefined || decl.initializer !== undefined, + }); + continue; + } + out.push({ type, optional: (p.flags & ts.SymbolFlags.Optional) !== 0 }); + } + return out; +} + +/** The required-only positional parameter types of a signature (the + * value-level `Parameters` tuple), null when a position is optional + * or variadic: such a tuple's length varies per call. */ +function signatureParameterTypes(sig: ts.Signature, ctx: TypeMapperCtx): ts.Type[] | null { + const elems = signatureParameters(sig, ctx); + if (!elems || elems.some((e) => e.optional)) return null; + return elems.map((e) => e.type); +} + +/** The element types of a REST parameter's type when it is a fixed tuple: + * a tuple of required elements (`[number, string]`, the empty `[]`), a type + * parameter bound to one, or `Parameters` over a bound function type. + * Such a rest parameter has a compile-time arity, so its native ABI is the + * flattened positional list (exactly the equivalent fixed signature — + * TypeScript treats `(...a: [A, B]) => R` and `(a: A, b: B) => R` alike). */ +export function restTupleElementTypes(type: ts.Type, ctx: TypeMapperCtx): ts.Type[] | null { + return restTupleElements(type, ctx)?.map((e) => e.type) ?? null; +} + +/** restTupleElementTypes with each position's optionality: a rest tuple + * may end in optional positions (`[A, B?]`, `Parameters<(a: A, b?: B) => + * R>`), which flatten to `T | undefined` slots exactly as the function + * type's optional parameters do. */ +function restTupleElements(type: ts.Type, ctx: TypeMapperCtx): TupleElement[] | null { + const { checker } = ctx; + if (type.flags & ts.TypeFlags.TypeParameter) { + const bound = ctx.resolveTypeParamTs?.(type); + return bound && bound !== type ? restTupleElements(bound, ctx) : null; + } + const viaAlias = boundSignatureAlias(type, ctx); + if (viaAlias) { + return viaAlias.name === "Parameters" ? signatureParameters(viaAlias.sig, ctx) : null; + } + if (!checker.isTupleType(type)) return null; + const ref = type as ts.TupleTypeReference; + const tupleShape = + (ref.elementFlags as ts.ElementFlags[] | undefined) !== undefined + ? ref + : (ref.getTarget() as ts.TupleType | undefined); + const args = checker.getTypeArguments(ref); + if (args.length === 0) return []; + const flags = tupleShape?.elementFlags; + if (flags === undefined) return null; + if (flags.some((f) => !(f & (ts.ElementFlags.Required | ts.ElementFlags.Optional)))) return null; + return args.map((t, i) => ({ + type: t, + optional: (flags[i]! & ts.ElementFlags.Optional) !== 0, + })); +} + +/** One tuple element's IR type under the tuple mapping's rules. */ +function mapTupleElement(t: ts.Type, ctx: TypeMapperCtx): IrType | null { + let et = (t.flags & ts.TypeFlags.Any) !== 0 ? DYN : mapType(t, ctx); + if (et?.kind === "void" && isUnitOnlyTsType(t, ctx.resolveTypeParam)) + et = unitOnlyUnion(ctx.unions); + return !et || et.kind === "void" ? null : et; +} + +/** The mapped element types of a fixed-tuple rest parameter type + * (restTupleElementTypes), null when it is not one or an element does not + * map. */ +export function restTupleParamTypes(type: ts.Type, ctx: TypeMapperCtx): IrType[] | null { + const elems = restTupleElements(type, ctx); + if (!elems) return null; + const out: IrType[] = []; + for (const e of elems) { + let et = mapTupleElement(e.type, ctx); + // An optional position's slot holds undefined when the caller omits + // it, as an optional parameter's does (mapCallSignature). + if (et && e.optional && et.kind !== "dyn") et = withUndefinedArm(et, ctx.unions); + if (!et) return null; + out.push(et); + } + return out; +} + +/** `Parameters` (the fixed tuple of F's parameters, interned exactly as + * the spelled tuple maps) and `ReturnType` (F's mapped result) over a + * bound function-typed type parameter. */ +function mapBoundSignatureAlias(type: ts.Type, ctx: TypeMapperCtx): IrType | null { + const target = boundSignatureAlias(type, ctx); + if (!target) return null; + if (target.name === "ReturnType") { + const ret = ctx.checker.getReturnTypeOfSignature(target.sig); + return ret.flags & ts.TypeFlags.Any ? DYN : mapType(ret, ctx); + } + const elems = signatureParameterTypes(target.sig, ctx); + if (!elems) return null; + if (elems.length === 0) return arrayOf(unitOnlyUnion(ctx.unions)); + const fields: { name: string; type: IrType }[] = []; + for (let i = 0; i < elems.length; i++) { + const et = mapTupleElement(elems[i]!, ctx); + if (!et) return null; + fields.push({ name: String(i), type: et }); + } + fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); + return { kind: "record", shapeId: ctx.shapes.intern(fields, true) }; +} + /** `Partial` / `Readonly` where T is a STILL-GENERIC type parameter — * the one mapped-type spelling a monomorphized body can contain that the * checker cannot resolve for us (`keyof T` is unknown inside the body, so @@ -4817,17 +5562,21 @@ function mapGenericAwaitedAlias(type: ts.Type, ctx: TypeMapperCtx): IrType | nul const { resolveTypeParam } = ctx; if (!resolveTypeParam) return null; const alias = type.getAliasSymbol(); - if (!alias || alias.name !== "Awaited") return null; + if (!alias || ts.symbolName(alias) !== "Awaited") return null; const aliasArgs = type.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg) return null; - if (!(arg.flags & ts.TypeFlags.TypeParameter)) return null; + // `Awaited>` (an async wrapper's settled result) resolves + // through F's bound signature like a bare type parameter. + const viaSignature = + arg.flags & ts.TypeFlags.TypeParameter ? null : boundSignatureAlias(arg, ctx); + if (!(arg.flags & ts.TypeFlags.TypeParameter) && viaSignature?.name !== "ReturnType") return null; // Provenance: the STANDARD LIBRARY's alias, not a user's shadowing one. const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; - const bound = resolveTypeParam(arg); + const bound = viaSignature ? mapBoundSignatureAlias(arg, ctx) : resolveTypeParam(arg); if (!bound) return null; let r = bound; while (r.kind === "promise") r = r.inner; @@ -4853,11 +5602,15 @@ function mapGenericIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | nu const obj = type.getObjectType(); if (obj === undefined || !(obj.flags & ts.TypeFlags.TypeParameter)) return null; const bound = resolveTypeParam(obj); + const idx = type.getIndexType(); + if (idx === undefined) return null; + // `T[keyof T]` over a T bound to an unknown-valued record view + // (`Record` maps to the checked-dynamic value, see + // mapRecordType): every member reads as unknown. + if (bound?.kind === "dyn" && idx.isIndexType() && idx.getTarget() === obj) return DYN; if (!bound || bound.kind !== "record") return null; const shape = shapes.get(bound.shapeId); if (!shape || shape.tuple) return null; - const idx = type.getIndexType(); - if (idx === undefined) return null; let covered: IrType[] | null = null; if (idx.isStringLiteralType()) { const f = shape.fields.find((x) => x.name === idx.value); @@ -4874,7 +5627,62 @@ function mapGenericIndexedAccess(type: ts.Type, ctx: TypeMapperCtx): IrType | nu } if (!covered || covered.length === 0) return null; const first = covered[0]!; - return covered.every((t) => typeEquals(t, first)) ? first : null; + if (covered.every((t) => typeEquals(t, first))) return first; + // Only the `keyof T` index itself covers every field at once; a key + // parameter bound to string keeps its literal-key resolution below. + if (!(idx.isIndexType() && idx.getTarget() === obj)) return null; + // Fields of different scalar types (`Object.entries` over a bound record + // reads `T[keyof T]`): any one of them, the union of their types. + const arms: IrType[] = []; + for (const t of covered) { + const parts = t.kind === "union" ? (ctx.unions.get(t.unionId)?.arms ?? null) : [t]; + if (!parts) return null; + for (const part of parts) { + if ( + part.kind !== "f64" && + part.kind !== "string" && + part.kind !== "bool" && + part.kind !== "undefinedT" && + part.kind !== "nullT" + ) + return null; + if (!arms.some((a) => typeEquals(a, part))) arms.push(part); + } + } + if (arms.length === 1) return arms[0]!; + arms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + return { kind: "union", unionId: ctx.unions.intern(arms) }; +} + +/** `keyof T extends never ? A : B` over a bound record T (the lib's + * Object.entries element spelling): decided by whether the binding has any + * key. */ +function mapBoundKeyofNeverConditional(type: ts.Type, ctx: TypeMapperCtx): IrType | null { + const { resolveTypeParam, shapes } = ctx; + if (!resolveTypeParam || !type.isConditionalType()) return null; + const check = type.getCheckType(); + const ext = type.getExtendsType(); + if (!check || !ext || !(ext.flags & ts.TypeFlags.Never) || !check.isIndexType()) return null; + const target = check.getTarget(); + if (!target || !(target.flags & ts.TypeFlags.TypeParameter)) return null; + const bound = resolveTypeParam(target); + // A T bound to the checked-dynamic value (`Record`, an + // unknown-valued record view) has no static key set to decide the + // branch, but both branches of the lib's `keyof T extends never ? + // unknown : T[keyof T]` read as unknown there: one answer either way. + if (bound?.kind === "dyn") { + const whenEmpty = type.getTrueType(); + const otherwise = type.getFalseType(); + const a = whenEmpty ? mapType(whenEmpty, ctx) : null; + const b = otherwise ? mapType(otherwise, ctx) : null; + return a && b && typeEquals(a, b) ? a : null; + } + if (bound?.kind !== "record") return null; + const shape = shapes.get(bound.shapeId); + if (!shape || shape.tuple) return null; + const hasKeys = shape.fields.length > 0 || shape.indexValue !== undefined; + const branch = hasKeys ? type.getFalseType() : type.getTrueType(); + return branch ? mapType(branch, ctx) : null; } /** A mapped type over a type parameter of the instance being lowered (`{ @@ -4909,7 +5717,7 @@ function mapBoundMappedType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null const fields: { name: string; type: IrType }[] = []; const order: string[] = []; for (const property of checker.getPropertiesOfType(bound)) { - const name = property.name; + const name = ts.symbolName(property); const evaluated = evalMappedTemplate(template, new Map([[key, name]]), new Map(), ctx); let type = evaluated ? mapType(evaluated, ctx) : null; if (!type) return null; @@ -5017,10 +5825,13 @@ function matchInferReference( const target = (t: ts.Type): ts.Type | undefined => t.isTypeReference?.() ? (t as ts.TypeReference).getTarget?.() : undefined; const wantTarget = target(extendsType) ?? extendsType.getAliasSymbol(); - if (!wantTarget) return undefined; + if (!wantTarget) return matchInferSignature(checked, extendsType, ctx); const gotTarget = target(checked) ?? checked.getAliasSymbol(); - if (!gotTarget) return checked.flags & ts.TypeFlags.Object ? undefined : false; - if (gotTarget !== wantTarget) return undefined; + if (!gotTarget) + return checked.flags & ts.TypeFlags.Object + ? matchInferThenable(checked, extendsType, ctx) + : false; + if (gotTarget !== wantTarget) return matchInferThenable(checked, extendsType, ctx); const wantArgs = target(extendsType) ? checker.getTypeArguments(extendsType as ts.TypeReference) : extendsType.getAliasTypeArguments(); @@ -5041,6 +5852,154 @@ function matchInferReference( return inferred; } +/** The type parameter an `infer X` declares, when `t` is one. */ +function inferSymbolOf(t: ts.Type, _ctx: TypeMapperCtx): ts.Symbol | undefined { + if (!(t.flags & ts.TypeFlags.TypeParameter)) return undefined; + const symbol = t.getSymbol(); + return symbol && + symbol.declarations.some((d) => d.parent !== undefined && ts.isInferTypeNode(d.parent)) + ? symbol + : undefined; +} + +/** `checked extends (...args: any) => infer R` — the shape of the standard + * library's ReturnType, over a concrete function type: R binds to its + * result. Pattern parameters must be `any` or `any[]`, and a pattern rest + * `any` or `any[]`; anything else stays undecided. The match follows + * TypeScript's assignability: a primitive never matches a function type, + * a function that needs more arguments than a pattern without a rest + * passes never matches, and a positional `any[]` parameter needs the + * function's parameter there to accept an array. */ +function matchInferSignature( + checked: ts.Type, + extendsType: ts.Type, + ctx: TypeMapperCtx, +): Map | false | undefined { + const { checker } = ctx; + const wantSigs = checker.getCallSignatures(extendsType); + const want = wantSigs[0]; + if (wantSigs.length !== 1 || !want || want.getTypeParameters().length > 0) return undefined; + const wantShape = signatureShape(want, ctx); + if (wantShape === undefined) return undefined; + const isAny = (t: ts.Type): boolean => (t.flags & ts.TypeFlags.Any) !== 0; + const isAnyArray = (t: ts.Type): boolean => + checker.isArrayType(t) && isAny(checker.getTypeArguments(t as ts.TypeReference)[0]!); + if (!wantShape.params.every((p) => isAny(p.type) || isAnyArray(p.type))) return undefined; + if (wantShape.rest !== null && !isAny(wantShape.rest)) return undefined; + if (!(checked.flags & ts.TypeFlags.Object)) return false; + const gotSigs = checker.getCallSignatures(checked); + const got = gotSigs[0]; + if (gotSigs.length !== 1 || !got || got.getTypeParameters().length > 0) return undefined; + const gotShape = signatureShape(got, ctx); + if (gotShape !== undefined) { + // `(a: any) => infer R` rejects `(a: string, b: string) => R` and + // `(...args: [string, number]) => R`: they need more arguments. + if (wantShape.rest === null && requiredArguments(gotShape) > wantShape.params.length) + return false; + for (let i = 0; i < wantShape.params.length; i++) { + if (!isAnyArray(wantShape.params[i]!.type)) continue; + const param = gotShape.params[i]?.type ?? gotShape.rest; + if (param === undefined || param === null) continue; + const accepts = acceptsArray(param, ctx); + if (accepts !== true) return accepts; + } + } else if (wantShape.params.some((p) => isAnyArray(p.type))) { + return undefined; + } + const wantRet = checker.getReturnTypeOfSignature(want); + const infer = inferSymbolOf(wantRet, ctx); + if (infer) return new Map([[infer, checker.getReturnTypeOfSignature(got)]]); + return wantRet.flags & ts.TypeFlags.Any ? new Map() : undefined; +} + +/** A signature's parameters as a call sees them: the positional ones, with + * a fixed-tuple rest (`...args: [A, B?]`) flattened into positions, and the + * element type of an array rest (`any` for `...args: any`). Undefined when + * a parameter has no declaration, or the rest is a variadic tuple or + * another type. */ +function signatureShape( + sig: ts.Signature, + ctx: TypeMapperCtx, +): { params: { type: ts.Type; optional: boolean }[]; rest: ts.Type | null } | undefined { + const { checker } = ctx; + const params: { type: ts.Type; optional: boolean }[] = []; + for (const p of sig.getParameters()) { + const decl = p.valueDeclaration; + if (decl === undefined || !ts.isParameter(decl)) return undefined; + const type = checker.getTypeOfSymbol(p); + if (decl.dotDotDotToken === undefined) { + params.push({ + type, + optional: decl.questionToken !== undefined || decl.initializer !== undefined, + }); + continue; + } + if (type.flags & ts.TypeFlags.Any) return { params, rest: type }; + if (checker.isArrayType(type)) + return { params, rest: checker.getTypeArguments(type as ts.TypeReference)[0]! }; + const elems = restTupleElements(type, ctx); + if (!elems) return undefined; + for (const e of elems) params.push({ type: e.type, optional: e.optional }); + return { params, rest: null }; + } + return { params, rest: null }; +} + +/** The fewest arguments a call must pass: up to the last required position. */ +function requiredArguments(shape: { params: { optional: boolean }[] }): number { + let required = 0; + shape.params.forEach((p, i) => { + if (!p.optional) required = i + 1; + }); + return required; +} + +/** Whether a parameter of this type accepts an `any[]` argument: true for + * `any`, `unknown` and array types, false for primitives, undefined + * otherwise. */ +function acceptsArray(t: ts.Type, ctx: TypeMapperCtx): boolean | undefined { + if (t.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) return true; + if (ctx.checker.isArrayType(t)) return true; + const primitive = + ts.TypeFlags.StringLike | + ts.TypeFlags.NumberLike | + ts.TypeFlags.BooleanLike | + ts.TypeFlags.BigIntLike | + ts.TypeFlags.ESSymbolLike | + ts.TypeFlags.Undefined | + ts.TypeFlags.Null | + ts.TypeFlags.Void; + return t.flags & primitive ? false : undefined; +} + +/** `checked extends PromiseLike` (or `Promise`) over the + * standard library's Promise: U binds to the settled type. An object with + * no `then` member is no thenable. Everything else stays undecided. */ +function matchInferThenable( + checked: ts.Type, + extendsType: ts.Type, + ctx: TypeMapperCtx, +): Map | false | undefined { + const { checker } = ctx; + const stdlibNamed = (t: ts.Type, names: readonly string[]): boolean => { + const symbol = t.getSymbol(); + return ( + symbol !== undefined && + names.includes(ts.symbolName(symbol)) && + symbol.declarations.some((d) => ctx.isStdlibFile(ts.getSourceFileOfNode(d))) + ); + }; + if (!stdlibNamed(extendsType, ["PromiseLike", "Promise"])) return undefined; + const wantArgs = checker.getTypeArguments(extendsType as ts.TypeReference); + const infer = wantArgs.length === 1 ? inferSymbolOf(wantArgs[0]!, ctx) : undefined; + if (!infer) return undefined; + if (stdlibNamed(checked, ["Promise", "PromiseLike"])) { + const gotArgs = checker.getTypeArguments(checked as ts.TypeReference); + return gotArgs.length === 1 ? new Map([[infer, gotArgs[0]!]]) : undefined; + } + return checker.getPropertyOfType(checked, "then") === undefined ? false : undefined; +} + /** The type mentions a mapped key or bound alias parameter (a template * shape evalMappedTemplate does not reduce). */ function mentionsMappedParams( @@ -5057,23 +6016,37 @@ function mentionsMappedParams( return false; } +function readonlyClassType(type: ts.Type, ctx: TypeMapperCtx): IrType | null { + const alias = type.getAliasSymbol(); + if (alias === undefined || ts.symbolName(alias) !== "Readonly") return null; + const args = type.getAliasTypeArguments(); + if (args.length !== 1 || !args[0]) return null; + const isLibAlias = alias.declarations.some( + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), + ); + if (!isLibAlias) return null; + const mapped = mapType(args[0], ctx); + return mapped !== null && isDynTypedRefType(mapped) ? mapped : null; +} + function mapGenericUtilityAlias(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { const { resolveTypeParam, shapes, unions } = ctx; if (!resolveTypeParam) return null; const alias = widened.getAliasSymbol(); - if (!alias || (alias.name !== "Partial" && alias.name !== "Readonly")) return null; + if (!alias || (ts.symbolName(alias) !== "Partial" && ts.symbolName(alias) !== "Readonly")) + return null; const aliasArgs = widened.getAliasTypeArguments(); const arg = aliasArgs[0]; if (aliasArgs.length !== 1 || !arg) return null; if (!(arg.flags & ts.TypeFlags.TypeParameter)) return null; // Provenance: the STANDARD LIBRARY's alias, not a user's shadowing one. const isLibAlias = alias.declarations.some( - (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isTypeAliasDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ); if (!isLibAlias) return null; const bound = resolveTypeParam(arg); if (!bound || bound.kind !== "record") return null; - if (alias.name === "Readonly") return bound; + if (ts.symbolName(alias) === "Readonly") return bound; const shape = shapes.get(bound.shapeId); if (!shape) return null; // Partial over an index-signature shape would need `V | undefined` @@ -5191,10 +6164,22 @@ export function isUnitOnlyTsType(t: ts.Type, resolveTypeParam?: TypeParamResolve return parts.every( (p) => (p.flags & UNIT) !== 0 || - ((p.flags & ts.TypeFlags.TypeParameter) !== 0 && resolveTypeParam?.(p)?.kind === "void"), + ((p.flags & ts.TypeFlags.TypeParameter) !== 0 && resolveTypeParam?.(p)?.kind === "void") || + boundVoidReturnType(p, resolveTypeParam), ); } +/** `ReturnType` over a type parameter bound to a void-returning + * function (a variadic wrapper's result slot): a unit-only value. */ +function boundVoidReturnType(p: ts.Type, resolveTypeParam?: TypeParamResolver): boolean { + if (!resolveTypeParam || ts.symbolNameOf(p.getAliasSymbol()) !== "ReturnType") return false; + const args = p.getAliasTypeArguments(); + const arg = args[0]; + if (args.length !== 1 || !arg || !(arg.flags & ts.TypeFlags.TypeParameter)) return false; + const bound = resolveTypeParam(arg); + return bound?.kind === "func" && bound.ret.kind === "void"; +} + /** IR-level `t | undefined`, canonicalized and fenced exactly like the * ts-union branch of mapType (typeKey-sorted arms, deduplicated; * Date/dyn/void arms unrepresentable; a func arm IS representable — the @@ -5278,8 +6263,8 @@ function mapHybridCallableIntersection(widened: ts.Type, ctx: TypeMapperCtx): Ir const pt = mapType(checker.getTypeOfSymbol(p), ctx); if (!pt || pt.kind === "void") return null; - fields.push({ name: p.name, type: pt }); - declaredOrder.push(p.name); + fields.push({ name: ts.symbolName(p), type: pt }); + declaredOrder.push(ts.symbolName(p)); } if (fields.length === 1) return null; // no properties — it is just F fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); @@ -5321,12 +6306,13 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType "URIError", "AggregateError", "SuppressedError", - ].includes(symbol.name) && - symbol.declarations.some((decl) => ctx.isStdlibFile(decl.getSourceFile())) + ].includes(ts.symbolName(symbol)) && + symbol.declarations.some((decl) => ctx.isStdlibFile(ts.getSourceFileOfNode(decl))) ); }); if (!hasError) return null; let error: IrType | null = null; + const extra: ts.Symbol[] = []; for (const part of parts) { const mapped = mapType(part, ctx); if (mapped?.kind === "object" && RUNTIME_ERROR_CLASSES.has(mapped.className)) { @@ -5342,19 +6328,50 @@ function mapErrorMetadataIntersection(type: ts.Type, ctx: TypeMapperCtx): IrType return null; for (const property of ctx.checker.getPropertiesOfType(part)) { const expected = - property.name === "errno" + ts.symbolName(property) === "errno" ? "f64" - : ["code", "syscall", "path", "dest"].includes(property.name) + : ["code", "syscall", "path", "dest"].includes(ts.symbolName(property)) ? "string" : null; - if (!expected || !(property.flags & ts.SymbolFlags.Optional)) return null; + if (!expected) { + extra.push(property); + continue; + } const field = mapType(ctx.checker.getTypeOfSymbol(property), ctx); const arms = field?.kind === "union" ? ctx.unions.get(field.unionId)?.arms : field ? [field] : []; if (!arms?.length || !arms.every((arm) => arm.kind === expected || arm.kind === "undefinedT")) return null; + // A required member must still admit the absent value the slot reads. + if ( + !(property.flags & ts.SymbolFlags.Optional) && + !arms.some((arm) => arm.kind === "undefinedT") + ) + return null; } } + if (!error) return null; + // Members beyond Error's own and the system-error metadata (`Error & { + // status: number }`, Object.assign's result) are own properties of the + // error's checked-dynamic node: each must map to a type a checked read + // can produce. Error's own string members keep their slots. + for (const property of extra) { + const field = mapType(ctx.checker.getTypeOfSymbol(property), ctx); + if (!field || field.kind === "void") return null; + if (ts.symbolName(property) === "message" || ts.symbolName(property) === "name") { + if (field.kind !== "string") return null; + continue; + } + if (ts.symbolName(property) === "stack" || ts.symbolName(property) === "cause") return null; + if ( + !canDynCheckTo( + field, + (id) => ctx.shapes.get(id), + (id) => ctx.unions.get(id), + ) + ) + return null; + } return error; } @@ -5465,7 +6482,8 @@ function immutableSourceType(type: ts.Type, ctx: TypeMapperCtx): ts.Type | null const { checker } = ctx; const symbol = type.getSymbol(); const decl = symbol ? symbol.declarations[0] : undefined; - if (decl === undefined || !CONTRACT_TYPES_FILE.test(decl.getSourceFile().fileName)) return null; + if (decl === undefined || !CONTRACT_TYPES_FILE.test(ts.getSourceFileOfNode(decl).fileName)) + return null; const props = checker.getPropertiesOfType(type); const seen = new Set(); for (const prop of props) { @@ -5587,14 +6605,14 @@ function recordProvenanceOk(t: ts.Type, ctx: TypeMapperCtx): boolean { .every((property) => property.declarations.every( (decl) => - !decl.getSourceFile().isDeclarationFile || - ctx.isExternalTypeFile(decl.getSourceFile()) || - packageTypeFile(decl.getSourceFile(), ctx), + !ts.getSourceFileOfNode(decl).isDeclarationFile || + ctx.isExternalTypeFile(ts.getSourceFileOfNode(decl)) || + packageTypeFile(ts.getSourceFileOfNode(decl), ctx), ), ); } return !decls.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }); } @@ -5671,7 +6689,7 @@ export function erasedGenericMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType const sigs = ctx.checker.getCallSignatures(t); if (sigs.length !== 1) return undefined; const sig = sigs[0]!; - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const erased = mapType(t, { ...ctx, @@ -5703,7 +6721,7 @@ export function isOverloadedCallableMemberType(t: ts.Type, checker: ts.TypeCheck if (decl === undefined) return false; if (!ts.isMethodSignatureDeclaration(decl) && !ts.isCallSignatureDeclaration(decl)) return false; - const file = decl.getSourceFile(); + const file = ts.getSourceFileOfNode(decl); return !file.isDeclarationFile && !isJsSourceFile(file); }); } @@ -5765,7 +6783,7 @@ function joinOverloadTypes(types: readonly IrType[], ctx: TypeMapperCtx): IrType export function erasedOverloadMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType | undefined { const funcs: (IrType & { kind: "func" })[] = []; for (const sig of ctx.checker.getCallSignatures(t)) { - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const mapped = mapCallSignature( sig, @@ -5811,7 +6829,7 @@ export function erasedOverloadMemberType(t: ts.Type, ctx: TypeMapperCtx): IrType function describeOverloadSetBlocker(t: ts.Type, ctx: TypeMapperCtx): string { const { checker } = ctx; for (const sig of checker.getCallSignatures(t)) { - const own = new Set(sig.getTypeParameters()); + const own = new Set(sig.getTypeParameters()); const outer = ctx.resolveTypeParam; const sigCtx: TypeMapperCtx = own.size === 0 @@ -5824,14 +6842,14 @@ function describeOverloadSetBlocker(t: ts.Type, ctx: TypeMapperCtx): string { const mapped = mapCallSignature(sig, sigCtx); if (mapped?.kind === "func" && mapped.rest !== true && mapped.argumentsAll !== true) continue; const decl = sig.declaration; - const spelled = decl ? decl.getText().replace(/\s+/g, " ").replace(/;$/, "") : ""; + const spelled = decl ? ts.getTextOfNode(decl).replace(/\s+/g, " ").replace(/;$/, "") : ""; const which = spelled ? `overload '${spelled}'` : "one overload"; if (mapped?.kind === "func") return `is an overload set, and its ${which} has a rest parameter (an overload set stores one fixed-arity function)`; for (const param of sig.getParameters()) { const paramTs = checker.getTypeOfSymbol(param); if (!mapType(paramTs, sigCtx)) - return `is an overload set, and its ${which} has parameter '${param.name}' of type '${checker.typeToString(paramTs)}', which does not compile`; + return `is an overload set, and its ${which} has parameter '${ts.symbolName(param)}' of type '${checker.typeToString(paramTs)}', which does not compile`; } const retTs = checker.getReturnTypeOfSignature(sig); return `is an overload set, and its ${which} returns '${checker.typeToString(retTs)}', which does not compile`; @@ -5847,6 +6865,7 @@ interface RecordShapeParts { fields: { name: string; type: IrType }[]; indexValue?: IrType; declaredOrder?: string[]; + accessorOrder?: string[]; } function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { @@ -5883,15 +6902,14 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { // (generic type parameters, mixin instantiations) cannot intern by // checker-type identity — fence recursive generic-open shapes. if (contextResolutions !== sensitivityAtEntry) return null; - return { - kind: "record", - shapeId: shapes.finalizeRecursive( - widened, - inner.fields, - inner.indexValue, - inner.declaredOrder, - ), - }; + const recursive = shapes.finalizeRecursive( + widened, + inner.fields, + inner.indexValue, + inner.declaredOrder, + ); + adoptAccessorOrder(shapes.get(recursive), inner.accessorOrder); + return { kind: "record", shapeId: recursive }; } // A record with no typed payload contract is a view of an existing // object, including across parameters and returns. Building a native @@ -5904,17 +6922,294 @@ function mapRecordType(widened: ts.Type, ctx: TypeMapperCtx): IrType | null { (inner.indexValue === undefined || inner.indexValue.kind === "dyn") ) return DYN; + const covered = coveredOptionalFieldsShape(widened, inner, ctx); + if (covered !== null) return { kind: "record", shapeId: covered }; + const merged = dictSpreadLiteralShape(widened, inner, ctx); + if (merged === "dyn") return DYN; + if (merged !== null) return { kind: "record", shapeId: merged }; const refined = refinedConstituentShape(widened, inner, ctx); if (refined !== null) return { kind: "record", shapeId: refined }; - return { - kind: "record", - shapeId: shapes.intern(inner.fields, false, inner.indexValue, inner.declaredOrder), - }; + const shapeId = shapes.intern(inner.fields, false, inner.indexValue, inner.declaredOrder); + adoptAccessorOrder(shapes.get(shapeId), inner.accessorOrder); + return { kind: "record", shapeId }; } finally { shapes.inProgress.delete(widened); } } +/** An index signature already describes every OPTIONAL declared member + * whose type is its slot type: @types/node's `ProcessEnv extends + * Dict { TZ?: string }` and every spread of it, or a declared + * environment `{ CI?: string } & Dictionary`. At runtime such a + * member is just another key, so the shape interns as the pure + * index-signature record. Its slot admits undefined, as the optional + * members do (`env.CI = undefined` keeps the key). A hybrid layout would + * give each member a struct slot, which enumerates before the overflow + * keys (Node keeps one insertion order) and makes `delete` and spread + * answer differently for declared and undeclared keys. Required members, + * and members typed otherwise than the slot (whose reads the slot type + * could not give back), keep the hybrid shape. */ +function coveredOptionalFieldsShape( + widened: ts.Type, + parts: RecordShapeParts, + ctx: TypeMapperCtx, +): string | null { + const slot = parts.indexValue; + if (!slot || parts.fields.length === 0) return null; + const { checker, shapes, unions } = ctx; + // The members' common type must be the slot plus undefined. Read it off + // the members (already interned) rather than building it: a hybrid that + // does not qualify must not intern anything new. + const optionalSlot = parts.fields[0]!.type; + if (!parts.fields.every((field) => typeEquals(field.type, optionalSlot))) return null; + if (optionalSlot.kind !== "union" || unions.armTag(optionalSlot.unionId, UNDEFINED_T) < 0) + return null; + const memberArms = unions.get(optionalSlot.unionId)?.arms ?? []; + const slotArms = + slot.kind === "union" ? (unions.get(slot.unionId)?.arms ?? []) : isUnitType(slot) ? [] : [slot]; + const definedArms = memberArms.filter((arm): boolean => arm.kind !== "undefinedT"); + const slotDefined = slotArms.filter((arm): boolean => arm.kind !== "undefinedT"); + if ( + definedArms.length === 0 || + definedArms.length !== slotDefined.length || + !slotDefined.every((arm) => definedArms.some((other) => typeEquals(arm, other))) + ) + return null; + if (!isSupportedIndexValue(optionalSlot)) return null; + const props = checker.getPropertiesOfType(widened); + if (props.length !== parts.fields.length) return null; + if (!props.every((p) => (p.flags & ts.SymbolFlags.Optional) !== 0)) return null; + return shapes.intern([], false, optionalSlot, []); +} + +/** The type of an object literal that spreads an index-signature record + * (`{ ...tags, bucket: b, status }`, the metric-tags idiom). The checker + * drops the signature and types the literal by its explicit members only, + * but at run time the object holds every key the spread copied, in one + * insertion order. Such a literal maps to the pure index-signature record + * whose slot joins the spread slots and the member types, so the merge, + * every enumeration and keyed writes see the same keys Node does. A + * member read still answers its member type: members written after the + * spreads always win, and a member written before a spread must have that + * spread's slot type, so an override cannot change its type. The join + * covers scalar and unit types only; other literals keep their shape and + * the spread fence. */ +function dictSpreadLiteralShape( + widened: ts.Type, + parts: RecordShapeParts, + ctx: TypeMapperCtx, +): string | "dyn" | null { + if (parts.indexValue) return null; + const symbol = widened.getSymbol(); + if (!symbol || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return null; + const literal = symbol.declarations.find((d): d is ts.ObjectLiteralExpression => + ts.isObjectLiteralExpression(d), + ); + if (!literal) return null; + const { checker, shapes, unions } = ctx; + const props = literal.properties.nodes; + if (!props.some((p) => ts.isSpreadAssignment(p))) return null; + // Decide on checker types alone before mapping anything: object literals + // with ordinary spreads are everywhere, and mapping their sources here + // would intern types out of their usual order. + // A literal written into a typed index-signature slot already lowers into + // it (the merge into the contextual record); its own type keeps its + // mapping. + const contextualRaw = checker.getContextualType(literal); + const contextual = contextualRaw ? checker.getNonNullableType(contextualRaw) : undefined; + // A literal written into a declared slot lowers into that slot (a typed + // index-signature record merges into it, a declared record converts), + // and its own type keeps its mapping. Only a literal with no declared + // destination (an unannotated binding, a type parameter, `any` or + // `unknown`, an unknown-valued record) is built by its own type. + // A generic argument's context is the instantiated parameter: the + // literal's own type when the type argument was inferred from it. + const ownContext = + contextual !== undefined && + (contextual === widened || contextual.getSymbol()?.declarations.includes(literal) === true); + if ( + contextual && + !ownContext && + !(contextual.flags & (ts.TypeFlags.TypeParameter | ts.TypeFlags.Any | ts.TypeFlags.Unknown)) + ) { + const infos = checker.getIndexInfosOfType(contextual); + const unknownValued = + infos.length > 0 && + infos.every((info) => (info.type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0); + if (!unknownValued) return null; + } + const kinds = props.map((p) => + ts.isSpreadAssignment(p) ? spreadSourceKindTs(checker, p.expression) : "member", + ); + if (kinds.some((k) => k === null) || !kinds.includes("dict")) return null; + const dictSlotOf = (e: ts.Expression): IrType | null => { + const t = checker.getNonNullableType(checker.getTypeAtLocation(e)); + if (checker.getPropertiesOfType(t).length === 0) { + const infos = checker.getIndexInfosOfType(t); + if (infos.length !== 1 || !(infos[0]!.keyType.flags & ts.TypeFlags.String)) return null; + return mapType(infos[0]!.type, ctx); + } + // Another such literal's type (`{ ...tags, repo }` over a binding + // initialized by `{ ...base, plan }`) is stored as the same kind of + // pure index-signature record. + if (t === widened) return null; + const mapped = mapType(t, ctx); + if (mapped?.kind === "dyn") return DYN; + const shape = mapped?.kind === "record" ? shapes.get(mapped.shapeId) : undefined; + return shape?.indexValue && shape.fields.length === 0 && !shape.tuple ? shape.indexValue : null; + }; + const arms = new Map(); + const addArms = (t: IrType): boolean => { + const list = t.kind === "union" ? (unions.get(t.unionId)?.arms ?? []) : [t]; + for (const arm of list) { + if (!OVERLOAD_JOIN_SCALARS.has(arm.kind)) return false; + arms.set(typeKey(arm), arm); + } + return true; + }; + const before: string[] = []; + // An unknown-valued spread (`Record`, or another such + // literal) makes the object the checked-dynamic value: it is built like + // a JavaScript object literal (copy each spread's own keys in order), and + // member reads check their declared types. + let unknownValued = false; + for (const [i, p] of props.entries()) { + // A fixed-shape or conditional spread contributes its own members, + // which the checker already types exactly (they are in `parts`). + if (kinds[i] === "record") { + // Its members may be overwritten by a later dict spread, like members + // written before one. + before.push(...spreadMemberNamesTs(checker, (p as ts.SpreadAssignment).expression)); + continue; + } + if (ts.isSpreadAssignment(p)) { + const slot = dictSlotOf(p.expression); + if (slot?.kind === "dyn") { + unknownValued = true; + continue; + } + if (!slot) return null; + if (!addArms(slot)) { + // A slot outside the scalar join still has the checked-dynamic + // representation when it converts to it. + if ( + !canConvertToDyn( + slot, + (id) => shapes.get(id), + (id) => unions.get(id), + ) + ) + return null; + unknownValued = true; + } + // A member written before this spread may be overwritten by it: the + // typed join keeps member types only where the slot is that type; + // otherwise the checked-dynamic object (whose member reads check + // their types) holds whatever the spread wrote. + for (const name of before) { + const field = parts.fields.find((f) => f.name === name); + if (!field) return null; + if (!typeEquals(field.type, slot)) unknownValued = true; + } + continue; + } + if (!(ts.isPropertyAssignment(p) || ts.isShorthandPropertyAssignment(p))) return null; + const name = p.name; + if (!(ts.isIdentifier(name) || ts.isStringLiteral(name))) return null; + before.push(name.text); + } + if (parts.fields.some((f) => f.name.startsWith("%"))) return null; + // Members outside the scalar join (an array of labels beside the tags) + // make the object the checked-dynamic value too. + if (!unknownValued && !parts.fields.every((f) => addArms(f.type))) unknownValued = true; + if (unknownValued) { + const getRecord = (id: string) => shapes.get(id); + const getUnion = (id: string) => unions.get(id); + return parts.fields.every( + (f) => f.type.kind === "dyn" || canConvertToDyn(f.type, getRecord, getUnion), + ) + ? "dyn" + : null; + } + for (const field of parts.fields) if (!addArms(field.type)) return null; + if (![...arms.values()].some((arm) => !isUnitType(arm))) return null; + const sorted = [...arms.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1)); + const slot: IrType = + sorted.length === 1 ? sorted[0]! : { kind: "union", unionId: unions.intern(sorted) }; + return shapes.intern([], false, slot, []); +} + +/** How a spread source contributes to dictSpreadLiteralShape, judged on + * checker types only: "dict" for a pure string index signature or another + * literal that spreads one, "record" for a fixed object shape or a + * conditional spread (`...(c && { k: v })`), null otherwise. */ +function spreadSourceKindTs(checker: ts.TypeChecker, e: ts.Expression): "dict" | "record" | null { + if (dictSpreadSourceTs(checker, e, 0)) return "dict"; + let inner = e; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + if ( + (ts.isBinaryExpression(inner) && + inner.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) || + ts.isConditionalExpression(inner) + ) + return "record"; + const t = checker.getNonNullableType(checker.getTypeAtLocation(e)); + if ((t.flags & ts.TypeFlags.Object) === 0 || t.isUnionType()) return null; + if (checker.getIndexInfosOfType(t).length > 0) return null; + if (checker.getCallSignatures(t).length > 0 || checker.getConstructSignatures(t).length > 0) + return null; + const symbol = t.getSymbol(); + if (symbol && (symbol.flags & ts.SymbolFlags.Class) !== 0) return null; + return "record"; +} + +/** The member names a fixed-shape or conditional spread contributes. */ +function spreadMemberNamesTs(checker: ts.TypeChecker, e: ts.Expression): string[] { + let inner = e; + while (ts.isParenthesizedExpression(inner)) inner = inner.expression; + const literals: ts.Expression[] = []; + if (ts.isBinaryExpression(inner)) literals.push(inner.right); + else if (ts.isConditionalExpression(inner)) literals.push(inner.whenTrue, inner.whenFalse); + else literals.push(inner); + const names: string[] = []; + for (const l of literals) { + const t = checker.getNonNullableType(checker.getTypeAtLocation(l)); + for (const property of checker.getPropertiesOfType(t)) names.push(ts.symbolName(property)); + } + return names; +} + +/** A spread source the checker types as a pure string index signature, + * or as another object literal that spreads one (dictSpreadLiteralShape), + * judged on checker types only. */ +function dictSpreadSourceTs(checker: ts.TypeChecker, e: ts.Expression, depth: number): boolean { + const t = checker.getNonNullableType(checker.getTypeAtLocation(e)); + if (checker.getPropertiesOfType(t).length === 0) { + const infos = checker.getIndexInfosOfType(t); + return infos.length === 1 && (infos[0]!.keyType.flags & ts.TypeFlags.String) !== 0; + } + if (depth > 4) return false; + const symbol = t.getSymbol(); + if (!symbol || (symbol.flags & ts.SymbolFlags.ObjectLiteral) === 0) return false; + const literal = symbol.declarations.find((d): d is ts.ObjectLiteralExpression => + ts.isObjectLiteralExpression(d), + ); + if (!literal) return false; + const spreads = literal.properties.nodes.filter((p) => ts.isSpreadAssignment(p)); + return ( + spreads.length > 0 && + spreads.every((p) => + dictSpreadSourceTs(checker, (p as ts.SpreadAssignment).expression, depth + 1), + ) + ); +} + +/** The first-seen accessor key order of a shape with accessor slots. */ +function adoptAccessorOrder(shape: IrRecordShape | undefined, order: string[] | undefined): void { + if (shape !== undefined && order !== undefined && shape.accessorOrder === undefined) + shape.accessorOrder = order; +} + /** A refinement intersection (`Expr & { kind: "mul" }`, which the checker * reduces to `Mul & { kind: "mul" }`) keeps the shape of the constituent it * refines when the refinement changes no field's representation. Recursive @@ -5931,11 +7226,11 @@ function refinedConstituentShape( ): string | null { if (!widened.isIntersectionType()) return null; const { checker, shapes } = ctx; - const names = checker.getPropertiesOfType(widened).map((prop) => prop.name); + const names = checker.getPropertiesOfType(widened).map((prop) => ts.symbolName(prop)); let found: string | null = null; for (const part of ts.constituentTypes(widened)) { if ((part.flags & ts.TypeFlags.Object) === 0 || shapes.inProgress.has(part)) continue; - const own = new Set(checker.getPropertiesOfType(part).map((prop) => prop.name)); + const own = new Set(checker.getPropertiesOfType(part).map((prop) => ts.symbolName(prop))); if (own.size !== names.length || !names.every((name) => own.has(name))) continue; const mapped = mapType(part, ctx); if (mapped?.kind !== "record") continue; @@ -5958,6 +7253,30 @@ function refinedConstituentShape( return found; } +/** The optional `undefined` members widening adds to an object literal's + * type for properties only its sibling literals declare (`[{ a: 1 }, { b: 2 + * }]` gives the first `b?: undefined`). They are never own keys of the + * literal's value, so they go after the declared members, by name. Where + * the checker lists them is not observable: TypeScript 7.0.2 orders them + * by the literal its checker widened first, TypeScript 7.1 by the sibling + * that declares them. */ +function wideningAddedProperties( + type: ts.Type, + props: readonly ts.Symbol[], + ctx: TypeMapperCtx, +): Set { + const added = new Set(); + const literal = type.getSymbol()?.declarations.find(ts.isObjectLiteralExpression); + if (literal === undefined || literal.properties.nodes.some(ts.isSpreadAssignment)) return added; + for (const prop of props) { + if ((prop.flags & ts.SymbolFlags.Optional) === 0 || prop.declarations.length === 0) continue; + if (prop.declarations.some((d) => d.parent === literal)) continue; + if ((ctx.checker.getTypeOfSymbol(prop).flags & ts.TypeFlags.Undefined) === 0) continue; + added.add(ts.symbolName(prop)); + } + return added; +} + function mapRecordTypeInner( widened: ts.Type, ctx: TypeMapperCtx, @@ -6016,7 +7335,7 @@ function mapRecordTypeInner( // `Dictionary`) holds whatever the program stores: its // overflow values are checked-dynamic, exactly `Record`'s. - const iv = info.valueType.flags & ts.TypeFlags.Any ? DYN : mapType(info.valueType, ctx); + const iv = info.type.flags & ts.TypeFlags.Any ? DYN : mapType(info.type, ctx); if (!iv || !isSupportedIndexValue(iv)) return null; if (v !== null && !typeEquals(v, iv)) return null; v = iv; @@ -6109,7 +7428,7 @@ function mapRecordTypeInner( if (ctx.classImplementedProtocol?.(widened)) return DYN; // Symbol-keyed records use the checked object's descriptor table. A // checker-internal __@ name is not a string field in a native struct. - if (props.some((property) => property.name.startsWith("__@"))) return DYN; + if (props.some((property) => ts.symbolName(property).startsWith("__@"))) return DYN; // A computed shape with NO members is not a real empty record: inside a // generic body `Partial` resolves to no members at all (keyof T is // still unknown there) — interning `{}` would give a wrong type. The @@ -6168,13 +7487,13 @@ function mapRecordTypeInner( (ts.isObjectLiteralExpression(d.parent) || ts.isInterfaceDeclaration(d.parent) || d.parent.kind === ts.SyntaxKind.TypeLiteral) && - !d.getSourceFile().isDeclarationFile && - !isJsSourceFile(d.getSourceFile()), + !ts.getSourceFileOfNode(d).isDeclarationFile && + !isJsSourceFile(ts.getSourceFileOfNode(d)), ); if (!accessorOwned || (!getDecl && !setDecl)) return null; // Symbol-keyed accessors have no foldable literal name to fill at // the literal — no slot to make. - if (p.name.startsWith("__@")) return null; + if (ts.symbolName(p).startsWith("__@")) return null; // An index-signature shape stores accessor NAMES nowhere the keyed // read/walk machinery can answer (the overflow would miss where // Node dispatches the getter) — those shapes stay unmapped. @@ -6196,8 +7515,9 @@ function mapRecordTypeInner( if (readT !== null && typeKey(readT) !== typeKey(writeT)) return null; } if (readT !== null || getDecl) - fields.push({ name: `%get:${p.name}`, type: funcOf([], readT ?? VOID) }); - if (writeT !== null) fields.push({ name: `%set:${p.name}`, type: funcOf([writeT], VOID) }); + fields.push({ name: `%get:${ts.symbolName(p)}`, type: funcOf([], readT ?? VOID) }); + if (writeT !== null) + fields.push({ name: `%set:${ts.symbolName(p)}`, type: funcOf([writeT], VOID) }); continue; } // Computed shapes carry provenance per MEMBER: a utility type over a @@ -6207,7 +7527,7 @@ function mapRecordTypeInner( if ( computed && p.declarations.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }) ) { @@ -6273,11 +7593,11 @@ function mapRecordTypeInner( // A DATA property spelled like a reserved accessor slot (`{ "%get:x": // v }` — a string-literal key): mapping it would collide with the // accessor dispatch, so the shape stays unmapped. - if (accessorSlotProp(p.name) !== null) return null; + if (accessorSlotProp(ts.symbolName(p)) !== null) return null; // an options record with `signal?: AbortSignal` must not turn its - fields.push({ name: p.name, type: pt }); + fields.push({ name: ts.symbolName(p), type: pt }); } // getPropertiesOfType yields DECLARATION order (interface/alias/literal // source order) — captured before the canonical sort so Object.keys/ @@ -6288,11 +7608,25 @@ function mapRecordTypeInner( // indices, from folded numeric computed keys and quoted numeric keys) // enumerate FIRST ascending, then string keys in insertion order // (OrdinaryOwnPropertyKeys) — Object.keys/JSON/inspect all read it. + const widenedAbsent = wideningAddedProperties(widened, props, ctx); const declaredOrder = esOwnKeyOrder( - fields.filter((f) => accessorSlotProp(f.name) === null).map((f) => f.name), + fields + .filter((f) => accessorSlotProp(f.name) === null && !widenedAbsent.has(f.name)) + .map((f) => f.name) + .concat([...widenedAbsent].sort()), ); + // With accessors: every own key in that order, an accessor property + // named once for its get/set pair (the checked-dynamic copy lists it). + const accessorOrder = fields.some((f) => accessorSlotProp(f.name) !== null) + ? esOwnKeyOrder([...new Set(fields.map((f) => accessorSlotProp(f.name)?.prop ?? f.name))]) + : undefined; fields.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)); - return { fields, ...(indexValue ? { indexValue } : {}), declaredOrder }; + return { + fields, + ...(indexValue ? { indexValue } : {}), + declaredOrder, + ...(accessorOrder ? { accessorOrder } : {}), + }; } } @@ -6354,6 +7688,23 @@ function dynSubsumableUnionArm(arm: IrType, ctx: TypeMapperCtx): boolean { } } +/** A parameter of a function type that is the default of a type parameter + * declared in node:test's standard-library declarations (MockTracker.fn's + * `F extends Function = (...args: any[]) => undefined`). */ +function isNodeTestDefaultFunctionParam(decl: ts.Declaration, ctx: TypeMapperCtx): boolean { + const fnType = decl.parent; + return ( + ts.isParameter(decl) && + fnType !== undefined && + ts.isFunctionTypeNode(fnType) && + fnType.parent !== undefined && + ts.isTypeParameterDeclaration(fnType.parent) && + fnType.parent.defaultType === fnType && + ctx.isStdlibFile(ts.getSourceFileOfNode(decl)) && + isDeclaredInAmbientModule(decl, "test") + ); +} + /** `any[]` (or `readonly any[]`): an array type whose element is `any`. */ export function isAnyArrayType(t: ts.Type, checker: ts.TypeChecker): boolean { if (!checker.isArrayType(t)) return false; @@ -6425,11 +7776,11 @@ export function describeComponentBlocker(widened: ts.Type, ctx: TypeMapperCtx): const psym = widened.getSymbol(); const container = psym !== undefined && - Object.prototype.hasOwnProperty.call(STDLIB_CONTAINERS, psym.name) && + Object.prototype.hasOwnProperty.call(STDLIB_CONTAINERS, ts.symbolName(psym)) && psym.declarations.some( - (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(d.getSourceFile()), + (d) => ts.isInterfaceDeclaration(d) && ctx.isStdlibFile(ts.getSourceFileOfNode(d)), ) - ? psym.name + ? ts.symbolName(psym) : undefined; if (container !== undefined) { const spec = STDLIB_CONTAINERS[container]!; @@ -6571,7 +7922,7 @@ export function describeComponentBlocker(widened: ts.Type, ctx: TypeMapperCtx): for (const p of sig.getParameters()) { const pTs = checker.getTypeOfSymbol(p); if (!mapType(pTs, ctx)) { - return `the function shape is supported, but its parameter '${p.name}' has type '${text(pTs)}', which does not compile`; + return `the function shape is supported, but its parameter '${ts.symbolName(p)}' has type '${text(pTs)}', which does not compile`; } } const retTs = checker.getReturnTypeOfSignature(sig); @@ -6603,14 +7954,14 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx // Accessor members and symbol-keyed members have their own multi-shaped // rules (mapRecordTypeInner) — no single-member claim is honest there. if ((p.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor)) !== 0) continue; - if (p.name.startsWith("__@")) continue; + if (ts.symbolName(p).startsWith("__@")) continue; // Computed shapes over LIBRARY members (`Readonly`): the record // fence there is per-member PROVENANCE (mapRecordTypeInner's rule), not // any one member's type — that story stays with the residual fence. if ( computed && p.declarations.some((d) => { - const sf = d.getSourceFile(); + const sf = ts.getSourceFileOfNode(d); return sf.isDeclarationFile && !ctx.isExternalTypeFile(sf) && !packageTypeFile(sf, ctx); }) ) { @@ -6619,7 +7970,7 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx const fieldTs = checker.getTypeOfSymbol(p); if (isOverloadedCallableMemberType(fieldTs, checker)) { if (erasedOverloadMemberType(fieldTs, ctx) !== undefined) continue; - return `the record shape is supported, but its member '${p.name}' ${describeOverloadSetBlocker(fieldTs, ctx)}`; + return `the record shape is supported, but its member '${ts.symbolName(p)}' ${describeOverloadSetBlocker(fieldTs, ctx)}`; } if (memberMaps(fieldTs, ctx, p)) continue; // Name the deepest member the failure comes from (`order.deployment. @@ -6627,9 +7978,9 @@ export function describeRecordMemberBlocker(widened: ts.Type, ctx: TypeMapperCtx // its outermost member. const inner = innerBlockedMember(fieldTs, ctx, new Set()); if (inner !== null && inner.path !== "") { - return `the record shape is supported, but its member '${p.name}${inner.path}' has type '${checker.typeToString(inner.type)}', which does not compile`; + return `the record shape is supported, but its member '${ts.symbolName(p)}${inner.path}' has type '${checker.typeToString(inner.type)}', which does not compile`; } - return `the record shape is supported, but its member '${p.name}' has type '${checker.typeToString(fieldTs)}', which does not compile`; + return `the record shape is supported, but its member '${ts.symbolName(p)}' has type '${checker.typeToString(fieldTs)}', which does not compile`; } return null; } @@ -6705,7 +8056,8 @@ function innerBlockedMember( } const symbol = t.getSymbol(); if ( - symbol?.name === "Promise" && + symbol !== undefined && + ts.symbolName(symbol) === "Promise" && symbol.declarations.some((d) => ctx.isStdlibFile(d.getSourceFile())) ) { const payload = checker.getTypeArguments(t as ts.TypeReference)[0]; @@ -6728,10 +8080,10 @@ function innerBlockedMember( return here; for (const p of checker.getPropertiesOfType(t)) { if ((p.flags & (ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor)) !== 0) continue; - if (p.name.startsWith("__@")) continue; + if (ts.symbolName(p).startsWith("__@")) continue; const fieldTs = checker.getTypeOfSymbol(p); if (memberMaps(fieldTs, ctx, p)) continue; - return step(`.${p.name}`, fieldTs); + return step(`.${ts.symbolName(p)}`, fieldTs); } return here; } diff --git a/packages/compiler/src/frontend/worker-target.ts b/packages/compiler/src/frontend/worker-target.ts index 99c97dded..5193f0539 100644 --- a/packages/compiler/src/frontend/worker-target.ts +++ b/packages/compiler/src/frontend/worker-target.ts @@ -84,7 +84,8 @@ export function isWorkerConstructor(program: ts.Program, expression: ts.Expressi export function workerModulePath(program: ts.Program, expression: ts.Expression): string | null { if (ts.isIdentifier(expression) && expression.text === "__filename") { - if (isNodeGlobal(program, expression)) return resolve(expression.getSourceFile().fileName); + if (isNodeGlobal(program, expression)) + return resolve(ts.getSourceFileOfNode(expression).fileName); } const value = staticForkString(program, expression); if (value === null) return null; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 7d8d88889..0f144ca2e 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -8,6 +8,7 @@ import { libraryWasmRefusal, } from "./library/prepare.js"; import { analyzeWithFrontend } from "./frontend/analysis.js"; +import { checkerCacheIdentity } from "./frontend/ts7/checker-backend.js"; import { analyzeWithVerdictCache, fileVerdictStore } from "./coverage/verdict-cache.js"; import { coverageCompilerIdentity } from "./coverage/verdict-startup.js"; import { commentOnlyReplay } from "./coverage/verdict-semantic.js"; @@ -379,6 +380,12 @@ export async function analyzeCached( const nodeFrontend: FrontendFactory = (entry, npmStatic, externalTypes, libraryNpmStatic) => runFrontend(entry, loadProgram, npmStatic, externalTypes, libraryNpmStatic); +/** The checker's part of the executable cache key (`checkerCacheIdentity`), as the CLI's startup route computes it. */ +function checkerField(): { checker?: string } { + const checker = checkerCacheIdentity(); + return checker === undefined ? {} : { checker }; +} + /** The whole pipeline: load → preflight → lower → validate → emit LLVM → link. */ function clearCompileSessionCaches(): void { clearResolveCaches(); @@ -528,6 +535,7 @@ async function compileExecutableNative( fileHandle: features.fileHandle, fetch: features.fetch, ...(features.eventTarget ? { eventTarget: true } : {}), + ...(features.wasm ? { wasm: true } : {}), ...(features.warningReplace ? { warningReplace: true } : {}), ...(features.stdioExtended ? { stdioExtended: true } : {}), zlib: features.zlib, @@ -537,18 +545,23 @@ async function compileExecutableNative( dc: features.dc, dynAsync: features.dynAsync, ...(features.dynTier ? { dynTier: true } : {}), + ...(features.streamDyn ? { streamDyn: true } : {}), + ...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}), events: features.events, emitter: features.emitter, symbol: features.symbol, bigint: features.bigint, searchParams: features.searchParams, qs: features.qs, + dynBuiltinClass: features.dynBuiltinClass, + dynBuiltinStream: features.dynBuiltinStream, parseArgs: features.parseArgs, stream: features.stream, net: features.net, http: features.http, http2: features.http2, dgram: features.dgram, + dnsPromises: features.dnsPromises, watch: features.watch, foreignFfi: features.foreignFfi, ...(features.workers ? { workers: true } : {}), @@ -558,6 +571,7 @@ async function compileExecutableNative( tlsCa: features.tlsCa, cipher: features.cipher, webcrypto: features.webcrypto, + perfLoop: features.perfLoop, ...(onArtifactReady === undefined ? {} : { onArtifactReady }), ...(ffi === null ? {} @@ -722,6 +736,7 @@ async function prepareExecutableInput( wasi: buildPlatform === "wasi", runtimeAbiMarker: outputKind === "obj", objectAudit: opts.sanitize === true, + ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), ...inlineRcOption(opts.optimization, opts.sanitize === true), }); } catch (err) { @@ -787,6 +802,7 @@ async function prepareExecutableInput( wasi: buildPlatform === "wasi", runtimeAbiMarker: useRuntimePack, objectAudit: opts.sanitize === true, + ...(opts.sanitize === true ? { wasmBoundsChecks: true } : {}), ...inlineRcOption(opts.optimization, opts.sanitize === true), ...(opts.detachedExit === true ? { detachedExit: true } : {}), }); @@ -1052,6 +1068,7 @@ async function compileTracked( ...(opts.strip ? { strip: true as const } : {}), ...(opts.windowsSubsystem === "gui" ? { windowsSubsystem: "gui" as const } : {}), ...(opts.detachedExit === true ? { detachedExit: true as const } : {}), + ...checkerField(), ...(youngNurseryRequested() ? { youngNursery: true as const } : {}), npmStatic: opts.npmStatic ?? null, ffiProfile: diff --git a/packages/compiler/src/ir/build.ts b/packages/compiler/src/ir/build.ts index adb7366cc..77259450c 100644 --- a/packages/compiler/src/ir/build.ts +++ b/packages/compiler/src/ir/build.ts @@ -58,10 +58,10 @@ export function dynUndefinedExpr(loc: SrcLoc): IrExpr { } /** An always-throwing Node error with the replaced expression's type. - * Kinds: 0 Error, 1 TypeError, 2 RangeError, 5 ReferenceError. An empty + * Kinds: 0 Error, 1 TypeError, 2 RangeError, 5 ReferenceError, 6 EvalError. An empty * code omits the error code property. */ export function nodeThrowExpr( - kind: 0 | 1 | 2 | 5, + kind: 0 | 1 | 2 | 5 | 6, code: string, message: string, type: IrType, diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 4d4193f73..d4e96a35a 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -75,6 +75,10 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "process.stdoutWriteBytes", "process.stderrWriteBytes", "procStream.write", + "child.stdioStream", + "stream.unboxDyn", + "stream.pipeDyn", + "stream.unpipeDyn", "weakMap.new", "weakSet.new", "dyn.fromEntries", @@ -133,6 +137,20 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "blob.new", "blob.fileNew", "blob.slice", + "wasm.compile", + "wasm.validate", + "wasm.instantiate", + "wasm.moduleNew", + "wasm.instanceNew", + "wasm.errorNew", + "wasm.memoryNew", + "wasm.tableNew", + "wasm.globalNew", + "wasm.withoutNew", + "wasm.moduleImports", + "wasm.moduleExports", + "wasm.moduleCustomSections", + "wasm.valueOf", "events.addAbortListener", "fetch.streamNew", "fetch.streamFrom", @@ -263,6 +281,14 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ // ("Invalid URL") or a non-http scheme (ERR_INVALID_PROTOCOL). "http.requestUrl", "http.requestUrlCb", + // The runtime options walk throws Node's validation errors and the + // fence for documented options it does not implement. + "http.requestDyn", + "http.requestDynCb", + "https.requestDyn", + "https.requestDynCb", + // address() of a Unix-socket server read as the AddressInfo record. + "net.serverAddressCheck", // The h2 client's throwing entries: connect on a bad/non-http // authority (ERR_INVALID_URL / ERR_INVALID_PROTOCOL), request on a // closed/destroyed session (ERR_HTTP2_INVALID_SESSION), setEncoding on @@ -324,6 +350,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "stream.onDyn", "readable.resume", "readable.pipe", + "readable.pipeGeneral", "readable.unpipe", "writable.write", "writable.writeStr", @@ -346,6 +373,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "process.kill", "process.killNum", "process.setExitCode", + "process.setExitCodeTracked", // Node's argument grammar throws ERR_INVALID_ARG_TYPE at the call. "process.emitWarning", // cpuUsage(prev)'s field validation: negative/non-finite prev fields @@ -366,6 +394,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "perf.entries", "perf.clearMarks", "perf.clearMeasures", + // monitorEventLoopDelay's resolution: Node's ERR_OUT_OF_RANGE. + "perf.eldNew", // Non-TTY stdin: Node's stdin has no setRawMode member, so the call is // the exact catchable TypeError. "process.stdinSetRawMode", @@ -402,6 +432,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "error.stack", "error.cause", "error.setCause", + "error.assign", "error.deleteCause", "dyn.objectTag", // Numeric coercion runs user valueOf/toString — throws propagate. @@ -448,6 +479,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "https.requestFn", "https.requestFnCb", "rl.question", + "rl.questionDyn", "rl.checkOptions", "rl.checkCompleter", "readable.fromArr", @@ -470,6 +502,11 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "rl.resume", "rl.prompt", "rl.promptWith", + "rl.setPromptDyn", + "rl.getPrompt", + "rl.cursorPos", + "rl.inputFromDyn", + "rl.outputFromDyn", "rl.write", "rl.writeKey", "rl.cursor", @@ -484,6 +521,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "json.stringifyReplacer", "json.stringifyValue", "date.nativeNew", + "date.nativeNewFields", + "date.nativeNewValue", "date.checkedValue", "util.parseArgs", "util.parseEnv", @@ -550,6 +589,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "dyn.objCreateWithProperties", "dyn.getPrototype", "dyn.setPrototype", + // a proxy in the prototype chain can throw + "dyn.builtinInstanceOf", + "dyn.functionInstanceOf", "dyn.getOwnPropertyNames", "dyn.ownKeys", "dyn.getOwnPropertySymbols", @@ -591,6 +633,9 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "dyn.mapSeedEntry", "dyn.freeze", "dyn.isFrozen", + "class.defineField", + "class.throwReadOnly", + "class.throwNotExtensible", "dyn.toString", "dyn.valueOf", "string.fromCodePoint", @@ -782,6 +827,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ // Node's argument checks (ERR_INVALID_ARG_TYPE/VALUE, ERR_SOCKET_BAD_PORT). "dns.lookupDyn", "dns.lookupService", + "dns.promisesLookup", + "dns.promisesLookupService", "dns.setDefaultResultOrder", "net.connect", "net.connectCb", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 697a185a5..cd3db784d 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -108,6 +108,45 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "blob.bytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } }, "blob.arrayBuffer": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, "blob.is": { argTypes: [DYN, F64], result: BOOL }, + /** WebAssembly (scr_wasm.c): modules translated at build time + * (wasm/translate.ts), found at run time by the SHA-256 of their bytes. + * compile and instantiate over bytes settle on a later loop turn; + * instantiate over a Module settles now, as in Node. `wasm.is` answers + * instanceof for Module, Instance, Memory, Table and Global (kinds 0-4) + * and CompileError, LinkError and RuntimeError (5-7); `wasm.errorNew` + * constructs one of the error classes (kind 5-7, message, options). */ + "wasm.compile": { argTypes: [DYN], result: { kind: "promise", inner: DYN } }, + "wasm.validate": { argTypes: [DYN], result: BOOL }, + "wasm.instantiate": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "wasm.moduleNew": { argTypes: [DYN], result: DYN }, + "wasm.instanceNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.is": { argTypes: [DYN, F64], result: BOOL }, + "wasm.errorNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** new WebAssembly.Memory(descriptor), Table(descriptor, value) and + * Global(descriptor, value) (an undefined value is the type's default); + * `wasm.withoutNew` throws the TypeError for Module, Instance, Memory, + * Table or Global (kinds 0-4) called without new, after its arguments + * evaluate. */ + "wasm.memoryNew": { argTypes: [DYN], result: DYN }, + "wasm.tableNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.globalNew": { argTypes: [DYN, DYN], result: DYN }, + "wasm.withoutNew": { argTypes: [F64, DYN, DYN], result: DYN }, + /** WebAssembly.Module.imports(module), exports(module) and + * customSections(module, name): checked-dynamic arrays of descriptor + * objects and ArrayBuffers. */ + "wasm.moduleImports": { argTypes: [DYN], result: DYN }, + "wasm.moduleExports": { argTypes: [DYN], result: DYN }, + "wasm.moduleCustomSections": { argTypes: [DYN, DYN], result: DYN }, + /** `v.valueOf()` on a WebAssembly-typed receiver: a Global's value, + * dyn.valueOf's answer for anything else. */ + "wasm.valueOf": { argTypes: [DYN], result: DYN }, + /** WebAssembly.compile (0), instantiate (1) and validate (2) as function + * values, and the namespace object holding them. */ + "wasm.function": { argTypes: [F64], result: DYN }, + "wasm.namespace": { argTypes: [], result: DYN }, + /** The global object viewed through a cast to a type with a WebAssembly + * member: an object holding the namespace. */ + "wasm.globalView": { argTypes: [], result: DYN }, "abort.timeout": { argTypes: [DYN], result: DYN }, "abort.now": { argTypes: [DYN], result: DYN }, "abort.any": { argTypes: [DYN], result: DYN }, @@ -206,12 +245,47 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.in": { argTypes: [DYN, STRING], result: BOOL }, "dyn.freeze": { argTypes: [DYN], result: DYN }, "dyn.isFrozen": { argTypes: [DYN], result: BOOL }, + /** Object.defineProperty on a declared class field (scr_json.c): the + * field's current value (borrowed), its FIELD_* attribute word, the + * instance's CLASS_* integrity word, the field name and the descriptor. + * Validates as Node does (TypeError "Cannot redefine property: f") and + * answers a one-property holder of the defined value and attributes (+1), + * read back with class.definedBits and class.definedValue. */ + "class.defineField": { argTypes: [DYN, F64, F64, STRING, DYN], result: DYN }, + "class.definedBits": { argTypes: [DYN, STRING], result: F64 }, + "class.definedValue": { argTypes: [DYN, STRING], result: DYN }, + /** Whether a property descriptor has a `value` member. */ + "class.descriptorHasValue": { argTypes: [DYN], result: BOOL }, + /** Throws Node's TypeError for a write to a read-only class field: the + * field name and the instance's class name. */ + "class.throwReadOnly": { argTypes: [STRING, STRING], result: VOID }, + /** Throws Node's TypeError for a class field initializer on a + * non-extensible instance: the field name. */ + "class.throwNotExtensible": { argTypes: [STRING], result: VOID }, "dyn.nativeSetNew": { argTypes: [DYN], result: DYN }, "dyn.nativeSetIs": { argTypes: [DYN], result: BOOL }, "dyn.nativeMapIs": { argTypes: [DYN], result: BOOL }, "dyn.nativeUrlIs": { argTypes: [DYN], result: BOOL }, "dyn.nativeDateIs": { argTypes: [DYN], result: BOOL }, + /** A builtin constructor (`Object`, `Date`, `RegExp`, `Map`, `Set`, + * `Promise`, `WeakMap`, `WeakSet`) read as a JavaScript value: one + * interned function per name, the same one dynamic values answer for + * `.constructor`. The name is a compiler-chosen literal. */ + "dyn.globalConstructor": { argTypes: [STRING], result: DYN }, + /** A builtin global function (setTimeout, setInterval, setImmediate, + * the clear functions, queueMicrotask, process.nextTick) read as a + * JavaScript value: one interned function per name whose calls run the + * timer APIs (it pulls in the event loop). The name is a compiler-chosen + * literal. */ + "dyn.builtinFunction": { argTypes: [STRING], result: DYN }, "date.nativeNew": { argTypes: [DYN], result: DYN }, + /** `new Date(year, monthIndex, ...)` in JavaScript: the argument array's + * first seven values pass through ToNumber in order (later ones are + * evaluated, never converted), then the local-time field constructor. */ + "date.nativeNewFields": { argTypes: [DYN], result: DYN }, + /** `new Date(value)` in JavaScript: a string argument parses with V8's + * full date parser; every other value constructs as date.nativeNew. */ + "date.nativeNewValue": { argTypes: [DYN], result: DYN }, "date.checkedValue": { argTypes: [DYN], result: DATE_T }, "dyn.nativeRegexIs": { argTypes: [DYN], result: BOOL }, /** toString() on a checked-dynamic receiver: runtime kind dispatch @@ -239,6 +313,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.promiseAll": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, "dyn.promiseAllSettled": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, "dyn.promiseAny": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + /** PromiseResolve over one checked-dynamic value: the promise it holds, + * a promise that adopts a thenable's settlement, or a promise fulfilled + * with the value. The second argument reads `then` as above. */ + "dyn.promiseResolve": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, /** Bare `typeof v` on a dyn value AS A STRING (arg: the dyn value, * borrowed; result: an owned string) — the dyn kind's JS answer: * undefined→"undefined", null/object/array/bytes→"object" (JS's oldest @@ -362,6 +440,20 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "perf.entryDetail": { argTypes: [F64], result: DYN }, "perf.entryKeep": { argTypes: [DYN], result: VOID }, "perf.entryRef": { argTypes: [F64], result: DYN }, + /** perf_hooks event-loop metrics (scr_perf_loop.c): loopStart is the + * loop's start in performance.now() ms (0 before it), idleTime the idle + * ms since. eldNew(resolution ms) creates a monitorEventLoopDelay + * histogram handle (MAY THROW Node's resolution RangeError); enable/disable answer whether the state changed; + * eldPercentile expects Node's validated range; eldStat(which) reads + * 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ + "perf.loopStart": { argTypes: [], result: F64 }, + "perf.idleTime": { argTypes: [], result: F64 }, + "perf.eldNew": { argTypes: [F64], result: F64 }, + "perf.eldEnable": { argTypes: [F64], result: BOOL }, + "perf.eldDisable": { argTypes: [F64], result: BOOL }, + "perf.eldReset": { argTypes: [F64], result: VOID }, + "perf.eldPercentile": { argTypes: [F64, F64], result: F64 }, + "perf.eldStat": { argTypes: [F64, F64], result: F64 }, /** process.memoryUsage(): memoryUsageTake samples the resident set and * the runtime heap once (MAY THROW Node's uv_resident_set_memory error) * and memoryUsageField(k) reads that sample (0 rss, 1 heapTotal, 2 @@ -1241,6 +1333,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "net.createServer": { argTypes: [], result: NETSERVER_T }, "net.createServerCb": { argTypes: [null], result: NETSERVER_T }, "net.listen": { argTypes: [NETSERVER_T, F64], result: VOID }, + /** listen(path[, cb]): a Unix-socket server; the socket file is removed + * when the server closes. Failures are the async 'error'. */ + "net.listenPath": { argTypes: [NETSERVER_T, STRING], result: VOID }, + "net.listenPathCb": { + argTypes: [NETSERVER_T, STRING, { kind: "func", params: [], ret: VOID }], + result: VOID, + }, "net.listenCb": { argTypes: [NETSERVER_T, F64, { kind: "func", params: [], ret: VOID }], result: VOID, @@ -1283,6 +1382,15 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * answers null (the serverPort stance). */ "net.serverAddress": { argTypes: [NETSERVER_T], result: VOID }, "net.serverClose": { argTypes: [NETSERVER_T], result: VOID }, + /** server.close((err) => ...): the callback (a dyn function) gets + * undefined once the server closes, or Node's ERR_SERVER_NOT_RUNNING + * when it was not listening. */ + "net.serverCloseDyn": { argTypes: [NETSERVER_T, DYN], result: VOID }, + /** server.address() in programs that may listen on a Unix socket: the + * path a listening server answers ("" for a TCP server), and the check + * a record-typed address() makes first (throws for a Unix server). */ + "net.serverUnixPath": { argTypes: [NETSERVER_T], result: STRING }, + "net.serverAddressCheck": { argTypes: [NETSERVER_T], result: VOID }, "net.serverCloseCb": { argTypes: [NETSERVER_T, { kind: "func", params: [], ret: VOID }], result: VOID, @@ -1446,6 +1554,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * callback) and lookupService(address, port, callback). */ "dns.lookupDyn": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, "dns.lookupService": { argTypes: [DYN, DYN, DYN, BOOL], result: VOID }, + /** node:dns/promises: lookup(hostname, options) and lookupService(address, + * port) over the callback forms; the promise fulfills with the dyn + * { address, family } (array for all: true) / { hostname, service }. + * Argument errors throw synchronously (may-throw), unlike the + * util.promisify forms below. */ + "dns.promisesLookup": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, + "dns.promisesLookupService": { argTypes: [DYN, DYN], result: { kind: "promise", inner: DYN } }, "dns.getServers": { argTypes: [], result: DYN }, "dns.getDefaultResultOrder": { argTypes: [], result: STRING }, "dns.setDefaultResultOrder": { argTypes: [DYN], result: VOID }, @@ -1865,6 +1980,17 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * non-http scheme. */ "http.requestUrl": { argTypes: [STRING, STRING, BOOL], result: HTTPCLIENTREQ_T }, "http.requestUrlCb": { argTypes: [STRING, STRING, BOOL, null], result: HTTPCLIENTREQ_T }, + /** request/get with a runtime options record: a RequestOptions + * variable, or a literal that spreads one or sets socketPath. Args + * (options, autoEnd[, cb]). The runtime reads the record in the + * ClientRequest constructor's order (host/port or socketPath, path, + * method, headers, timeout, agent, signal, maxHeaderSize) with Node's + * errors, and documented options it does not implement throw the + * runtime fence. The "https." pair adds the TLS options. MAY THROW. */ + "http.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, + "http.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, + "https.requestDyn": { argTypes: [DYN, BOOL], result: HTTPCLIENTREQ_T }, + "https.requestDynCb": { argTypes: [DYN, BOOL, null], result: HTTPCLIENTREQ_T }, /** The paused-mode demux surface (portless's first-byte TLS peek: * once('readable') + read(1) + unshift + emit('connection')). * sockOnReadable registers a zero-param listener (a consumer: arrived @@ -1910,6 +2036,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** Listeners with a parameter, as dyn functions: close(hadError), * write's cb(err) and end's cb. */ "net.sockOnCloseDyn": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, + /** socket.on/once('lookup', listener): the listener (a dyn function) + * gets (err, address, family, host) once per resolved address, or once + * with the lookup error, before the connect. */ + "net.sockOnLookup": { argTypes: [NETSOCKET_T, DYN, BOOL], result: VOID }, "net.sockOnWriteFlush": { argTypes: [NETSOCKET_T, { kind: "func", params: [], ret: VOID }], result: VOID, @@ -2391,6 +2521,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** Rest listeners use the checked function bridge to pack event arguments. */ "child.onDyn": { argTypes: [CHILD_T, STRING, DYN], result: VOID }, "process.connected": { argTypes: [], result: BOOL }, + /** Whether this fork child opened its channel; part of process.send's + * presence in programs with fork targets. Never throws. */ + "process.ipcOpen": { argTypes: [], result: BOOL }, "process.send": { argTypes: [STRING], result: BOOL }, "process.sendCb": { argTypes: [STRING, null], result: BOOL }, "process.disconnect": { argTypes: [], result: VOID }, @@ -2514,8 +2647,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * queue, end/destroy settle it, writable is a pure state read, and * listener callbacks move into the writer registry. */ "child.stdin": { argTypes: [CHILD_T], result: VOID }, + /** Under the general stream representation (general-streams.ts), a + * stdio read (slot 0, 1 or 2) answers the child's bridged runtime stream + * — `Writable | null` for stdin, `Readable | null` otherwise; the bridge + * throws when the program lacks the stream unit's boxing helpers. */ + "child.stdioStream": { argTypes: [CHILD_T, F64], result: VOID }, // Listener registrations: callback func shapes are program-dependent - // (zero-param / Buffer / Buffer-armed union) — the slots pin arity, + // (zero-param / Buffer / string / unknown / Buffer-armed union) — the slots pin arity, // the stream receiver, and the once-flag. "stream.onData": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, "stream.onDataStr": { argTypes: [CHILDSTREAM_T, null, BOOL], result: VOID }, @@ -2535,6 +2673,39 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * stderrWrite paths so buffering and ordering stay identical. Data * borrowed; the bool is Node's always-true backpressure signal. */ "procStream.write": { argTypes: [PROCSTREAM_T, STRING], result: BOOL }, + /** process.stdout/stderr flowing into a runtime Writable slot under the + * general stream representation: an fd-backed Writable (+1) whose + * writes take process.stdout.write's synchronous path. */ + /** A pipeline() stage that is a checked-dynamic value: a boxed native + * stream unwraps by identity (+1); anything else throws the given + * not-supported message. Typed as a Duplex so every stage position + * accepts it; the runtime pipeline checks the stream's real sides. */ + "stream.unboxDyn": { + argTypes: [DYN, STRING], + result: { kind: "object", className: "%Duplex" }, + }, + /** Checked-dynamic stream slots (dyn-streams.ts): the native stream a + * checked-dynamic value boxes (+1) — a runtime stream with the readable + * (1) or writable (2) side — or null for any other value. Never throws; + * the result union is `%Readable | null` or `%Writable | null`. */ + "stream.dynNative": { argTypes: [DYN, F64], result: VOID }, + /** stream.dynNative for a pipe or unpipe destination: also a + * constructor-function stream's runtime stream, in programs that link + * that unit, unless it overrides write() or end(). */ + "stream.dynNativePipe": { argTypes: [DYN, F64], result: VOID }, + /** readable.pipe(destination, end) into a checked-dynamic destination + * that is not a native stream: [source, the source boxed, destination, + * end]. Node's pipe listener protocol over the destination's members + * (scr_stream_dyn.c); throws what those members throw. */ + "stream.pipeDyn": { argTypes: [null, DYN, DYN, BOOL], result: VOID }, + /** readable.unpipe(destination) for a checked-dynamic destination that + * is not a native stream: [source, destination]. Unpipes the pipe + * stream.pipeDyn registered into it, if any (scr_stream_dyn.c). */ + "stream.unpipeDyn": { argTypes: [null, DYN], result: VOID }, + "stream.stdioWritable": { + argTypes: [PROCSTREAM_T], + result: { kind: "object", className: "%Writable" }, + }, "child.killed": { argTypes: [CHILD_T], result: BOOL }, "child.kill": { argTypes: [CHILD_T, STRING], result: BOOL }, "child.killNum": { argTypes: [CHILD_T, F64], result: BOOL }, @@ -3025,8 +3196,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "process.exit": { argTypes: [F64], result: VOID }, /** Numeric process.exitCode write (integer validation, implicit exit status). */ "process.setExitCode": { argTypes: [F64], result: VOID }, + /** The process.exitCode write of a program that also reads the + * property: the same validation, and the code counts as assigned. */ + "process.setExitCodeTracked": { argTypes: [F64], result: VOID }, /** The code for process.exit() with no argument, or zero when unset. */ "process.currentExitCode": { argTypes: [], result: F64 }, + /** Whether process.exitCode reads a code rather than undefined. Never throws. */ + "process.hasExitCode": { argTypes: [], result: BOOL }, "process.cwd": { argTypes: [], result: STRING }, /** getpid(2) / getuid(2): zero args → f64. POSIX-only target, so both * always answer (the checker's `getuid?` optionality covers Windows — @@ -3118,6 +3294,23 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "error.hasCause": { argTypes: [null], result: BOOL }, /** Assignment borrows both operands and retains the new cause. */ "error.setCause": { argTypes: [null, DYN], result: VOID }, + /** `err.code = s` on an error-hierarchy receiver: the code slot that + * error.code reads and coded throws stamp. Borrows both; never throws. */ + "error.setCode": { argTypes: [null, STRING], result: VOID }, + /** `err.code = v` with a value that is not statically a string: strings + * take the code slot, anything else clears it and sets the dynamic own + * property. Borrows both; never throws. */ + "error.setCodeDyn": { argTypes: [null, DYN], result: VOID }, + /** `Object.assign(err, ...sources)` onto an error-hierarchy receiver + * (args: the error, a dyn array of the sources): the own-member copy + * onto its checked-dynamic node, then the typed slots (name, message, + * stack, cause, code) re-read from it. Throws what the copy throws. */ + "error.assign": { argTypes: [null, DYN], result: VOID }, + /** `key in err` for a key no compiled class of the receiver declares + * (args: the error, the key): a runtime-set own property (Object.assign, + * throw-site fields, dynamic writes) or an Object.prototype member. */ + "error.hasKey": { argTypes: [null, STRING], result: BOOL }, + "error.hasCode": { argTypes: [null], result: BOOL }, "error.defineCause": { argTypes: [null, DYN], result: VOID }, "error.deleteCause": { argTypes: [null], result: VOID }, /** The compiler-resolved Node-parity throw for always-throwing lowered @@ -3231,6 +3424,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** SameValue over checked values, including NaN, signed zero and reference identity. */ "dyn.sameValue": { argTypes: [DYN, DYN], result: BOOL }, "error.stack": { argTypes: [null], result: STRING }, + "error.setStack": { argTypes: [null, STRING], result: VOID }, "error.stackLimitGet": { argTypes: [], result: F64 }, "error.stackLimitSet": { argTypes: [F64], result: VOID }, // Receiver (any error-hierarchy object) and the program-dependent @@ -3409,6 +3603,20 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "dyn.classPrototype": { argTypes: [DYN, DYN], result: DYN }, "dyn.classBasePrototype": { argTypes: [DYN], result: DYN }, "dyn.classInherit": { argTypes: [DYN, DYN], result: DYN }, + /** EventEmitter (0), Stream (1), Readable (2), Writable (3), Duplex (4), + * Transform (5) and PassThrough (6) as untyped constructor values, one + * per thread (scr_dyn_builtin_class.c). */ + "dyn.builtinClass": { argTypes: [F64], result: DYN }, + /** The stream classes (2-6 above) with their stream half: constructor + * functions over them get native stream state + * (scr_dyn_builtin_stream.c). */ + "dyn.builtinStreamClass": { argTypes: [F64], result: DYN }, + /** `value instanceof C` for those constructors, over the value's + * prototype chain. */ + "dyn.builtinInstanceOf": { argTypes: [DYN, F64], result: BOOL }, + /** `value instanceof f` for an untyped constructor function: the + * prototype chain against f.prototype. */ + "dyn.functionInstanceOf": { argTypes: [DYN, DYN], result: BOOL }, "dyn.classSuper": { argTypes: [DYN, DYN, DYN], result: VOID }, "dyn.assignPrototype": { argTypes: [DYN, DYN, DYN], result: DYN }, "dyn.definePrototypeProps": { argTypes: [DYN, DYN, DYN], result: DYN }, @@ -3499,6 +3707,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * throws err when unhandled. count/countFn/names/listeners/getMax/ * setMax/setDefaultMax/getDefaultMax are the introspection surface. */ "emitter.new": { argTypes: [], result: VOID }, + /** `x.constructor.name` of an emitter or stream (a borrowed object of + * EventEmitter's hierarchy): the +1 class name the instance records. */ + "emitter.className": { argTypes: [null], result: STRING }, "emitter.ctor": { argTypes: [null], result: VOID }, "emitter.on": { argTypes: [null, STRING, null, BOOL, BOOL], result: VOID }, "emitter.off": { argTypes: [null, STRING, null], result: VOID }, @@ -3563,7 +3774,9 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * returns the below-hwm answer. readable.read: (recv, size — -1 for * absent) → Buffer|null union. pause/resume return recv +1 (`this`); * isPaused answers the flag. readable.pipe: (recv, dst, end) → dst +1, - * fires 'pipe' on dst; readable.unpipe (recv[, dst]) → recv +1. + * fires 'pipe' on dst; readable.pipeGeneral: the same, with `end` off + * when dst is the converted process.stdout / process.stderr; + * readable.unpipe (recv[, dst]) → recv +1. * writable.write/writeStr: (recv, chunk[, cb]) → below-hwm bool (cb * MOVES when present, called after the user write completes). * writable.end: (recv, flags[, chunk][, cb]) → recv +1. cork/uncork are @@ -3761,6 +3974,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "readable.resume": { argTypes: [null], result: VOID }, "readable.isPaused": { argTypes: [null], result: BOOL }, "readable.pipe": { argTypes: [null, null, BOOL], result: VOID }, + "readable.pipeGeneral": { argTypes: [null, null, BOOL], result: VOID }, "readable.unpipe": { argTypes: [null], result: VOID }, "readable.flowing": { argTypes: [null], result: VOID }, "writable.write": { argTypes: [null, BYTES_U8], result: BOOL }, @@ -4016,6 +4230,14 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ argTypes: [F64, STRING, DYN], result: { kind: "promise", inner: STRING }, }, + /** The question forms with a checked-dynamic query (a JavaScript value + * that need not be a string). */ + "rl.questionDyn": { argTypes: [F64, DYN, null], result: VOID }, + "rl.questionAsyncDyn": { argTypes: [F64, DYN], result: { kind: "promise", inner: STRING } }, + "rl.questionAsyncSignalDyn": { + argTypes: [F64, DYN, DYN], + result: { kind: "promise", inner: STRING }, + }, "rl.close": { argTypes: [F64], result: VOID }, "rl.pause": { argTypes: [F64], result: VOID }, "rl.resume": { argTypes: [F64], result: VOID }, @@ -4023,6 +4245,7 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ /** prompt(preserveCursor): [handle, preserveCursor] */ "rl.promptWith": { argTypes: [F64, BOOL], result: VOID }, "rl.setPrompt": { argTypes: [F64, STRING], result: VOID }, + "rl.setPromptDyn": { argTypes: [F64, DYN], result: VOID }, "rl.getPrompt": { argTypes: [F64], result: STRING }, "rl.write": { argTypes: [F64, STRING], result: VOID }, /** write(data, key): [handle, hasData, data, hasKey, hasName, name, @@ -4055,6 +4278,14 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "rl.terminal": { argTypes: [F64, F64, BOOL, DYN, BOOL], result: VOID }, "rl.line": { argTypes: [F64], result: STRING }, "rl.isTerminal": { argTypes: [F64], result: BOOL }, + "rl.isPromises": { argTypes: [F64], result: BOOL }, + /** createInterface's input or output as a checked-dynamic value: + * [value, output] — the stream; refused when it is not one. */ + "rl.inputFromDyn": { argTypes: [DYN, BOOL], result: { kind: "object", className: "%Readable" } }, + "rl.outputFromDyn": { + argTypes: [DYN, BOOL], + result: { kind: "object", className: "%Writable" }, + }, "rl.cursor": { argTypes: [F64], result: F64 }, /** readline.cursorTo / moveCursor / clearLine / clearScreenDown: rl.seq * [kind (0-3), the first and second numbers, whether the second is @@ -4098,6 +4329,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * errorBoxer, listenForError] → a pending promise of any[]; the boxers * turn the event's and the 'error' tuple into that array. */ "events.once": { argTypes: [null, STRING, null, null, BOOL], result: VOID }, + /** events.once(emitter, name, { signal }) (scr_events_once_signal.c): + * events.once's arguments plus the checked-dynamic signal (an + * AbortSignal or undefined). */ + "events.onceSignal": { argTypes: [null, STRING, null, null, BOOL, DYN], result: VOID }, /** The resolve-value forms: [ms?, value, ref] → a pending promise of the * value's type that copies the borrowed already-resolved `value` promise * when the timer (or immediate) fires; ref false unrefs it. The result @@ -4293,13 +4528,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ "date.toDateString": { argTypes: [DATE_T], result: STRING }, "date.toTimeString": { argTypes: [DATE_T], result: STRING }, /** The composed `new Date(dateString).getTime()` read: one borrowed - * string, f64 milliseconds since epoch. The parsed grammar is BOUNDED - * (documented divergence — V8's parser accepts far more): the ASN.1 - * validity shape X509Certificate.validFrom/validTo answer ("Jul 1 - * 00:00:00 2026 GMT" — the portless cert-expiry read), and ECMA's own - * date-time string format (YYYY-MM-DD[THH:mm[:ss[.sss]]][Z|±HH:MM] — - * date-only forms are UTC, exactly the spec). Anything else is NaN, - * Node's invalid-date getTime. Never throws. */ + * string, f64 milliseconds since epoch, with V8's date-string grammar + * (the ES5 format, then the legacy forms: RFC 1123/2822, Date#toString, + * "Jan 2, 2024", ...). Zone-less dates are local time; anything V8 + * rejects is NaN, Node's invalid-date getTime. Never throws. */ "date.parseGetTime": { argTypes: [STRING], result: F64 }, /** `Date.UTC(...)` — seven f64 arguments (the frontend completes the * spec's defaults for omitted trailing parts: month 0, date 1, time @@ -4307,6 +4539,12 @@ export const LIB_FN_SIGS = defineLibFnSignatures({ * map to 1900+year, out-of-range months/dates roll over, non-finite * parts and out-of-range results answer NaN. Never throws. */ "date.utc": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: F64 }, + /** `new Date(year, monthIndex[, date, hours, minutes, seconds, ms])` — + * the same seven-f64 completion as date.utc, but the fields are LOCAL + * time: V8's MakeDay/MakeTime, then UTC(t) in the host zone (DST gaps + * read with the offset before the transition, overlaps answer the + * earlier instant), then TimeClip. Never throws. */ + "date.newLocal": { argTypes: [F64, F64, F64, F64, F64, F64, F64], result: DATE_T }, /** WHATWG TextDecoder.decode over u8 bytes (scr_bytes.c): utf-8 with * default options — the same maximal-subpart replacement decode as * Buffer.toString("utf8"), with the leading BOM stripped (the one diff --git a/packages/compiler/src/ir/capture-values.ts b/packages/compiler/src/ir/capture-values.ts index d7a1516bb..779ca1b76 100644 --- a/packages/compiler/src/ir/capture-values.ts +++ b/packages/compiler/src/ir/capture-values.ts @@ -238,7 +238,7 @@ interface FunctionFacts { hoisted: Map; /** Bindings called through `callValue` without a receiver. */ callees: Set; - /** Temporal-dead-zone bindings: the statement list holding both the + /** TDZ (temporal dead zone) bindings: the statement list holding both the * empty `varDecl` and the initializing `assign`, with their indexes. */ tdzInits: Map; classCaptured: Set; diff --git a/packages/compiler/src/ir/function-references.test.ts b/packages/compiler/src/ir/function-references.test.ts index c2d461e09..4ecc60a6f 100644 --- a/packages/compiler/src/ir/function-references.test.ts +++ b/packages/compiler/src/ir/function-references.test.ts @@ -154,6 +154,7 @@ const MODULE_FIELDS: Record>, Role> = { "classes[].jsName": "other", "classes[].genericOf": "other", "classes[].baseValueGlobal": "other", + "classes[].errorNameAfter": "other", "classes[].prototypeDataHelper": "reference", "classes[].instancePrototypeHelper": "reference", "classes[].localCaptures[].name": "other", @@ -170,6 +171,7 @@ const MODULE_FIELDS: Record>, Role> = { "records[].id": "other", "records[].fields[].name": "other", "records[].declaredOrder[]": "other", + "records[].accessorOrder[]": "other", "unions[].id": "other", "unions[].discriminant.field": "other", "unions[].discriminant.cases[].values[]": "other", @@ -210,6 +212,8 @@ const MODULE_FIELDS: Record>, Role> = { "contracts.acyclic[]": "other", "contracts.immortalGlobals[]": "other", "contracts.programLifetime[]": "other", + "wasmModules[].sha256": "other", + "wasmModules[].bytes": "other", "threadSlotKeys[]": "other", }; diff --git a/packages/compiler/src/ir/int32-slots.ts b/packages/compiler/src/ir/int32-slots.ts index fe0467fac..a08693013 100644 --- a/packages/compiler/src/ir/int32-slots.ts +++ b/packages/compiler/src/ir/int32-slots.ts @@ -347,6 +347,8 @@ const DYN_READ_ONLY = new Set([ "arrayBuffer.viewU8", "arrayBuffer.viewU8C", "date.nativeNew", + "date.nativeNewFields", + "date.nativeNewValue", // The dynamic operators run the ToPrimitive and coercion helpers of // dyn.compare, dyn.numberConstructor and dyn.toStringCoerce, then build a // fresh number, string or bigint. dyn.increment steps dyn.toNumeric's result. diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index b84c0594e..4c5bbd183 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1044,6 +1044,24 @@ export interface IrModule { * write in the program (ir/contract-check.ts). Backends may rely on them * unconditionally. Absent when the program writes no contract. */ contracts?: IrContracts; + /** The WebAssembly modules translated into this program (wasm/ + * translate.ts), present exactly when the program uses the WebAssembly + * API (possibly empty). The backend appends each module's translation + * and emits the registry the runtime looks modules up in. */ + wasmModules?: IrWasmModule[]; + /** The program calls WebAssembly.Module.customSections, so translated + * modules keep their custom sections (otherwise they carry none, and + * debug sections cost nothing). */ + wasmCustomSections?: boolean; +} + +/** A WebAssembly module whose bytes the build saw, validated within the + * translatable feature level. */ +export interface IrWasmModule { + /** Hex SHA-256 of the bytes. */ + sha256: string; + /** The bytes, base64. */ + bytes: string; } /** Verified contract facts, keyed by IR names. */ @@ -1406,6 +1424,11 @@ export interface IrClassDef { * bag. Its descriptors preserve presence and insertion order independently * of native layout order; field values remain in their native slots. */ tracksOwnFields?: boolean; + /** Error subclasses: the field an instance's own `name` follows among its + * own properties, or null when it precedes every field. Absent when no + * class of the chain sets `name` during construction, and then an own + * `name` follows every other own property. */ + errorNameAfter?: string | null; /** Stable module symbol identities for public instance layout fields. */ symbolFields?: { field: string; globalId: string }[]; /** RUNTIME-PROVIDED class (the builtin Error hierarchy): the struct, RC @@ -1621,6 +1644,10 @@ export interface IrRecordShape { * fields carry that declaredOrder OMITS are internal '%'-fields (Dirent's * %dtype) — hidden from every key-order surface, JSON included. */ declaredOrder?: string[]; + /** Shapes with accessor slots: every own key in declaration order, with + * an accessor property named once (see accessorSlotProp). The + * checked-dynamic copy lists its keys in this order. */ + accessorOrder?: string[]; } /** fs.Dirent rows: the hidden %dtype entry kind (libuv's UV_DIRENT @@ -1932,8 +1959,23 @@ export type IrStmt = | { kind: "return"; value: IrExpr | null; awaited?: true; loc: SrcLoc } /** Field write `obj.f = v` — statement-only, like `assign`/`arraySet`. * Evaluation order: obj, then value. The old value is released; ownership - * of a refcounted new value MOVES into the object. */ - | { kind: "fieldSet"; obj: IrExpr; className: string; field: string; value: IrExpr; loc: SrcLoc } + * of a refcounted new value MOVES into the object. `sloppy` marks a write + * in sloppy-mode code, which skips a read-only field instead of throwing. + * `define` marks a class field initializer, which defines the property + * (a non-extensible instance rejects it). `attributesChecked` marks a + * write that already honors the instance's property attributes + * (CLASS_ATTRIBUTES). */ + | { + kind: "fieldSet"; + obj: IrExpr; + className: string; + field: string; + value: IrExpr; + sloppy?: true; + define?: true; + attributesChecked?: true; + loc: SrcLoc; + } /** Record field write `r.f = v` — mirrors `fieldSet` exactly (evaluation * order obj then value; old value released; refcounted new value moved * in), with a shape id in place of a class name. */ @@ -3331,9 +3373,10 @@ export type IrExpr = /** Keyed read on a dyn value — `pkg.name` / `pkg["k"]` / the * `pkg?.scripts` chain step on a JSON.parse result. `key` is * string-typed (a strLit for the dot form); `type` is always dyn. An - * OBJ receiver answers the member (+1) or the undefined singleton (the - * own-property answer — prototype members like `toString` answer - * undefined, SEMANTICS.md); ARR answers `length` and canonical + * OBJ receiver answers the member (+1), an inherited Object.prototype + * member (the Object constructor and the interned valueOf/toString/ + * hasOwnProperty/propertyIsEnumerable values), or the undefined singleton; + * ARR answers `length` and canonical * in-range indices, STR answers `length` (UTF-16-exact), both * undefined otherwise; NUM/BOOL/BYTES answer undefined. An * undefined/null receiver THROWS the catchable Node-shaped TypeError @@ -3891,6 +3934,9 @@ function isJsonSafeAt( const shape = getRecord(t.shapeId); if (!shape) return false; if (recordTextCodecClass(shape) !== null) return false; + // Accessor slots are not function-valued properties: Node's + // JSON.stringify invokes the getter (see accessorSlotProp). + if (shapeHasAccessorSlots(shape)) return false; // The recursive knot: answer true and let the rest of the graph // decide (any unsafe constituent ends the traversal immediately). if (visiting.has(t.shapeId)) return true; @@ -4075,6 +4121,27 @@ export function isClassOwnEnumerableFieldName(name: string): boolean { /** Compiler-owned storage for properties added through untyped references. */ export const DYN_CLASS_PROPERTIES = "%dynProperties"; +/** Compiler-owned integrity state of a class instance whose hierarchy is + * passed to Object.freeze, Object.seal, Object.preventExtensions or + * Object.defineProperty: an f64 holding CLASS_NOT_EXTENSIBLE, CLASS_SEALED + * and CLASS_FROZEN bits. Other hierarchies have no such field. */ +export const CLASS_ATTRIBUTES = "%attributes"; +export const CLASS_NOT_EXTENSIBLE = 1; +export const CLASS_SEALED = 2; +export const CLASS_FROZEN = 4; + +/** Compiler-owned attributes of one declared field that Object.defineProperty + * changes: an f64 holding FIELD_NOT_WRITABLE, FIELD_NOT_ENUMERABLE and + * FIELD_NOT_CONFIGURABLE bits (zero is an ordinary data property). */ +export const CLASS_FIELD_ATTRIBUTES_PREFIX = "%attr:"; +export const FIELD_NOT_WRITABLE = 1; +export const FIELD_NOT_ENUMERABLE = 2; +export const FIELD_NOT_CONFIGURABLE = 4; + +export function classFieldAttributesName(field: string): string { + return CLASS_FIELD_ATTRIBUTES_PREFIX + field; +} + /** A class capsule can always preserve its exact native identity. Its * optional property view additionally needs converters in both directions; * fields such as Maps may remain opaque without preventing the round trip. */ diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 9bab3e79f..fd7c98cf6 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -9,59 +9,6 @@ import { } from "./ir.js"; import { everyModuleNode } from "./traverse.js"; -export interface RuntimeFeatures { - regex: boolean; - copying: boolean; - legacyTextDecoder: boolean; - fileHandle: boolean; - fetch: boolean; - eventTarget: boolean; - processEvents: boolean; - /** process.emitWarning is assigned (process.setEmitWarning). Declared - * in the literal's order: JSON.stringify of the features record must - * match between the Node-run and self-hosted compilers. */ - warningReplace: boolean; - /** stdin setEncoding / string data listeners / stdio 'resize' events. */ - stdioExtended: boolean; - emitter: boolean; - stream: boolean; - zlib: boolean; - dc: boolean; - assert: boolean; - dynInvoke: boolean; - dynAsync: boolean; - /** The program has dynamic-tier jsval operations (js.* builtins, the - * jsval pass): links the scr_js_* units (two-tier design I3). */ - dynTier: boolean; - /** The program enters AsyncLocalStorage stores (store.run, enterWith, - * a diagnostics_channel store binding) or reads the test runner's - * context (getTestContext): its timer, immediate, tick and - * queueMicrotask scheduling goes through the runtime's *_als entry - * points, which carry the active stores into each callback. */ - asyncContext: boolean; - inspect: boolean; - childProcess: boolean; - net: boolean; - symbol: boolean; - bigint: boolean; - searchParams: boolean; - qs: boolean; - parseArgs: boolean; - fsWatch: boolean; - nodeTest: boolean; - /** The program calls mock.timers members: its timer calls go through - * scr_test.c's mock-aware entry points. */ - mockTimers: boolean; - dgram: boolean; - http: boolean; - http2: boolean; - tls: boolean; - tlsCa: boolean; - cipher: boolean; - /** crypto.subtle importKey/sign/verify/digest: scr_webcrypto.c. */ - webcrypto: boolean; -} - const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ // DEP0040 rides the dyn-async unit's warning machinery. "punycode.warn", @@ -74,6 +21,7 @@ const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ "dyn.promiseAll", "dyn.promiseAllSettled", "dyn.promiseAny", + "dyn.promiseResolve", "process.onUncaughtException", "process.offUncaughtException", "process.onUnhandledRejection", @@ -182,7 +130,12 @@ const LEGACY_TEXT_DECODER_LIB_FNS: ReadonlySet = new Set([ /** Signal-taking readline, stream and timers/promises entry points, which register abort listeners. */ const ABORT_SIGNAL_LIB_FNS: ReadonlySet = new Set([ + "events.onceSignal", "http.clientSignal", + "http.requestDyn", + "http.requestDynCb", + "https.requestDyn", + "https.requestDynCb", "rl.createSignal", "rl.questionAsyncSignal", "sp.finishedOptsSignal", @@ -197,6 +150,7 @@ const ABORT_SIGNAL_LIB_FNS: ReadonlySet = new Set([ /** Entry points that link the stream unit (with STREAM_LIB_PREFIXES). */ const STREAM_LIB_FNS: ReadonlySet = new Set([ + "child.stdioStream", "console.bind", "crypto.hashStream", "crypto.hashStreamPiped", @@ -204,6 +158,8 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "fileHandle.createWriteStream", "fs.createReadStream", "fs.createWriteStream", + "rl.inputFromDyn", + "rl.outputFromDyn", "sp.finished", "sp.pipeline", "sp.pipelineStages", @@ -213,6 +169,12 @@ const STREAM_LIB_FNS: ReadonlySet = new Set([ "stream.destroyErr", "stream.errored", "stream.prop", + "stream.dynNative", + "stream.dynNativePipe", + "stream.pipeDyn", + "stream.unpipeDyn", + "stream.stdioWritable", + "stream.unboxDyn", "stream.workerStdio", ]); @@ -247,14 +209,30 @@ const READABLE_DYN_ASYNC_PREFIXES: readonly string[] = ["readable.fromGen", "rea /** Entry points that link the dyn-invoke unit. */ const DYN_INVOKE_LIB_FNS: ReadonlySet = new Set([ + "stream.dynNative", + "stream.dynNativePipe", + "stream.pipeDyn", + "stream.unpipeDyn", "dyn.arrayProtoCall", "dyn.arrayPrototype", + "dyn.builtinFunction", "dyn.builtinMethod", "dyn.defineProperty", "dyn.defineProps", "dyn.definePrototypeProps", "dyn.functionApply", + "dyn.globalConstructor", "dyn.objCreateWithProperties", + "dyn.objectTag", +]); + +/** Member names whose constant-key dynamic reads answer the inherited Object.prototype member through the dyn-invoke unit. Mirrors expr-dynamic.ts INHERITED_OBJECT_MEMBERS (the IR layer does not import the backend). */ +const INHERITED_MEMBER_NAMES: ReadonlySet = new Set([ + "constructor", + "hasOwnProperty", + "propertyIsEnumerable", + "toString", + "valueOf", ]); /** Entry points that link the child-process unit (with CHILD_PROCESS_LIB_PREFIXES): the fork IPC channel on the process object. */ @@ -262,6 +240,7 @@ const CHILD_PROCESS_LIB_FNS: ReadonlySet = new Set([ "process.connected", "process.disconnect", "process.forkTarget", + "process.ipcOpen", "process.onDisconnect", "process.onMessage", "process.send", @@ -271,8 +250,23 @@ const CHILD_PROCESS_LIB_FNS: ReadonlySet = new Set([ /** libCall name prefixes that link the child-process unit. */ const CHILD_PROCESS_LIB_PREFIXES: readonly string[] = ["child.", "cp.", "spawnRes.", "writer."]; +/** The entry points scr_dyn_builtin_class.c defines. The unit calls + * listeners through the dyn-invoke unit, renders unhandled 'error' + * payloads through the inspect unit, creates EventEmitter's private + * symbols through the symbol unit and shares defaultMaxListeners with the + * emitter unit, so each of them links all five. */ +const BUILTIN_CLASS_LIB_FNS: readonly IrLibFn[] = [ + "dyn.builtinClass", + // the stream classes, through scr_dyn_builtin_stream.c + "dyn.builtinStreamClass", + "dyn.builtinInstanceOf", + "dyn.functionInstanceOf", +]; + /** Entry points that link the symbol unit, besides the sym. and disposable. families. */ const SYMBOL_LIB_FNS: ReadonlySet = new Set([ + // EventEmitter's kShapeMode and kCapture properties + ...BUILTIN_CLASS_LIB_FNS, "dyn.usingDispose", "dyn.usingMethod", "util.isDeepStrictEqual", @@ -311,9 +305,6 @@ const COPYING_BYTES_METHODS: ReadonlySet = new Set([ "with", ]); -const startsWithAny = (fn: string, prefixes: readonly string[]): boolean => - prefixes.some((prefix) => fn.startsWith(prefix)); - /** Inspect types and expressions together in one traversal. This is a fresh * snapshot: callers may mutate an IR module between compilation passes. */ /** The checked-dynamic undefined value (dynUndefinedExpr). */ @@ -393,60 +384,420 @@ function boxesPromise(t: IrType, tables: TypeTables, seen: Set): boolean } } +/** The libCall fns served by the OPTIONAL events unit (scr_events.c): + * process signal/exit listeners and the piped-stdin surface. */ +const PROCESS_EVENT_LIB_FNS: ReadonlySet = new Set([ + "process.stdio", + "process.onSignal", + "process.offSignal", + "process.onExit", + "process.offExit", + "stdin.onData", + "stdin.onEnd", + "stdin.onError", + "stdin.nextChunk", + // node:readline rides the stdin unit (scr_readline.c links beside + // scr_events.c under the same gate). + "rl.create", + "rl.createSignal", + "rl.question", + "rl.questionAsync", + "rl.questionAsyncSignal", + "rl.questionDyn", + "rl.questionAsyncDyn", + "rl.questionAsyncSignalDyn", + "rl.close", + "rl.pause", + "rl.resume", + "rl.prompt", + "rl.promptWith", + "rl.setPrompt", + "rl.setPromptDyn", + "rl.getPrompt", + "rl.write", + "rl.writeKey", + "rl.on", + "rl.iterOpen", + "rl.iterNext", + "rl.iterReturn", + "rl.checkOptions", + "rl.checkCompleter", + "rl.setOptions", + "rl.setHistory", + "rl.history", + "rl.numProp", + "rl.terminal", + "rl.line", + "rl.isTerminal", + "rl.isPromises", + "rl.cursor", + "rl.seq", + "rl.moves", + "rl.cursorPos", + "rl.keypressStdin", + "rl.keypressStream", + "stdin.onKeypress", + "rl.uiOpen", + "rl.uiOpenStream", + "rl.uiCursorTo", + "rl.uiMoveCursor", + "rl.uiClearLine", + "rl.uiClearScreenDown", + "rl.uiRollback", + "rl.uiCommit", +]); + +/** The legacy http2.* libCalls implemented by scr_http.c/scr_tls.c (the + * allowHTTP1 compatibility slice) — they must NOT pull scr_http2.c, so + * divergence-57 binaries keep their exact link line. */ +const HTTP2_LEGACY_FNS: ReadonlySet = new Set([ + "http2.streamNoop", + "http2.streamUndefCall", +]); + /** Names that reach the extended process-stream surface (the * SCR_STDIO_EXTENDED events unit): stdin.setEncoding and 'resize'. */ const STDIO_EXTENDED_NAMES: ReadonlySet = new Set(["setEncoding", "resize"]); -function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): RuntimeFeatures { - const features: RuntimeFeatures = { - regex: false, - copying: false, - legacyTextDecoder: false, - fileHandle: false, - fetch: false, - // Subclasses of the runtime EventTarget/Event bases tear down and box - // through the EventTarget unit even when no event.* libCall survives. - eventTarget: (mod.classes ?? []).some( - (c) => c.name === RUNTIME_EVENT_TARGET_CLASS || c.name === RUNTIME_EVENT_CLASS, - ), - processEvents: false, - warningReplace: false, - stdioExtended: false, - emitter: - mod.workers === true || (mod.classes ?? []).some((c) => c.name === RUNTIME_EMITTER_CLASS), - stream: (mod.classes ?? []).some((c) => RUNTIME_STREAM_CLASSES.has(c.name)), - zlib: false, - dc: false, - assert: false, - dynInvoke: mod.workers === true, - dynAsync: +/** The libCall nodes the rules below test. */ +type LibCallNode = IrExpr & { kind: "libCall" }; + +/** What makes a program need one optional runtime unit. The feature is set + * when any condition holds: `initial` from the module's declarations, a + * libCall named in `fns` or starting with one of `prefixes`, a libCall + * `libCall` accepts, another expression node `expr` accepts, or a type + * slot of one of the `types` kinds. A libCall or type that needs several + * units appears in each unit's rule. */ +interface RuntimeFeatureRule { + initial?: (mod: IrModule) => boolean; + fns?: readonly IrLibFn[]; + prefixes?: readonly string[]; + libCall?: (node: LibCallNode) => boolean; + expr?: (node: IrExpr, tables: () => TypeTables) => boolean; + types?: readonly IrType["kind"][]; +} + +/** Gives every rule in the table one type. */ +const rule = (r: RuntimeFeatureRule): RuntimeFeatureRule => r; + +/* The HTTP, HTTPS, HTTP/2 and TLS surfaces, which link the net unit. */ +const NET_PREFIXES: readonly string[] = ["net.", "http.", "tls.", "https.", "http2."]; + +/** One rule per runtime feature, sorted by name. A new feature is an + * entry here and its line in noRuntimeFeatures; RuntimeFeatures, the scan + * and its early exit derive from this table. */ +const RUNTIME_FEATURE_RULES = { + assert: rule({ prefixes: ["assert."] }), + /** The program enters AsyncLocalStorage stores (store.run, enterWith, + * a diagnostics_channel store binding) or reads the test runner's + * context (getTestContext): its timer, immediate, tick and + * queueMicrotask scheduling goes through the runtime's *_als entry + * points, which carry the active stores into each callback. */ + asyncContext: rule({ fns: [...ASYNC_CONTEXT_LIB_FNS] }), + /** scr_wasm.c converts i64 values through BigInt. */ + bigint: rule({ + fns: ["process.hrtimeValue"], + prefixes: ["bigint.", "wasm."], + types: ["bigint"], + }), + childProcess: rule({ + fns: [...CHILD_PROCESS_LIB_FNS], + prefixes: CHILD_PROCESS_LIB_PREFIXES, + types: ["child", "childStream", "childWriter", "spawnRes"], + }), + /** scr_cipher.c over the mbedTLS archive; DEP0182 reports through the + * process-warning unit, and unknown output encodings render through the + * inspect unit. */ + cipher: rule({ fns: ["crypto.cipherNew"] }), + copying: rule({ + expr: (node, _tables) => + (node.kind === "arrIntrinsic" && COPYING_ARR_METHODS.has(node.method)) || + (node.kind === "bytesIntrinsic" && COPYING_BYTES_METHODS.has(node.method)), + }), + dc: rule({ prefixes: ["dc."] }), + /** dns.lookup lives in the dgram unit. */ + dgram: rule({ prefixes: ["dgram.", "dns."], types: ["dgramSocket"] }), + /** node:dns/promises: scr_dns_promises.c over the dgram unit. */ + dnsPromises: rule({ fns: ["dns.promisesLookup", "dns.promisesLookupService"] }), + dynAsync: rule({ + initial: (mod) => mod.workers === true || mod.functions.some( (fn) => fn.async === true && fn.generator === undefined && fn.returnType.kind === "dyn", ), - dynTier: false, + // The legacy url functions and crypto.createCipheriv emit their + // deprecation warnings through the dyn-async unit; scr_wasm.c boxes its + // handles and promises through it. + fns: [ + ...DYN_ASYNC_LIB_FNS, + ...READABLE_DYN_ASYNC_LIB_FNS, + ...LEGACY_URL_LIB_FNS, + "crypto.cipherNew", + ], + prefixes: [...READABLE_DYN_ASYNC_PREFIXES, "wasm."], + expr: (node, tables) => + (node.kind === "awaitExpr" && node.type.kind === "dyn") || + // Rejection reasons cross as dyn values (allSettled's records, any's + // AggregateError list). + (node.kind === "intrinsic" && + (node.name === "promise.allSettled" || node.name === "promise.any")) || + (node.kind === "dynFrom" && boxesPromise(node.value.type, tables(), new Set())), + }), + /** EventEmitter and the stream classes as untyped constructor values + * (scr_dyn_builtin_class.c), which call listeners through the dyn-invoke + * unit, render unhandled 'error' payloads through the inspect unit and + * create EventEmitter's private symbols through the symbol unit. */ + dynBuiltinClass: rule({ + fns: [...BUILTIN_CLASS_LIB_FNS], + }), + /** The stream classes as untyped constructor values with their stream + * half (scr_dyn_builtin_stream.c): native stream state for constructor + * functions, over the stream unit. */ + dynBuiltinStream: rule({ fns: ["dyn.builtinStreamClass"] }), + dynInvoke: rule({ + initial: (mod) => mod.workers === true, + fns: [...DYN_INVOKE_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS, ...BUILTIN_CLASS_LIB_FNS], + // Constant-key reads of Object.prototype member names answer the + // inherited member through scr_dyn_invoke.c. + expr: (node, _tables) => + node.kind === "dynInvoke" || + (node.kind === "dynKeyGet" && + node.key.kind === "strLit" && + INHERITED_MEMBER_NAMES.has(node.key.value)), + }), + /** The program has dynamic-tier jsval operations (js.* builtins, the + * jsval pass): links the scr_js_* units (two-tier design I3). */ + dynTier: rule({ prefixes: ["js."] }), + /** Checked-dynamic stream slots test values and pipe into JavaScript + * stream objects (scr_stream_dyn.c). */ + streamDyn: rule({ + fns: ["stream.dynNative", "stream.dynNativePipe", "stream.pipeDyn", "stream.unpipeDyn"], + }), + /** The stream unit emits through the emitter unit, also when the + * program constructs no stream (a pipeline of functions). */ + emitter: rule({ + initial: (mod) => + mod.workers === true || (mod.classes ?? []).some((c) => c.name === RUNTIME_EMITTER_CLASS), + fns: [...STREAM_LIB_FNS, ...BUILTIN_CLASS_LIB_FNS], + prefixes: ["emitter.", ...STREAM_LIB_PREFIXES], + }), + /** Subclasses of the runtime EventTarget/Event bases tear down and box + * through the EventTarget unit even when no event.* libCall survives. */ + eventTarget: rule({ + initial: (mod) => + (mod.classes ?? []).some( + (c) => c.name === RUNTIME_EVENT_TARGET_CLASS || c.name === RUNTIME_EVENT_CLASS, + ), + fns: ["events.addAbortListener", ...ABORT_SIGNAL_LIB_FNS], + prefixes: ["event."], + }), + /** events.once(emitter, name, { signal }) (scr_events_once_signal.c). */ + eventsOnceSignal: rule({ fns: ["events.onceSignal"] }), + fetch: rule({ prefixes: ["fetch."] }), + fileHandle: rule({ types: ["fileHandle"] }), + fsWatch: rule({ prefixes: ["fs.watch", "watcher."], types: ["fsWatcher"] }), + /** A socket 'lookup' listener: the C main installs the net unit's + * lookup records (scr_net_lookup_install). */ + netLookup: rule({ fns: ["net.sockOnLookup"] }), + http: rule({ + prefixes: ["http.", "tls.", "https.", "http2."], + types: ["httpReq", "httpRes", "httpClientReq", "secureCtx"], + }), + /** The real h2 surface (scr_http2.c); the legacy http2.* libCalls of the + * allowHTTP1 slice are served by scr_http.c and scr_tls.c. */ + http2: rule({ + libCall: (node) => node.fn.startsWith("http2.") && !HTTP2_LEGACY_FNS.has(node.fn), + types: ["http2Session", "http2Stream"], + }), + /** scr_test.c renders thrown strings with the inspect unit's string + * formatting; scr_util_callable.c wraps the inspect unit; readline's + * terminal unit prints completion errors through it when there is a + * completer. */ + inspect: rule({ + fns: [ + "global.native", + "util.styleText", + "crypto.cipherNew", + ...BUILTIN_CLASS_LIB_FNS, + ...UTIL_CALLABLE_LIB_FNS, + ], + prefixes: ["insp.", "console.", "test."], + libCall: (node) => node.fn === "rl.terminal" && !isDynUndefined(node.args[3]!), + }), + legacyTextDecoder: rule({ fns: [...LEGACY_TEXT_DECODER_LIB_FNS] }), + /** The program calls mock.timers members: its timer calls go through + * scr_test.c's mock-aware entry points. */ + mockTimers: rule({ prefixes: ["test.mockTimers", "test.ctxMockTimers"] }), + net: rule({ + prefixes: NET_PREFIXES, + types: [ + "netServer", + "netSocket", + "http2Session", + "http2Stream", + "httpReq", + "httpRes", + "httpClientReq", + "secureCtx", + ], + }), + nodeTest: rule({ prefixes: ["test."], types: ["testCtx"] }), + /** scr_util_callable.c reads options through the parseArgs unit's + * descriptors. */ + parseArgs: rule({ fns: [...PARSE_ARGS_LIB_FNS, ...UTIL_CALLABLE_LIB_FNS] }), + /** perf_hooks event-loop metrics (eventLoopUtilization, + * monitorEventLoopDelay): scr_perf_loop.c. */ + perfLoop: rule({ fns: ["perf.idleTime"], prefixes: ["perf.loop", "perf.eld"] }), + processEvents: rule({ fns: [...PROCESS_EVENT_LIB_FNS, "util.styleText"], types: ["procStream"] }), + /** scr_url_legacy.c parses and stringifies queries. */ + qs: rule({ fns: [...QS_LIB_FNS, ...LEGACY_URL_LIB_FNS] }), + /** scr_url_legacy.c lowercases and normalizes hostnames with the regex + * unit's libunicode, NODE_DEBUG matching in scr_util_callable.c and + * readline's terminal unit use the regex unit, and locale case mapping + * runs its default conversion. */ + regex: rule({ + fns: [ + ...REGEX_LIB_FNS, + ...LEGACY_URL_LIB_FNS, + ...UTIL_CALLABLE_LIB_FNS, + "rl.terminal", + "str.localeCase", + ], + expr: (node, _tables) => + node.kind === "regexLit" || + node.kind === "regexIntrinsic" || + (node.kind === "strIntrinsic" && CASE_MAPPING_STR_METHODS.has(node.method)), + types: ["regex"], + }), + searchParams: rule({ fns: ["url.searchParams"], prefixes: ["sp."], types: ["searchParams"] }), + /** stdin setEncoding / string data listeners / stdio 'resize' events. A + * typed string 'data' listener takes setEncoding-decoded chunks; the + * process stream object's setEncoding and 'resize' members are reached + * by name (dynamic member calls and event-name strings). */ + stdioExtended: rule({ + libCall: (node) => + node.fn === "stdin.onData" && + node.args[0]?.type.kind === "func" && + node.args[0].type.params[0]?.kind === "string", + expr: (node, _tables) => + (node.kind === "dynInvoke" && STDIO_EXTENDED_NAMES.has(node.method)) || + (node.kind === "strLit" && STDIO_EXTENDED_NAMES.has(node.value)), + }), + stream: rule({ + initial: (mod) => (mod.classes ?? []).some((c) => RUNTIME_STREAM_CLASSES.has(c.name)), + fns: [...STREAM_LIB_FNS, "dyn.builtinStreamClass"], + prefixes: STREAM_LIB_PREFIXES, + }), + /** A Dirent row converted to dyn carries Node's Symbol(type) key + * (scr_dyn_dirent_finish, defined in scr_symbol.c). */ + symbol: rule({ + initial: (mod) => (mod.records ?? []).some(isDirentShape), + fns: [...SYMBOL_LIB_FNS], + prefixes: ["sym.", "disposable."], + types: ["symbol"], + }), + tls: rule({ prefixes: ["tls.", "https.", "http2."], types: ["secureCtx"] }), + tlsCa: rule({ prefixes: ["tlsca."] }), + /** process.emitWarning is assigned (process.setEmitWarning). */ + warningReplace: rule({ fns: ["process.setEmitWarning"] }), + /** The WebAssembly API (scr_wasm.c). */ + wasm: rule({ prefixes: ["wasm."] }), + /** crypto.subtle importKey/sign/verify/digest and CryptoKey: + * scr_webcrypto.c. */ + webcrypto: rule({ prefixes: ["webcrypto."] }), + zlib: rule({ prefixes: ["zlib."] }), +}; + +type RuntimeFeatureName = keyof typeof RUNTIME_FEATURE_RULES; + +export type RuntimeFeatures = Record; + +const RUNTIME_FEATURE_NAMES: RuntimeFeatureName[] = []; +for (const name in RUNTIME_FEATURE_RULES) RUNTIME_FEATURE_NAMES.push(name as RuntimeFeatureName); + +/** Every feature off, in the table's order. TypeScript rejects this + * literal when it misses a feature or names one without a rule. */ +function noRuntimeFeatures(): RuntimeFeatures { + return { + assert: false, asyncContext: false, - inspect: false, - childProcess: false, - net: false, - // A Dirent row converted to dyn carries Node's Symbol(type) key - // (scr_dyn_dirent_finish, defined in scr_symbol.c). - symbol: (mod.records ?? []).some(isDirentShape), bigint: false, - searchParams: false, - qs: false, - parseArgs: false, - fsWatch: false, - nodeTest: false, - mockTimers: false, + childProcess: false, + cipher: false, + copying: false, + dc: false, dgram: false, + dnsPromises: false, + dynAsync: false, + dynBuiltinClass: false, + dynBuiltinStream: false, + dynInvoke: false, + dynTier: false, + emitter: false, + eventTarget: false, + eventsOnceSignal: false, + fetch: false, + fileHandle: false, + fsWatch: false, + netLookup: false, http: false, http2: false, + inspect: false, + legacyTextDecoder: false, + mockTimers: false, + net: false, + nodeTest: false, + parseArgs: false, + perfLoop: false, + processEvents: false, + qs: false, + regex: false, + searchParams: false, + stdioExtended: false, + stream: false, + streamDyn: false, + symbol: false, tls: false, tlsCa: false, - cipher: false, + warningReplace: false, + wasm: false, webcrypto: false, + zlib: false, }; +} + +const ruleOf = (name: RuntimeFeatureName): RuntimeFeatureRule => RUNTIME_FEATURE_RULES[name]; + +/** The rules indexed for the scan: features by libCall name and by type + * kind, and the prefix and predicate conditions in rule order. */ +const FEATURES_BY_FN = new Map(); +const FEATURES_BY_TYPE = new Map(); +const FEATURE_PREFIXES: { prefix: string; name: RuntimeFeatureName }[] = []; +const FEATURE_LIBCALL_TESTS: RuntimeFeatureName[] = []; +const FEATURE_EXPR_TESTS: RuntimeFeatureName[] = []; +for (const name of RUNTIME_FEATURE_NAMES) { + const rule = ruleOf(name); + for (const fn of rule.fns ?? []) { + const names = FEATURES_BY_FN.get(fn); + if (names) names.push(name); + else FEATURES_BY_FN.set(fn, [name]); + } + for (const kind of rule.types ?? []) { + const names = FEATURES_BY_TYPE.get(kind); + if (names) names.push(name); + else FEATURES_BY_TYPE.set(kind, [name]); + } + for (const prefix of rule.prefixes ?? []) FEATURE_PREFIXES.push({ prefix, name }); + if (rule.libCall) FEATURE_LIBCALL_TESTS.push(name); + if (rule.expr) FEATURE_EXPR_TESTS.push(name); +} + +function scanRuntimeFeatures(mod: IrModule, stopAt?: RuntimeFeatureName): RuntimeFeatures { + const features = noRuntimeFeatures(); + for (const name of RUNTIME_FEATURE_NAMES) { + const initial = ruleOf(name).initial; + if (initial && initial(mod)) features[name] = true; + } const keepGoing = (): boolean => stopAt === undefined || !features[stopAt]; let tables: TypeTables | undefined; const typeTables = (): TypeTables => @@ -458,207 +809,19 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run const expr = (node: IrExpr): boolean => { if (node.kind === "libCall") { const fn = node.fn; - if (fn.startsWith("js.")) features.dynTier = true; - if (REGEX_LIB_FNS.has(fn)) features.regex = true; - if (LEGACY_TEXT_DECODER_LIB_FNS.has(fn)) features.legacyTextDecoder = true; - if (fn.startsWith("fetch.")) features.fetch = true; - if (fn === "process.setEmitWarning") features.warningReplace = true; - // A typed string 'data' listener takes setEncoding-decoded chunks. - if ( - fn === "stdin.onData" && - node.args[0]?.type.kind === "func" && - node.args[0].type.params[0]?.kind === "string" - ) - features.stdioExtended = true; - if (fn.startsWith("event.") || fn === "events.addAbortListener") features.eventTarget = true; - if (ABORT_SIGNAL_LIB_FNS.has(fn)) features.eventTarget = true; - if (PROCESS_EVENT_LIB_FNS.has(fn)) features.processEvents = true; - // With a completer, readline's terminal unit prints completion errors - // through the inspect unit. - if (fn === "rl.terminal") { - features.regex = true; - if (!isDynUndefined(node.args[3]!)) features.inspect = true; - } - if (fn.startsWith("emitter.")) features.emitter = true; - if (STREAM_LIB_FNS.has(fn) || startsWithAny(fn, STREAM_LIB_PREFIXES)) { - // the stream unit emits through the emitter unit, also when the - // program constructs no stream (a pipeline of functions) - features.stream = true; - features.emitter = true; - } - if (fn.startsWith("zlib.")) features.zlib = true; - if (READABLE_DYN_ASYNC_LIB_FNS.has(fn) || startsWithAny(fn, READABLE_DYN_ASYNC_PREFIXES)) - features.dynAsync = true; - if (fn.startsWith("dc.")) features.dc = true; - if (fn.startsWith("assert.")) features.assert = true; - if (DYN_INVOKE_LIB_FNS.has(fn)) features.dynInvoke = true; - if (DYN_ASYNC_LIB_FNS.has(fn)) features.dynAsync = true; - if (ASYNC_CONTEXT_LIB_FNS.has(fn)) features.asyncContext = true; - if ( - fn.startsWith("insp.") || - fn.startsWith("console.") || - fn === "global.native" || - fn === "util.styleText" - ) - features.inspect = true; - if (CHILD_PROCESS_LIB_FNS.has(fn) || startsWithAny(fn, CHILD_PROCESS_LIB_PREFIXES)) - features.childProcess = true; - if (fn.startsWith("net.")) features.net = true; - if (fn.startsWith("http.")) { - features.net = true; - features.http = true; - } - if (fn.startsWith("tls.") || fn.startsWith("https.") || fn.startsWith("http2.")) { - features.net = true; - features.http = true; - features.tls = true; - } - if (fn.startsWith("http2.") && !HTTP2_LEGACY_FNS.has(fn)) features.http2 = true; - if (fn.startsWith("sym.") || fn.startsWith("disposable.") || SYMBOL_LIB_FNS.has(fn)) - features.symbol = true; - if (fn === "process.hrtimeValue") features.bigint = true; - if (fn.startsWith("bigint.")) features.bigint = true; - if (fn.startsWith("sp.") || fn === "url.searchParams") features.searchParams = true; - if (QS_LIB_FNS.has(fn)) features.qs = true; - // scr_url_legacy.c lowercases and normalizes hostnames (the regex - // unit's libunicode), parses and stringifies queries, and emits its - // deprecation warnings through the dyn-async unit. - if (LEGACY_URL_LIB_FNS.has(fn)) { - features.regex = true; - features.qs = true; - features.dynAsync = true; - } - if (PARSE_ARGS_LIB_FNS.has(fn)) features.parseArgs = true; - if (fn === "util.styleText") features.processEvents = true; - // scr_util_callable.c: dyn wrappers over the descriptor, inspect, - // regex (NODE_DEBUG matching) and terminal-color units. - if (UTIL_CALLABLE_LIB_FNS.has(fn)) { - features.parseArgs = true; - features.inspect = true; - features.dynInvoke = true; - features.regex = true; - } - if (fn.startsWith("fs.watch") || fn.startsWith("watcher.")) features.fsWatch = true; - // scr_test.c renders thrown strings in the failing-tests section with - // the inspect unit's string formatting. - if (fn.startsWith("test.")) { - features.nodeTest = true; - features.inspect = true; - if (fn.startsWith("test.mockTimers") || fn.startsWith("test.ctxMockTimers")) { - features.mockTimers = true; - } - } - if (fn.startsWith("dgram.") || fn.startsWith("dns.")) features.dgram = true; - if (fn.startsWith("tlsca.")) features.tlsCa = true; - // Locale case mapping runs the default conversion of the regex unit. - if (fn === "str.localeCase") features.regex = true; - // scr_cipher.c over the mbedTLS archive; DEP0182 reports through the - // process-warning unit, and unknown output encodings render through - // the inspect unit. - if (fn === "crypto.cipherNew") { - features.cipher = true; - features.dynAsync = true; - features.inspect = true; - } - // crypto.subtle and CryptoKey: scr_webcrypto.c. - if (fn.startsWith("webcrypto.")) features.webcrypto = true; + for (const name of FEATURES_BY_FN.get(fn) ?? []) features[name] = true; + for (const { prefix, name } of FEATURE_PREFIXES) + if (!features[name] && fn.startsWith(prefix)) features[name] = true; + for (const name of FEATURE_LIBCALL_TESTS) + if (!features[name] && ruleOf(name).libCall!(node)) features[name] = true; } else { - switch (node.kind) { - case "regexLit": - case "regexIntrinsic": - features.regex = true; - break; - case "strIntrinsic": - if (CASE_MAPPING_STR_METHODS.has(node.method)) features.regex = true; - break; - case "arrIntrinsic": - if (COPYING_ARR_METHODS.has(node.method)) features.copying = true; - break; - case "bytesIntrinsic": - if (COPYING_BYTES_METHODS.has(node.method)) features.copying = true; - break; - case "dynInvoke": - features.dynInvoke = true; - if (STDIO_EXTENDED_NAMES.has(node.method)) features.stdioExtended = true; - break; - // The process stream object's setEncoding and 'resize' members are - // reached by name (dynamic member calls and event-name strings). - case "strLit": - if (STDIO_EXTENDED_NAMES.has(node.value)) features.stdioExtended = true; - break; - case "awaitExpr": - if (node.type.kind === "dyn") features.dynAsync = true; - break; - case "intrinsic": - // Rejection reasons cross as dyn values (allSettled's records, - // any's AggregateError list). - if (node.name === "promise.allSettled" || node.name === "promise.any") - features.dynAsync = true; - break; - case "dynFrom": - if (!features.dynAsync && boxesPromise(node.value.type, typeTables(), new Set())) - features.dynAsync = true; - break; - } + for (const name of FEATURE_EXPR_TESTS) + if (!features[name] && ruleOf(name).expr!(node, typeTables)) features[name] = true; } return keepGoing(); }; const type = (node: IrType): boolean => { - switch (node.kind) { - case "regex": - features.regex = true; - break; - case "fileHandle": - features.fileHandle = true; - break; - case "procStream": - features.processEvents = true; - break; - case "child": - case "childStream": - case "childWriter": - case "spawnRes": - features.childProcess = true; - break; - case "netServer": - case "netSocket": - features.net = true; - break; - case "http2Session": - case "http2Stream": - features.net = true; - features.http2 = true; - break; - case "httpReq": - case "httpRes": - case "httpClientReq": - features.net = true; - features.http = true; - break; - case "secureCtx": - features.net = true; - features.http = true; - features.tls = true; - break; - case "symbol": - features.symbol = true; - break; - case "bigint": - features.bigint = true; - break; - case "searchParams": - features.searchParams = true; - break; - case "fsWatcher": - features.fsWatch = true; - break; - case "testCtx": - features.nodeTest = true; - break; - case "dgramSocket": - features.dgram = true; - break; - } + for (const name of FEATURES_BY_TYPE.get(node.kind) ?? []) features[name] = true; return keepGoing(); }; if (keepGoing()) everyModuleNode(mod, { expr, stmt: keepGoing, type }); @@ -702,64 +865,6 @@ export function moduleUsesFetch(mod: IrModule): boolean { return scanRuntimeFeatures(mod, "fetch").fetch; } -/** The libCall fns served by the OPTIONAL events unit (scr_events.c): - * process signal/exit listeners and the piped-stdin surface. */ -const PROCESS_EVENT_LIB_FNS: ReadonlySet = new Set([ - "process.stdio", - "process.onSignal", - "process.offSignal", - "process.onExit", - "process.offExit", - "stdin.onData", - "stdin.onEnd", - "stdin.onError", - "stdin.nextChunk", - // node:readline rides the stdin unit (scr_readline.c links beside - // scr_events.c under the same gate). - "rl.create", - "rl.createSignal", - "rl.question", - "rl.questionAsync", - "rl.questionAsyncSignal", - "rl.close", - "rl.pause", - "rl.resume", - "rl.prompt", - "rl.promptWith", - "rl.setPrompt", - "rl.getPrompt", - "rl.write", - "rl.writeKey", - "rl.on", - "rl.iterOpen", - "rl.iterNext", - "rl.iterReturn", - "rl.checkOptions", - "rl.checkCompleter", - "rl.setOptions", - "rl.setHistory", - "rl.history", - "rl.numProp", - "rl.terminal", - "rl.line", - "rl.isTerminal", - "rl.cursor", - "rl.seq", - "rl.moves", - "rl.cursorPos", - "rl.keypressStdin", - "rl.keypressStream", - "stdin.onKeypress", - "rl.uiOpen", - "rl.uiOpenStream", - "rl.uiCursorTo", - "rl.uiMoveCursor", - "rl.uiClearLine", - "rl.uiClearScreenDown", - "rl.uiRollback", - "rl.uiCommit", -]); - /** True when the module uses the process-events surface — the link switch * that pulls scr_events.c into the binary and has the emitted main call * scr_events_install (native-toolchain.ts + emitter; the scr_regex/scr_fetch/scr_zlib @@ -793,6 +898,14 @@ export function moduleUsesStream(mod: IrModule): boolean { return scanRuntimeFeatures(mod, "stream").stream; } +/** True when the program links scr_dyn_builtin_stream.c (stream classes + * as untyped constructor values): its runtime streams box through that + * unit's view and checked casts to the stream classes accept constructor- + * function streams. Programs without it keep their exact code. */ +export function moduleUsesDynBuiltinStream(mod: IrModule): boolean { + return scanRuntimeFeatures(mod, "dynBuiltinStream").dynBuiltinStream; +} + export const NPM_COMPRESS_MIN = 1024; export function moduleUsesZlib(mod: IrModule): boolean { @@ -949,14 +1062,6 @@ export function moduleUsesHttpServer(mod: IrModule): boolean { return scanRuntimeFeatures(mod, "http").http; } -/** The legacy http2.* libCalls implemented by scr_http.c/scr_tls.c (the - * allowHTTP1 compatibility slice) — they must NOT pull scr_http2.c, so - * divergence-57 binaries keep their exact link line. */ -const HTTP2_LEGACY_FNS: ReadonlySet = new Set([ - "http2.streamNoop", - "http2.streamUndefCall", -]); - /** True when the module uses the REAL h2 surface (scr_http2.c): any core * http2.* libCall, or an h2 handle type left behind by a fenced statement * (its emitted release call needs the unit — the moduleUsesNet story). */ diff --git a/packages/compiler/src/ir/traverse.test.ts b/packages/compiler/src/ir/traverse.test.ts index 874e7846e..fbe84644a 100644 --- a/packages/compiler/src/ir/traverse.test.ts +++ b/packages/compiler/src/ir/traverse.test.ts @@ -28,7 +28,8 @@ const program = createProgram([path], { noLib: true, noResolve: true, types: [] const source = program.getSourceFile(path)!; afterAll(() => program.dispose()); function childType(type: ts.TypeNode): boolean { - if (ts.isTypeReferenceNode(type)) return ["IrExpr", "IrStmt"].includes(type.typeName.getText()); + if (ts.isTypeReferenceNode(type)) + return ["IrExpr", "IrStmt"].includes(ts.getTextOfNode(type.typeName)); if (ts.isArrayTypeNode(type)) return childType(type.elementType); if (ts.isUnionTypeNode(type)) return type.types.nodes.some(childType); if (ts.isTypeLiteralNode(type)) @@ -47,15 +48,15 @@ for (const name of ["IrExpr", "IrStmt"]) { for (const variant of alias.type.types.nodes) { if (!ts.isTypeLiteralNode(variant)) throw new Error("IR variants must be records"); const members = variant.members.nodes.filter(ts.isPropertySignature); - const tag = members.find((member) => member.name.getText() === "kind")!.type!; - const kind = JSON.parse(tag.getText()) as string; + const tag = members.find((member) => ts.getTextOfNode(member.name) === "kind")!.type!; + const kind = JSON.parse(ts.getTextOfNode(tag)) as string; test(`${name}.${kind}: every declared child is visited and rewritten, metadata is preserved`, () => { for (const presence of [true, false]) { const expected: (IrExpr | IrStmt)[] = []; function value(type: ts.TypeNode): unknown { if (ts.isTypeReferenceNode(type)) { const node = - type.typeName.getText() === "IrExpr" + ts.getTextOfNode(type.typeName) === "IrExpr" ? num(expected.length) : statement(expected.length); expected.push(node); @@ -72,7 +73,7 @@ for (const name of ["IrExpr", "IrStmt"]) { }; for (const member of type.members.nodes) { if (ts.isPropertySignature(member) && member.type && childType(member.type)) - result[member.name.getText()] = value(member.type); + result[ts.getTextOfNode(member.name)] = value(member.type); } return result; } @@ -86,7 +87,7 @@ for (const name of ["IrExpr", "IrStmt"]) { }; for (const member of members) { if (member.type && childType(member.type) && (presence || !member.postfixToken)) - input[member.name.getText()] = value(member.type); + input[ts.getTextOfNode(member.name)] = value(member.type); } const before = structuredClone(input); const seen: (IrExpr | IrStmt)[] = []; @@ -135,7 +136,7 @@ for (const name of ["IrExpr", "IrStmt"]) { expect(output.loc).toBe(loc); expect((output as unknown as Record)["metadata"]).toBe(input["metadata"]); for (const member of members) { - const key = member.name.getText(); + const key = ts.getTextOfNode(member.name); if (member.postfixToken && !presence) expect(Object.hasOwn(output, key)).toBe(false); } } @@ -217,7 +218,7 @@ test("module traversal covers every declared type slot", () => { const active = new Set(); function slots(node: ts.TypeNode): unknown { if (ts.isTypeReferenceNode(node)) { - const name = node.typeName.getText(); + const name = ts.getTextOfNode(node.typeName); if (name === "IrType") { const value: IrType = { kind: "regex" }; expected.push(value); @@ -268,7 +269,7 @@ test("module traversal covers every declared type slot", () => { for (const member of members) { if (!ts.isPropertySignature(member) || !member.type) continue; const value = slots(member.type); - if (value !== undefined) result[member.name.getText()] = value; + if (value !== undefined) result[ts.getTextOfNode(member.name)] = value; } return result; } @@ -316,20 +317,20 @@ test("type traversal visits all structural children and leaves named shapes as r if (!ts.isTypeLiteralNode(variant)) throw new Error("IrType variants must be records"); const members = variant.members.nodes.filter(ts.isPropertySignature); const kind = JSON.parse( - members.find((member) => member.name.getText() === "kind")!.type!.getText(), + ts.getTextOfNode(members.find((member) => ts.getTextOfNode(member.name) === "kind")!.type!), ) as string; const node: Record = { kind }; const expected: IrType[] = []; for (const member of members) { if (!member.type) continue; const child = - ts.isTypeReferenceNode(member.type) && member.type.typeName.getText() === "IrType"; + ts.isTypeReferenceNode(member.type) && ts.getTextOfNode(member.type.typeName) === "IrType"; const array = - ts.isArrayTypeNode(member.type) && member.type.elementType.getText() === "IrType"; + ts.isArrayTypeNode(member.type) && ts.getTextOfNode(member.type.elementType) === "IrType"; if (!child && !array) continue; const value: IrType = { kind: "fileHandle" }; expected.push(value); - node[member.name.getText()] = array ? [value] : value; + node[ts.getTextOfNode(member.name)] = array ? [value] : value; } const seen: IrType[] = []; expect( diff --git a/packages/compiler/src/ir/unshare.ts b/packages/compiler/src/ir/unshare.ts index 36dd6176f..a83d52a5f 100644 --- a/packages/compiler/src/ir/unshare.ts +++ b/packages/compiler/src/ir/unshare.ts @@ -42,14 +42,15 @@ export function unshareNodes(mod: IrModule): IrModule { } if (affected.size === 0) return mod; // Rebuilding a function copies every node that has children; a repeated - // childless node reads back as a fresh object at each of its sites there. + // childless node gets its own shallow copy at each of its sites there. + // Both kinds of copy keep sharing the type and location objects. const expr = (node: IrExpr): IrExpr => { const copy = mapExprChildren(node, expr, stmt); - return copy !== node || !repeated.has(node) ? copy : reparseExpr(node); + return copy !== node || !repeated.has(node) ? copy : copyChildlessExpr(node); }; const stmt = (node: IrStmt): IrStmt => { const copy = mapStmtChildren(node, expr, stmt); - return copy !== node || !repeated.has(node) ? copy : reparseStmt(node); + return copy !== node || !repeated.has(node) ? copy : copyChildlessStmt(node); }; return { ...mod, @@ -58,3 +59,66 @@ export function unshareNodes(mod: IrModule): IrModule { ), }; } + +/** A shallow copy of a node that mapExprChildren returns unchanged. Each + * case spreads one node kind, so the copy is a plain record copy of that + * kind. Any other kind is copied through the encoding. */ +function copyChildlessExpr(node: IrExpr): IrExpr { + switch (node.kind) { + case "varRef": + return { ...node }; + case "strLit": + return { ...node }; + case "numLit": + return { ...node }; + case "boolLit": + return { ...node }; + case "unitLit": + return { ...node }; + case "closure": + return { ...node }; + case "moduleNsRef": + return { ...node }; + case "incDec": + return { ...node }; + case "chainRecv": + return { ...node }; + case "regexLit": + return { ...node }; + case "templateStrings": + return { ...node }; + case "setNew": + return { ...node }; + case "mapNew": + return { ...node }; + case "dynObjLit": + return { ...node }; + case "selfRef": + return { ...node }; + case "promiseWithResolvers": + return { ...node }; + case "classRef": + return { ...node }; + case "fieldAbsent": + return { ...node }; + default: + return reparseExpr(node); + } +} + +/** A shallow copy of a statement that mapStmtChildren returns unchanged, + * one case per kind as in copyChildlessExpr. */ +function copyChildlessStmt(node: IrStmt): IrStmt { + switch (node.kind) { + case "break": + return { ...node }; + case "continue": + return { ...node }; + case "runtimeFence": + return { ...node }; + case "rethrow": + return { ...node }; + default: + return reparseStmt(node); + } +} diff --git a/packages/compiler/src/ir/validate.ts b/packages/compiler/src/ir/validate.ts index 4a961dfca..f75f6f46e 100644 --- a/packages/compiler/src/ir/validate.ts +++ b/packages/compiler/src/ir/validate.ts @@ -4109,9 +4109,15 @@ function validateFunction( if (checkLibEmitterCall(e, sig.result)) return; if ( e.fn === "error.stack" || + e.fn === "error.setStack" || e.fn === "error.cause" || e.fn === "error.hasCause" || e.fn === "error.setCause" || + e.fn === "error.setCode" || + e.fn === "error.setCodeDyn" || + e.fn === "error.assign" || + e.fn === "error.hasKey" || + e.fn === "error.hasCode" || e.fn === "error.defineCause" || e.fn === "error.deleteCause" ) { @@ -4153,7 +4159,7 @@ function validateFunction( if (!ok) err(`libCall ${e.fn} must be a promise of its ${cryptoPromise} value`, e.loc); return true; } - if (e.fn === "events.once") { + if (e.fn === "events.once" || e.fn === "events.onceSignal") { const [recv, , boxer, errorBoxer] = e.args; const arrayT = e.type.kind === "promise" ? e.type.inner : null; const boxes = (b: IrExpr | undefined) => @@ -4164,7 +4170,7 @@ function validateFunction( !boxes(boxer) || !boxes(errorBoxer) ) { - err(`libCall events.once must box its event tuples into the promised array`, e.loc); + err(`libCall ${e.fn} must box its event tuples into the promised array`, e.loc); } return true; } @@ -4426,7 +4432,9 @@ function validateFunction( e.fn === "http.clientOnResponse" || e.fn === "http.requestAgentCb" || e.fn === "https.requestAgentCb" || - e.fn === "https.requestUrlCb" + e.fn === "https.requestUrlCb" || + e.fn === "http.requestDynCb" || + e.fn === "https.requestDynCb" ) { // The response listener: void, no params or exactly (res: httpReq). const cbT = @@ -4437,11 +4445,13 @@ function validateFunction( ? 9 : e.fn === "http.requestUrlCb" || e.fn === "https.requestUrlCb" ? 3 - : e.fn === "http.requestAgentCb" - ? 8 - : e.fn === "https.requestAgentCb" - ? 10 - : 1 + : e.fn === "http.requestDynCb" || e.fn === "https.requestDynCb" + ? 2 + : e.fn === "http.requestAgentCb" + ? 8 + : e.fn === "https.requestAgentCb" + ? 10 + : 1 ]?.type; let ok = cbT?.kind === "func" && cbT.ret.kind === "void" && cbT.params.length <= 1; if (ok && cbT?.kind === "func" && cbT.params.length === 1) { @@ -5223,6 +5233,35 @@ function validateFunction( } return true; } + if (e.fn === "stream.dynNative" || e.fn === "stream.dynNativePipe") { + const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined; + const side = e.args[1]; + const className = + side?.kind === "numLit" ? (side.value === 1 ? "%Readable" : "%Writable") : null; + const ok = + def && + className !== null && + e.args[0]?.type.kind === "dyn" && + def.arms.length === 2 && + def.arms.some((a) => a.kind === "object" && a.className === className) && + def.arms.some((a) => a.kind === "nullT"); + if (!ok) err(`libCall ${e.fn} must return its side's stream class or null`, e.loc); + return true; + } + if (e.fn === "child.stdioStream") { + const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined; + const slot = e.args[1]; + const className = + slot?.kind === "numLit" ? (slot.value === 0 ? "%Writable" : "%Readable") : null; + const ok = + def && + className !== null && + def.arms.length === 2 && + def.arms.some((a) => a.kind === "object" && a.className === className) && + def.arms.some((a) => a.kind === "nullT"); + if (!ok) err(`libCall ${e.fn} must return the stream class of its stdio slot or null`, e.loc); + return true; + } if (e.fn === "child.stdin" || e.fn === "child.stdout" || e.fn === "child.stderr") { const def = e.type.kind === "union" ? unions.get(e.type.unionId) : undefined; const handleKind = e.fn === "child.stdin" ? "childWriter" : "childStream"; @@ -5714,9 +5753,9 @@ function validateFunction( } return true; } - if (e.fn === "readable.pipe") { + if (e.fn === "readable.pipe" || e.fn === "readable.pipeGeneral") { if (!isStreamObject(e.args[1]?.type) || !typeEquals(e.type, e.args[1]!.type)) { - err(`libCall readable.pipe must return its destination's type`, e.loc); + err(`libCall ${e.fn} must return its destination's type`, e.loc); } return true; } @@ -6199,7 +6238,10 @@ function validateFunction( if (expectsArg && (rp.params.length !== 1 || !typeEquals(rp.params[0]!, inner))) { err(`newPromise resolve param must be (${inner.kind}) => void`, e.loc); } - if (!expectsArg && rp.params.length !== 0) { + // A Promise executor types resolve as `(value: never) => + // void` (one parameter over a void inner): the void resolve never + // reads an argument, and a never value cannot be produced. + if (!expectsArg && rp.params.length > 1) { err("newPromise resolve takes no argument", e.loc); } } diff --git a/packages/compiler/src/library/ir-policy.ts b/packages/compiler/src/library/ir-policy.ts index 2d1bc85d4..0c343fa98 100644 --- a/packages/compiler/src/library/ir-policy.ts +++ b/packages/compiler/src/library/ir-policy.ts @@ -54,6 +54,11 @@ const LIB_MODE_REFUSED_PREFIXES: readonly [string, string][] = [ ["webcrypto.", "WebCrypto (crypto.subtle)"], ["fs.watch", "fs.watch"], ["watcher.", "fs.watch"], + // The event-loop metrics sample the loop and its timers (scr_perf_loop.c), + // which library links exclude. + ["perf.loopStart", "performance.eventLoopUtilization"], + ["perf.idleTime", "performance.eventLoopUtilization"], + ["perf.eld", "perf_hooks.monitorEventLoopDelay"], ["test.", "the node:test surface"], ["readable.", "the node:stream surface"], ["writable.", "the node:stream surface"], @@ -158,6 +163,7 @@ export function moduleLibAsyncSurface(mod: IrModule): { surface: string; loc: Sr [features.nodeTest, "the node:test surface"], [features.dynAsync, "the checked-dynamic async surface"], [features.dc, "the diagnostics_channel surface"], + [features.perfLoop, "the perf_hooks event-loop metrics"], ]; for (const [on, surface] of coarse) { if (on) return { surface, loc: entryLoc }; diff --git a/packages/compiler/src/native/cli.ts b/packages/compiler/src/native/cli.ts index 7d00acbc8..a9056a093 100644 --- a/packages/compiler/src/native/cli.ts +++ b/packages/compiler/src/native/cli.ts @@ -89,18 +89,22 @@ async function main(): Promise { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, tls: false, cipher: false, webcrypto: false, + perfLoop: false, tlsCa: false, }, options.optimization ?? "release", diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index 54100a413..a6b4be63c 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -143,12 +143,15 @@ function validFeatures(value: unknown): value is NativeLinkFeatures { "bigint", "searchParams", "qs", + "dynBuiltinClass", + "dynBuiltinStream", "parseArgs", "stream", "net", "http", "http2", "dgram", + "dnsPromises", "watch", "foreignFfi", "nodeTest", @@ -156,6 +159,7 @@ function validFeatures(value: unknown): value is NativeLinkFeatures { "tlsCa", "cipher", "webcrypto", + "perfLoop", ].every((name) => typeof features[name] === "boolean"); } diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index 6ca84af6f..7607034b2 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -75,7 +75,10 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( workers: false, youngNursery: false, eventTarget: false, + wasm: false, dynTier: false, + streamDyn: false, + eventsOnceSignal: false, warningReplace: false, stdioExtended: false, regex: true, @@ -95,12 +98,15 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, @@ -108,6 +114,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")( tlsCa: false, cipher: false, webcrypto: false, + perfLoop: false, nativeFetch: false, netEffective: false, httpEffective: false, diff --git a/packages/compiler/src/native/toolchain.test.ts b/packages/compiler/src/native/toolchain.test.ts index 9afccdc18..0ee434300 100644 --- a/packages/compiler/src/native/toolchain.test.ts +++ b/packages/compiler/src/native/toolchain.test.ts @@ -14,6 +14,7 @@ test("native toolchain installation paths relocate with the manifest", () => { compiler_version: "1.2.3", target: "macos-arm64", ts7: "typescript/tsc", + tsc_ts: "tsc-ts/lib/tsc-ts", llvm_package: "llvm", runtime_pack: "runtime", linker: "tools/linker", @@ -23,7 +24,10 @@ test("native toolchain installation paths relocate with the manifest", () => { try { writeFileSync(path, JSON.stringify(manifest)); const toolchain = loadNativeToolchain(path); - expect(toolchain.ts7Executable).toBe(join(root, "typescript/tsc")); + expect(toolchain.ts7Executable).toBe(join(root, "tsc-ts/lib/tsc-ts")); + expect(loadNativeToolchain(path, { SCRIPTC_CHECKER: "tsgo" }).ts7Executable).toBe( + join(root, "typescript/tsc"), + ); expect(toolchain.helperExecutable).toBe(join(root, "llvm/bin/scriptc-llvm-codegen")); expect(toolchain.runtimePackRoot).toBe(join(root, "runtime")); expect(toolchain.linker).toBe(join(root, "tools/linker")); @@ -39,6 +43,7 @@ test("native toolchain installation paths relocate with the manifest", () => { { ...manifest, schema: "other" }, { ...manifest, target: "other" }, { ...manifest, ts7: "" }, + { ...manifest, tsc_ts: "" }, { ...manifest, linker_args: [1] }, ]) { writeFileSync(path, JSON.stringify(invalid)); @@ -71,6 +76,7 @@ test("native toolchain assets resolve from the physical manifest through package compiler_version: "1.2.3", target: "windows-x64-msvc", ts7: asset("typescript/lib/tsc.exe"), + tsc_ts: asset("tsc-ts/lib/tsc-ts.exe"), llvm_package: asset("llvm"), runtime_pack: asset("runtime"), runtime_packs: [{ target: "windows-x64-msvc", path: asset("runtime") }], @@ -94,6 +100,10 @@ test("native toolchain assets resolve from the physical manifest through package const viaLink = loadNativeToolchain(join(linked, "bin", "scriptc.exe.json")); expect(viaLink).toEqual(direct); expect(viaLink.ts7Executable).toBe(join(assets, "typescript/lib/tsc.exe")); + expect( + loadNativeToolchain(join(linked, "bin", "scriptc.exe.json"), { SCRIPTC_CHECKER: "tsc-ts" }) + .ts7Executable, + ).toBe(join(assets, "tsc-ts/lib/tsc-ts.exe")); expect(viaLink.helperExecutable).toBe(join(assets, "llvm/bin/scriptc-llvm-codegen.exe")); expect( loadNativeToolchain(join(linked, "bin", "scriptc.exe.json"), { @@ -126,6 +136,7 @@ test("cross-target packs can be added to a project after the compiler is install compiler_version: "1.2.3", target: "macos-arm64", ts7: "../lib/typescript/tsc", + tsc_ts: "../lib/tsc-ts/lib/tsc-ts", llvm_package: "../lib/llvm", runtime_pack: "../lib/runtime-darwin-arm64", linker: "clang", @@ -174,6 +185,7 @@ test("versioned GNU targets select Zig while ordinary host builds keep the insta compiler_version: "1.2.3", target: "linux-x64-gnu", ts7: "tsc", + tsc_ts: "tsc-ts", llvm_package: "llvm", runtime_pack: "runtime", linker: "clang", @@ -192,3 +204,41 @@ test("versioned GNU targets select Zig while ordinary host builds keep the insta rmSync(root, { recursive: true, force: true }); } }); + +test("a manifest without tsc_ts takes the host's tsc-ts executable from the installed package", () => { + const root = realpathSync( + mkdtempSync(join(process.platform === "win32" ? tmpdir() : "/tmp", "scriptc-checker-tools-")), + ); + const path = join(root, "toolchain.json"); + const manifest: NativeToolchainManifest = { + schema: "scriptc.native-toolchain.v1", + compiler_version: "1.2.3", + target: "linux-x64-musl", + ts7: "typescript/tsc", + llvm_package: "llvm", + runtime_pack: "runtime", + linker: "tools/linker", + linker_args: ["cc"], + dsymutil: "dsymutil", + }; + try { + writeFileSync(path, JSON.stringify(manifest)); + const project = join(root, "project"); + const platformPackage = join(project, "node_modules/@tsc-ts/linux-x64-musl"); + mkdirSync(join(project, "node_modules/tsc-ts"), { recursive: true }); + writeFileSync(join(project, "node_modules/tsc-ts/package.json"), '{"name":"tsc-ts"}'); + mkdirSync(join(platformPackage, "lib"), { recursive: true }); + writeFileSync(join(platformPackage, "lib/tsc-ts"), ""); + expect(loadNativeToolchain(path, {}, project).ts7Executable).toBe( + join(platformPackage, "lib/tsc-ts"), + ); + expect(loadNativeToolchain(path, { SCRIPTC_CHECKER: "tsgo" }, project).ts7Executable).toBe( + join(root, "typescript/tsc"), + ); + expect(() => loadNativeToolchain(path, {}, root)).toThrow( + "the tsc-ts package is not installed", + ); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/packages/compiler/src/native/toolchain.ts b/packages/compiler/src/native/toolchain.ts index 651d87027..fbcd94251 100644 --- a/packages/compiler/src/native/toolchain.ts +++ b/packages/compiler/src/native/toolchain.ts @@ -6,6 +6,11 @@ import { type NativeTargetSpec, type NativeHelperSpec, } from "../backend/targets.js"; +import { + checkerBackend, + tscTsExecutable, + tscTsPlatformPackage, +} from "../frontend/ts7/checker-backend.js"; export interface NativeToolchain { compilerVersion: string; @@ -34,6 +39,8 @@ export interface NativeToolchainManifest { compiler_version: string; target: string; ts7: string; + /** The tsc-ts executable, the default checker; without it, an installed `tsc-ts` package's. */ + tsc_ts?: string; llvm_package: string; runtime_pack: string; runtime_sources?: string; @@ -106,6 +113,7 @@ export function loadNativeToolchain( } const manifest = raw as NativeToolchainManifest; for (const name of [ + "tsc_ts", "runtime_sources", "declarations", "archiver", @@ -169,7 +177,19 @@ export function loadNativeToolchain( : [...target.defaultLinkerArgs]; return { compilerVersion: manifest.compiler_version, - ts7Executable: pathFrom(root, manifest.ts7), + // tsc-ts, the default checker except on Windows, comes with the distribution; + // a development build takes it from the tsc-ts package installed with the + // compiler or for the working directory. SCRIPTC_CHECKER selects a checker. + ts7Executable: + checkerBackend(env, host.platform) === "tsgo" + ? pathFrom(root, manifest.ts7) + : manifest.tsc_ts !== undefined + ? pathFrom(root, manifest.tsc_ts) + : tscTsExecutable( + [root, cwd], + tscTsPlatformPackage(host.platform, host.architecture, host.name.endsWith("-musl")), + host.platform, + ), target, helper: host.helper, helperPackageRoot, diff --git a/packages/compiler/src/startup-cache.ts b/packages/compiler/src/startup-cache.ts index fedc36fd5..c2f82fdad 100644 --- a/packages/compiler/src/startup-cache.ts +++ b/packages/compiler/src/startup-cache.ts @@ -22,5 +22,6 @@ export function precompiledRuntimePackTarget( ): NativeTargetSpec | null { return nativeCodegenTarget(env); } +export { checkerCacheIdentity } from "./frontend/ts7/checker-backend.js"; export { readCoverageVerdict } from "./coverage/verdict-startup.js"; export { coverageEnvelope, coveragePasses, renderCoverage } from "./coverage/report.js"; diff --git a/packages/compiler/src/wasm/base64.ts b/packages/compiler/src/wasm/base64.ts new file mode 100644 index 000000000..122bcd4ef --- /dev/null +++ b/packages/compiler/src/wasm/base64.ts @@ -0,0 +1,61 @@ +/* Base64 for WebAssembly module bytes: decoding inline modules found in + * source text (discover.ts) and carrying translated modules on the IR + * (IrModule.wasmModules). Plain TypeScript, so the native compiler + * compiles it too. */ + +const BASE64_VALUES = (() => { + const table = new Int16Array(128).fill(-1); + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + for (let i = 0; i < alphabet.length; i++) table[alphabet.charCodeAt(i)] = i; + // base64url spellings + table["-".charCodeAt(0)] = 62; + table["_".charCodeAt(0)] = 63; + return table; +})(); + +/** Decodes standard or URL-safe base64 (padding optional); null when the + * text is not base64. */ +export function decodeBase64(text: string): Uint8Array | null { + let end = text.length; + while (end > 0 && text.charCodeAt(end - 1) === 61) end--; + if (text.length - end > 2 || end % 4 === 1) return null; + const out = new Uint8Array(Math.floor((end * 3) / 4)); + let acc = 0; + let bits = 0; + let j = 0; + for (let i = 0; i < end; i++) { + const c = text.charCodeAt(i); + const v = c < 128 ? BASE64_VALUES[c]! : -1; + if (v < 0) return null; + acc = ((acc << 6) | v) & 0xffffff; + bits += 6; + if (bits >= 8) { + bits -= 8; + out[j++] = (acc >> bits) & 0xff; + } + } + return j === out.length ? out : out.subarray(0, j); +} + +const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** Standard base64 with padding. */ +export function encodeBase64(bytes: Uint8Array): string { + const parts: string[] = []; + let chunk = ""; + for (let i = 0; i < bytes.length; i += 3) { + const a = bytes[i]!; + const b = i + 1 < bytes.length ? bytes[i + 1]! : 0; + const c = i + 2 < bytes.length ? bytes[i + 2]! : 0; + const n = (a << 16) | (b << 8) | c; + chunk += BASE64_ALPHABET[(n >> 18) & 63]! + BASE64_ALPHABET[(n >> 12) & 63]!; + chunk += i + 1 < bytes.length ? BASE64_ALPHABET[(n >> 6) & 63]! : "="; + chunk += i + 2 < bytes.length ? BASE64_ALPHABET[n & 63]! : "="; + if (chunk.length >= 65536) { + parts.push(chunk); + chunk = ""; + } + } + parts.push(chunk); + return parts.join(""); +} diff --git a/packages/compiler/src/wasm/binary.ts b/packages/compiler/src/wasm/binary.ts index 5ddc28cc9..eaedf25af 100644 --- a/packages/compiler/src/wasm/binary.ts +++ b/packages/compiler/src/wasm/binary.ts @@ -20,6 +20,7 @@ * The decoder never evaluates anything and keeps no module contents beyond * the counts and the feature set it reports. It is plain TypeScript that * the native compiler also compiles. */ +import { readMemargFlags } from "./module.js"; /** Proposals the summary can name, in report order. The first group is the * translatable set; the rest are reported as unsupported. */ @@ -1244,19 +1245,9 @@ class FunctionValidator { /** A memarg. A memory other than 0 stops typing (multi-memory). */ memarg(naturalAlign: number): void { const r = this.r; - let align = r.u32(); - let multi = false; - if (align >= 64 && align < 128) { - align -= 64; - multi = true; - } else if (align >= 64) { - r.malformed("malformed memop flags"); - } - let memory = 0; - if (multi) { - memory = r.u32(); - if (memory !== 0) this.otherMemory(memory); - } + const { align, memory } = readMemargFlags(() => r.u32()); + if (align < 0) r.malformed("malformed memop flags"); + if (memory !== 0) this.otherMemory(memory); if (this.m.memory64) r.u64(); else r.u32(); if (!this.typed) return; @@ -1840,9 +1831,9 @@ class FunctionValidator { scanMemarg(): void { const r = this.r; - const align = r.u32(); - if (align >= 128) r.malformed("malformed memop flags"); - if (align >= 64 && r.u32() !== 0) this.m.feature("multi-memory"); + const { align, memory } = readMemargFlags(() => r.u32()); + if (align < 0) r.malformed("malformed memop flags"); + if (memory !== 0) this.m.feature("multi-memory"); if (this.m.memory64) r.u64(); else r.u32(); } diff --git a/packages/compiler/src/wasm/discover.ts b/packages/compiler/src/wasm/discover.ts index 95fc28647..de38bf64f 100644 --- a/packages/compiler/src/wasm/discover.ts +++ b/packages/compiler/src/wasm/discover.ts @@ -6,7 +6,9 @@ * 1. path: a `.wasm` path the compiler resolves from the source text * (`require.resolve`, `createRequire(...).resolve`, `import.meta.resolve`, * `new URL("./x.wasm", import.meta.url)`, and `path.join(__dirname, ...)` - * or `__dirname + "/x.wasm"`); + * or `__dirname + "/x.wasm"`), or a `.so` path spelled the same ways + * whose file is a module (WebAssembly shared libraries, such as + * quickjs-wasi's extensions); * 2. inline: a string literal that decodes as base64 to a module, or an * array literal of its bytes; * 3. package: every `.wasm` file shipped by a reached npm package whose @@ -28,6 +30,9 @@ import { trackedReadFile, } from "../frontend/input-tracker.js"; import { analyzeWasm, isWasmHeader, type WasmModuleSummary } from "./binary.js"; +import { decodeBase64 } from "./base64.js"; + +export { decodeBase64 }; export type WasmSourceKind = "path" | "inline" | "package" | "config"; @@ -56,6 +61,9 @@ export interface WasmDiscoveryInput { configured: readonly string[]; /** require.resolve from `fromFile`, or null when it does not resolve. */ resolveRequest: (fromFile: string, specifier: string) => string | null; + /** Receives each distinct module's bytes once, when it is first found + * (builds translate them). */ + onModule?: (sha256: string, bytes: Uint8Array) => void; } const SCAN_KINDS: ReadonlySet = new Set([ @@ -72,6 +80,9 @@ const BASE64_MAGIC = "AGFzbQ"; const BYTES_MAGIC = /\[\s*(?:0|0x0+)\s*,\s*(?:97|0x61)\s*,\s*(?:115|0x73)\s*,\s*(?:109|0x6d)\s*,/i; /** Package directories deeper than this are not searched for modules. */ const PACKAGE_SCAN_DEPTH = 6; +/** A `.so` path in a string literal: the cheap test before a file with no + * `.wasm` mention is walked for shared-library paths. */ +const SHARED_LIBRARY_PATH = /\.so["'`]/; /** Larger files are reported by size but not read. */ const MAX_MODULE_BYTES = 256 * 1024 * 1024; @@ -110,6 +121,7 @@ class Discovery { }; this.byHash.set(sha256, report); this.order.push(sha256); + this.input.onModule?.(sha256, bytes); } if (report.path === undefined && path !== undefined) report.path = path; addSource(report, source); @@ -132,6 +144,8 @@ class Discovery { return; } if (bytes.length > MAX_MODULE_BYTES) return; + // A shared-library path names a module only when the file is one. + if (source.kind === "path" && !path.endsWith(".wasm") && !isWasmHeader(bytes)) return; this.byPath.set(path, this.addBytes(bytes, path, source)); } @@ -139,13 +153,13 @@ class Discovery { scanFile(sf: ts.SourceFile): void { const text = sf.text; - const paths = text.includes(".wasm"); + const paths = text.includes(".wasm") || SHARED_LIBRARY_PATH.test(text); const base64 = text.includes(BASE64_MAGIC); const bytes = BYTES_MAGIC.test(text); if (!paths && !base64 && !bytes) return; const file = sf.fileName; const lineOf = (node: ts.Node): number => - ts.getLineAndCharacterOfPosition(sf, node.getStart()).line + 1; + ts.getLineAndCharacterOfPosition(sf, ts.tokenStartOfNode(node)).line + 1; ts.walkPreorder( sf, (node) => { @@ -177,7 +191,8 @@ class Discovery { ); } - /** The module path a resolution expression names, when it ends in .wasm. */ + /** The module path a resolution expression names, when it ends in .wasm + * (or .so). */ resolvedPath(sf: ts.SourceFile, node: ts.Node): string | null { const dir = dirname(sf.fileName); if (ts.isCallExpression(node)) { @@ -238,7 +253,7 @@ class Discovery { if (!ts.isStringLiteral(arg) && !ts.isNoSubstitutionTemplateLiteral(arg)) return null; parts.push(arg.text); } - if (!parts[parts.length - 1]!.endsWith(".wasm")) return null; + if (!isModulePath(parts[parts.length - 1]!)) return null; let path = parts[0]!; for (let i = 1; i < parts.length; i++) path = resolve(path, parts[i]!); return path; @@ -307,9 +322,15 @@ function addSource(report: WasmModuleReport, source: WasmSource): void { report.sources.push(source); } +/** A path that may name a module: `.wasm`, or `.so` for WebAssembly shared + * libraries (addFile keeps those only when the file is a module). */ +function isModulePath(text: string): boolean { + return text.endsWith(".wasm") || text.endsWith(".so"); +} + function wasmLiteral(node: ts.Expression): string | null { if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) return null; - return node.text.endsWith(".wasm") ? node.text : null; + return isModulePath(node.text) ? node.text : null; } function isDirname(node: ts.Expression): boolean { @@ -351,40 +372,6 @@ function byteArrayLiteral(node: ts.ArrayLiteralExpression): Uint8Array | null { return out; } -const BASE64_VALUES = (() => { - const table = new Int16Array(128).fill(-1); - const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - for (let i = 0; i < alphabet.length; i++) table[alphabet.charCodeAt(i)] = i; - // base64url spellings - table["-".charCodeAt(0)] = 62; - table["_".charCodeAt(0)] = 63; - return table; -})(); - -/** Decodes standard or URL-safe base64 (padding optional); null when the - * text is not base64. */ -export function decodeBase64(text: string): Uint8Array | null { - let end = text.length; - while (end > 0 && text.charCodeAt(end - 1) === 61) end--; - if (text.length - end > 2 || end % 4 === 1) return null; - const out = new Uint8Array(Math.floor((end * 3) / 4)); - let acc = 0; - let bits = 0; - let j = 0; - for (let i = 0; i < end; i++) { - const c = text.charCodeAt(i); - const v = c < 128 ? BASE64_VALUES[c]! : -1; - if (v < 0) return null; - acc = ((acc << 6) | v) & 0xffffff; - bits += 6; - if (bits >= 8) { - bits -= 8; - out[j++] = (acc >> bits) & 0xff; - } - } - return j === out.length ? out : out.subarray(0, j); -} - /** The root directory of the npm package that contains `file` (the * directory below its innermost node_modules), or null outside one. */ export function packageRootOf(file: string): string | null { diff --git a/packages/compiler/src/wasm/module.ts b/packages/compiler/src/wasm/module.ts new file mode 100644 index 000000000..587a20d81 --- /dev/null +++ b/packages/compiler/src/wasm/module.ts @@ -0,0 +1,676 @@ +/* The structure of a WebAssembly module, for the translator. + * + * binary.ts validates a module and keeps nothing but counts. This parser + * reads the parts translation needs (types, imports, functions, tables, + * memories, globals, exports, the start function, element and data + * segments, function bodies and custom sections) from bytes that binary.ts + * already accepted, so it checks nothing beyond what it must to stay in + * bounds. Constant expressions and function bodies are kept as byte ranges + * that the translator reads itself. + * + * i64 values never become JavaScript numbers: the translator reads their + * LEB128 encodings into two 32-bit halves. Plain TypeScript, so the native + * compiler compiles it too. */ + +export const VAL_I32 = 0x7f; +export const VAL_I64 = 0x7e; +export const VAL_F32 = 0x7d; +export const VAL_F64 = 0x7c; +export const VAL_FUNCREF = 0x70; +export const VAL_EXTERNREF = 0x6f; + +export const KIND_FUNC = 0; +export const KIND_TABLE = 1; +export const KIND_MEMORY = 2; +export const KIND_GLOBAL = 3; + +export interface WasmFuncType { + params: number[]; + results: number[]; +} + +/** A byte range of the module: a constant expression (through its `end` + * opcode) or a payload. */ +export interface WasmRange { + start: number; + end: number; +} + +export interface WasmImport { + module: string; + name: string; + kind: number; + /** Functions: the type index. Tables and globals: the value type. */ + type: number; + min: number; + max: number | null; + mutable: boolean; +} + +export interface WasmTable { + elemType: number; + min: number; + max: number | null; + imported: boolean; + /** A defined table's initializer (reference-types table with an init + * expression), or null for the null reference. */ + init: WasmRange | null; +} + +export interface WasmMemory { + min: number; + max: number | null; + imported: boolean; +} + +export interface WasmGlobal { + type: number; + mutable: boolean; + imported: boolean; + init: WasmRange | null; +} + +export interface WasmExport { + name: string; + kind: number; + index: number; +} + +export interface WasmElem { + mode: "active" | "passive" | "declarative"; + table: number; + offset: WasmRange | null; + elemType: number; + /** One initializer per element: a function index (>= 0), or -1 when the + * element is a constant expression in `exprs`. */ + funcs: number[]; + exprs: (WasmRange | null)[]; +} + +export interface WasmData { + mode: "active" | "passive"; + memory: number; + offset: WasmRange | null; + payload: WasmRange; +} + +export interface WasmCode { + /** Declared locals, expanded (not including parameters). */ + locals: number[]; + body: WasmRange; +} + +export interface WasmCustom { + name: string; + payload: WasmRange; +} + +export interface WasmModule { + bytes: Uint8Array; + types: WasmFuncType[]; + imports: WasmImport[]; + /** The type index of every function, imported functions first. */ + funcTypes: number[]; + importedFuncs: number; + tables: WasmTable[]; + memories: WasmMemory[]; + globals: WasmGlobal[]; + exports: WasmExport[]; + start: number | null; + elems: WasmElem[]; + datas: WasmData[]; + codes: WasmCode[]; + customs: WasmCustom[]; + /** Functions referenced outside a body (exports, element segments, + * ref.func in constant expressions): only these need a function + * reference record. Bodies add their own ref.func targets, which a valid + * module also declares in one of these places. */ + declaredRefs: Set; +} + +/** A byte cursor over a module that binary.ts validated. */ +export class WasmCursor { + bytes: Uint8Array; + pos: number; + constructor(bytes: Uint8Array, pos: number) { + this.bytes = bytes; + this.pos = pos; + } + + u8(): number { + const b = this.bytes[this.pos]; + if (b === undefined) throw new Error("wasm: unexpected end of module"); + this.pos++; + return b; + } + + u32(): number { + let result = 0; + let scale = 1; + for (;;) { + const b = this.u8(); + result += (b & 0x7f) * scale; + if ((b & 0x80) === 0) return result; + scale *= 128; + } + } + + /** A signed LEB128 of at most 33 bits. */ + s33(): number { + let result = 0; + let scale = 1; + for (;;) { + const b = this.u8(); + result += (b & 0x7f) * scale; + scale *= 128; + if ((b & 0x80) === 0) { + if (b & 0x40) result -= scale; + return result; + } + } + } + + /** A signed 32-bit LEB128 as a signed 32-bit number. */ + s32(): number { + return this.s33() | 0; + } + + /** A signed 64-bit LEB128 as its two's-complement halves [lo, hi], each + * an unsigned 32-bit number. */ + s64(): [number, number] { + let lo = 0; + let hi = 0; + let shift = 0; + let b = 0; + do { + b = this.u8(); + const bits = b & 0x7f; + if (shift < 32) { + lo = (lo | (bits << shift)) >>> 0; + if (shift > 25) hi = (hi | (bits >>> (32 - shift))) >>> 0; + } else if (shift < 64) { + hi = (hi | (bits << (shift - 32))) >>> 0; + } + shift += 7; + } while (b & 0x80); + if (shift < 64 && b & 0x40) { + // Sign-extend from bit `shift`. + if (shift < 32) { + lo = (lo | (~0 << shift)) >>> 0; + hi = 0xffffffff; + } else { + hi = (hi | (~0 << (shift - 32))) >>> 0; + } + } + return [lo, hi]; + } + + u32le(): number { + const b = this.bytes; + const p = this.pos; + this.pos += 4; + return (b[p]! | (b[p + 1]! << 8) | (b[p + 2]! << 16) | (b[p + 3]! << 24)) >>> 0; + } + + name(): string { + const length = this.u32(); + const start = this.pos; + this.pos += length; + return decodeUtf8(this.bytes, start, this.pos); + } + + /** Skips a constant expression through its `end` and answers its range. */ + constExpr(): WasmRange { + const start = this.pos; + for (;;) { + const op = this.u8(); + if (op === 0x0b) break; + skipImmediates(this, op); + } + return { start, end: this.pos }; + } + + valType(): number { + return this.u8(); + } + + /** A block type: -1 for the empty type, a value type, or a type index. */ + blockType(): + | { kind: "empty" } + | { kind: "value"; type: number } + | { kind: "index"; index: number } { + const b = this.bytes[this.pos]!; + if (b === 0x40) { + this.pos++; + return { kind: "empty" }; + } + if ( + b === VAL_I32 || + b === VAL_I64 || + b === VAL_F32 || + b === VAL_F64 || + b === VAL_FUNCREF || + b === VAL_EXTERNREF + ) { + this.pos++; + return { kind: "value", type: b }; + } + return { kind: "index", index: this.s33() }; + } +} + +function decodeUtf8(bytes: Uint8Array, start: number, end: number): string { + let out = ""; + let i = start; + while (i < end) { + const b = bytes[i]!; + let cp = b; + let need = 0; + if (b >= 0xf0) { + cp = b & 0x07; + need = 3; + } else if (b >= 0xe0) { + cp = b & 0x0f; + need = 2; + } else if (b >= 0xc0) { + cp = b & 0x1f; + need = 1; + } + for (let k = 1; k <= need; k++) cp = cp * 64 + (bytes[i + k]! & 0x3f); + out += String.fromCodePoint(cp); + i += need + 1; + } + return out; +} + +/** The flags of a memarg (the immediates of loads, stores and atomics): + * the alignment exponent and the memory index. Flags with bit 6 set + * (multi-memory) are followed by an explicit memory index, which may be + * 0; flags of 128 or more are malformed and answer align -1 without + * reading further. The offset follows. Validation (binary.ts), translation + * and skipImmediates all read memargs through this function, so they agree + * on where each instruction ends; `u32` reads the next u32 immediate of + * their own reader. */ +export function readMemargFlags(u32: () => number): { align: number; memory: number } { + const flags = u32(); + if (flags >= 128) return { align: -1, memory: 0 }; + if (flags >= 64) return { align: flags - 64, memory: u32() }; + return { align: flags, memory: 0 }; +} + +/** Reads the immediates of opcode `op` (already consumed). Covers the + * translatable feature level; anything else was refused before + * translation. */ +export function skipImmediates(c: WasmCursor, op: number): void { + switch (op) { + case 0x02: + case 0x03: + case 0x04: + c.blockType(); + return; + case 0x0c: + case 0x0d: + case 0x10: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0xd2: + c.u32(); + return; + case 0x0e: { + const n = c.u32(); + for (let i = 0; i <= n; i++) c.u32(); + return; + } + case 0x11: + c.u32(); + c.u32(); + return; + case 0x1c: { + const n = c.u32(); + for (let i = 0; i < n; i++) c.valType(); + return; + } + case 0x3f: + case 0x40: + // The memory index (a LEB128 zero; V8 accepts long encodings). + c.u32(); + return; + case 0x41: + c.s32(); + return; + case 0x42: + c.s64(); + return; + case 0x43: + c.pos += 4; + return; + case 0x44: + c.pos += 8; + return; + case 0xd0: + c.valType(); + return; + case 0xfc: { + const sub = c.u32(); + switch (sub) { + case 8: + c.u32(); + c.u32(); + return; + case 9: + case 13: + case 15: + case 16: + case 17: + c.u32(); + return; + case 10: + c.u32(); + c.u32(); + return; + case 11: + c.u32(); + return; + case 12: + case 14: + c.u32(); + c.u32(); + return; + default: + return; + } + } + default: + if (op >= 0x28 && op <= 0x3e) { + const flags = readMemargFlags(() => c.u32()); + if (flags.align < 0 || flags.memory !== 0) + throw new Error(`wasm: memarg of memory ${flags.memory} is not supported`); + c.u32(); + } + return; + } +} + +function limits(c: WasmCursor): { min: number; max: number | null } { + const flags = c.u8(); + const min = c.u32(); + const max = flags & 0x01 ? c.u32() : null; + return { min, max }; +} + +/** Parses a module binary.ts reported as valid within the translatable + * feature level. */ +export function parseWasmModule(bytes: Uint8Array): WasmModule { + const m: WasmModule = { + bytes, + types: [], + imports: [], + funcTypes: [], + importedFuncs: 0, + tables: [], + memories: [], + globals: [], + exports: [], + start: null, + elems: [], + datas: [], + codes: [], + customs: [], + declaredRefs: new Set(), + }; + const c = new WasmCursor(bytes, 8); + while (c.pos < bytes.length) { + const id = c.u8(); + const size = c.u32(); + const end = c.pos + size; + switch (id) { + case 0: { + const name = c.name(); + m.customs.push({ name, payload: { start: c.pos, end } }); + break; + } + case 1: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + c.u8(); // 0x60 + const params: number[] = []; + const np = c.u32(); + for (let k = 0; k < np; k++) params.push(c.valType()); + const results: number[] = []; + const nr = c.u32(); + for (let k = 0; k < nr; k++) results.push(c.valType()); + m.types.push({ params, results }); + } + break; + } + case 2: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const module = c.name(); + const name = c.name(); + const kind = c.u8(); + const imp: WasmImport = { + module, + name, + kind, + type: 0, + min: 0, + max: null, + mutable: false, + }; + if (kind === KIND_FUNC) { + imp.type = c.u32(); + m.funcTypes.push(imp.type); + m.importedFuncs++; + } else if (kind === KIND_TABLE) { + imp.type = c.valType(); + const l = limits(c); + imp.min = l.min; + imp.max = l.max; + m.tables.push({ + elemType: imp.type, + min: l.min, + max: l.max, + imported: true, + init: null, + }); + } else if (kind === KIND_MEMORY) { + const l = limits(c); + imp.min = l.min; + imp.max = l.max; + m.memories.push({ min: l.min, max: l.max, imported: true }); + } else { + imp.type = c.valType(); + imp.mutable = c.u8() === 1; + m.globals.push({ type: imp.type, mutable: imp.mutable, imported: true, init: null }); + } + m.imports.push(imp); + } + break; + } + case 3: { + const n = c.u32(); + for (let i = 0; i < n; i++) m.funcTypes.push(c.u32()); + break; + } + case 4: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + let init: WasmRange | null = null; + if (c.bytes[c.pos] === 0x40) { + // table with an initializer: 0x40 0x00 tabletype expr + c.pos += 2; + const elemType = c.valType(); + const l = limits(c); + init = c.constExpr(); + m.tables.push({ elemType, min: l.min, max: l.max, imported: false, init }); + continue; + } + const elemType = c.valType(); + const l = limits(c); + m.tables.push({ elemType, min: l.min, max: l.max, imported: false, init }); + } + break; + } + case 5: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const l = limits(c); + m.memories.push({ min: l.min, max: l.max, imported: false }); + } + break; + } + case 6: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const type = c.valType(); + const mutable = c.u8() === 1; + const init = c.constExpr(); + noteConstRefs(m, init); + m.globals.push({ type, mutable, imported: false, init }); + } + break; + } + case 7: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const name = c.name(); + const kind = c.u8(); + const index = c.u32(); + m.exports.push({ name, kind, index }); + if (kind === KIND_FUNC) m.declaredRefs.add(index); + } + break; + } + case 8: + m.start = c.u32(); + break; + case 9: { + const n = c.u32(); + for (let i = 0; i < n; i++) m.elems.push(elemSegment(m, c)); + break; + } + case 10: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const size = c.u32(); + const bodyEnd = c.pos + size; + const locals: number[] = []; + const groups = c.u32(); + for (let g = 0; g < groups; g++) { + const count = c.u32(); + const type = c.valType(); + for (let k = 0; k < count; k++) locals.push(type); + } + m.codes.push({ locals, body: { start: c.pos, end: bodyEnd } }); + c.pos = bodyEnd; + } + break; + } + case 11: { + const n = c.u32(); + for (let i = 0; i < n; i++) { + const flags = c.u32(); + if (flags === 1) { + const size = c.u32(); + m.datas.push({ + mode: "passive", + memory: 0, + offset: null, + payload: { start: c.pos, end: c.pos + size }, + }); + c.pos += size; + continue; + } + const memory = flags === 2 ? c.u32() : 0; + const offset = c.constExpr(); + const size = c.u32(); + m.datas.push({ + mode: "active", + memory, + offset, + payload: { start: c.pos, end: c.pos + size }, + }); + c.pos += size; + } + break; + } + default: + // 12 (data count) and anything binary.ts accepted without content + // translation needs. + break; + } + c.pos = end; + } + return m; +} + +function elemSegment(m: WasmModule, c: WasmCursor): WasmElem { + const flags = c.u32(); + const passiveOrDeclarative = (flags & 1) !== 0; + const explicitTable = (flags & 2) !== 0; + const usesExprs = (flags & 4) !== 0; + let mode: WasmElem["mode"] = "active"; + if (passiveOrDeclarative) mode = explicitTable ? "declarative" : "passive"; + let table = 0; + let offset: WasmRange | null = null; + if (!passiveOrDeclarative) { + if (explicitTable) table = c.u32(); + offset = c.constExpr(); + } + let elemType = VAL_FUNCREF; + if (passiveOrDeclarative || explicitTable) { + // elemkind (0x00 = funcref) or a reference type + const b = c.u8(); + elemType = usesExprs ? b : VAL_FUNCREF; + } + const n = c.u32(); + const funcs: number[] = []; + const exprs: (WasmRange | null)[] = []; + for (let i = 0; i < n; i++) { + if (usesExprs) { + const expr = c.constExpr(); + const direct = refFuncOf(m.bytes, expr); + if (direct !== null) { + funcs.push(direct); + exprs.push(null); + m.declaredRefs.add(direct); + } else { + noteConstRefs(m, expr); + funcs.push(-1); + exprs.push(expr); + } + } else { + const f = c.u32(); + funcs.push(f); + exprs.push(null); + m.declaredRefs.add(f); + } + } + return { mode, table, offset, elemType, funcs, exprs }; +} + +/** The function of a `ref.func f; end` expression, or null. */ +function refFuncOf(bytes: Uint8Array, expr: WasmRange): number | null { + if (bytes[expr.start] !== 0xd2) return null; + const c = new WasmCursor(bytes, expr.start + 1); + const f = c.u32(); + return c.pos === expr.end - 1 && bytes[c.pos] === 0x0b ? f : null; +} + +function noteConstRefs(m: WasmModule, expr: WasmRange): void { + const c = new WasmCursor(m.bytes, expr.start); + while (c.pos < expr.end) { + const op = c.u8(); + if (op === 0xd2) { + m.declaredRefs.add(c.u32()); + continue; + } + skipImmediates(c, op); + } +} diff --git a/packages/compiler/src/wasm/translate.test.ts b/packages/compiler/src/wasm/translate.test.ts new file mode 100644 index 000000000..86ba042ef --- /dev/null +++ b/packages/compiler/src/wasm/translate.test.ts @@ -0,0 +1,247 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, test } from "vitest"; +import { analyzeWasm } from "./binary.js"; +import { decodeBase64, encodeBase64 } from "./base64.js"; +import { WasmCursor, parseWasmModule } from "./module.js"; +import { + signatureHash, + stackCheckedFunctions, + translateWasmModule, + wasmSymbolPrefix, +} from "./translate.js"; + +const SHA = new Uint8Array(32).fill(0xab); + +/** (module (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add) + * (memory (export "mem") 1) (data (i32.const 8) "hi")) */ +const ADD = new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 7, 1, 96, 2, 127, 127, 1, 127, 3, 2, 1, 0, 5, 3, 1, 0, 1, 7, 13, + 2, 3, 97, 100, 100, 0, 0, 3, 109, 101, 109, 2, 0, 10, 9, 1, 7, 0, 32, 0, 32, 1, 106, 11, 11, 8, 1, + 0, 65, 8, 11, 2, 104, 105, +]); + +/** Every top-level line must be one the native helper's text partitioner + * accepts (native/llvm-codegen/src/partition.cpp), so translated modules + * keep the program on its concurrent parsing path. */ +function topLevelLines(text: string[]): string[] { + const lines: string[] = []; + for (const part of text) { + let inFunction = false; + for (const line of part.split("\n")) { + if (inFunction) { + if (line === "}") inFunction = false; + continue; + } + if (line.startsWith("define ")) inFunction = true; + lines.push(line); + } + } + return lines; +} + +describe("translateWasmModule", () => { + test("emits functions, thunks, data and the descriptor in the helper's grammar", () => { + const out = translateWasmModule(ADD, SHA, { boundsChecks: false }); + const prefix = wasmSymbolPrefix(SHA); + expect(prefix).toBe("scr_wasm_abababababababab"); + expect(out.descriptor).toBe(`@${prefix}_desc`); + const text = out.text.join("\n"); + expect(text).toContain(`define internal i32 @${prefix}_f0(ptr %inst, i32 %p0, i32 %p1)`); + expect(text).toContain(`define internal void @${prefix}_x0(ptr %inst, ptr %a, ptr %r)`); + expect(text).toContain(`c"hi"`); + expect(text).toContain(`"disable-tail-calls"="true"`); + for (const line of topLevelLines(out.text)) { + expect( + line === "" || /^(define internal |@[\w.$-]+ = (private|internal) )/.test(line), + line, + ).toBe(true); + } + for (const decl of out.decls) expect(decl).toMatch(/^declare /); + }); + + test("keeps custom sections only when asked, and gives unexported table functions call thunks", () => { + // (module (type $t (func (param i32) (result i32))) (table 2 funcref) + // (elem (i32.const 0) $hidden $shown) (func $hidden (type $t) local.get 0) + // (func $shown (export "shown") (type $t) local.get 0)), plus a custom + // section "meta" with "hi". + const refs = new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 6, 1, 96, 1, 127, 1, 127, 3, 3, 2, 0, 0, 4, 4, 1, 112, 0, 2, + 7, 9, 1, 5, 115, 104, 111, 119, 110, 0, 1, 9, 8, 1, 0, 65, 0, 11, 2, 0, 1, 10, 11, 2, 4, 0, + 32, 0, 11, 4, 0, 32, 0, 11, 0, 7, 4, 109, 101, 116, 97, 104, 105, + ]); + const prefix = wasmSymbolPrefix(SHA); + const plain = translateWasmModule(refs, SHA, { boundsChecks: false }).text.join("\n"); + expect(plain).toContain( + `@${prefix}_customs = private constant [0 x { ptr, i64, ptr, i64 }] []`, + ); + expect(plain).not.toContain(`c"hi"`); + const kept = translateWasmModule(refs, SHA, { boundsChecks: false, customSections: true }); + const text = kept.text.join("\n"); + expect(text).toContain(`@${prefix}_c0 = private unnamed_addr constant [2 x i8] c"hi", align 1`); + expect(text).toContain(`@${prefix}_customs = private constant [1 x { ptr, i64, ptr, i64 }]`); + // One call thunk for the signature, used by the unexported function + // only (the exported one has its export thunk). + expect(text).toContain( + `define internal void @${prefix}_k0(ptr %code, ptr %inst, ptr %a, ptr %r)`, + ); + expect(text).toContain(`%res = call i32 %code(ptr %inst, i32 `); + expect(text).toContain( + `@${prefix}_calls = private constant [2 x ptr] [ptr @${prefix}_k0, ptr null]`, + ); + for (const line of topLevelLines(kept.text)) { + expect( + line === "" || /^(define internal |@[\w.$-]+ = (private|internal) )/.test(line), + line, + ).toBe(true); + } + }); + + test("checks memory accesses explicitly only when asked", () => { + const guarded = translateWasmModule(ADD, SHA, { boundsChecks: false }).text.join("\n"); + const checked = translateWasmModule(ADD, SHA, { boundsChecks: true }).text.join("\n"); + expect(guarded).not.toContain("%msize"); + expect(checked).toContain("%msize"); + }); + + test("reads a memarg with an explicit memory index as one immediate", () => { + // (func (result i32) (i32.load 0 offset=8 (i32.const 0))) with memory 1: + // memarg flags 0x42 (alignment 2, memory index present), memory 0, + // offset 8, then `end`. + const bytes = new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 127, 3, 2, 1, 0, 5, 3, 1, 0, 1, 10, 10, 1, 8, + 0, 65, 0, 40, 66, 0, 8, 11, + ]); + expect(analyzeWasm(bytes).status).toBe("valid"); + const text = translateWasmModule(bytes, SHA, { boundsChecks: false }).text.join("\n"); + expect(text).toContain("add i64 %v1, 8"); + expect(text).toContain("load i32, ptr %v3"); + }); + + test("translates every valid module of the WebAssembly spec test suite", () => { + const cases = readFileSync(join(import.meta.dirname, "../../test/wasm-spec-cases.txt"), "utf8") + .split("\n") + .filter((line) => line !== "" && !line.startsWith("#")); + let translated = 0; + for (const line of cases) { + const [, , where, b64] = line.split(" "); + const bytes = decodeBase64(b64!)!; + if (analyzeWasm(bytes).status !== "valid") continue; + try { + translateWasmModule(bytes, SHA, { boundsChecks: false }); + } catch (error) { + throw new Error(`${where}: ${error instanceof Error ? error.message : String(error)}`); + } + translated++; + } + expect(translated).toBeGreaterThan(800); + }); +}); + +/** A module from its sections: `[id, ...payload]`, each payload under 128 + * bytes. */ +function wasmModule(...sections: number[][]): Uint8Array { + const out = [0, 97, 115, 109, 1, 0, 0, 0]; + for (const [id, ...payload] of sections) out.push(id!, payload.length, ...payload); + return new Uint8Array(out); +} + +/** (module (memory 1) (func (export "f") (param i32) (result i32) + * local.get 0 i32.load )) with the memarg's bytes as given. */ +function loadModule(memarg: number[]): Uint8Array { + const body = [0, 0x20, 0, 0x28, ...memarg, 0x0b]; + return wasmModule( + [1, 1, 0x60, 1, 0x7f, 1, 0x7f], + [3, 1, 0], + [5, 1, 0, 1], + [7, 1, 1, 0x66, 0, 0], + [10, 1, body.length, ...body], + ); +} + +describe("memargs and operand types", () => { + test("a memarg naming memory 0 explicitly translates as the plain form", () => { + // Flags 0x42: alignment 2 with bit 6 set, then memory index 0 and + // offset 4. Validation and translation read the same three immediates. + // Only the descriptor (which records the module's byte length) differs. + const code = (memarg: number[]): string[] => + translateWasmModule(loadModule(memarg), SHA, { boundsChecks: true }) + .text.join("\n") + .split("\n") + .filter((line) => !line.startsWith(`@${wasmSymbolPrefix(SHA)}_desc =`)); + expect(code([0x42, 0, 4])).toEqual(code([0x02, 4])); + }); + + test("a memarg naming another memory is refused", () => { + expect(() => + translateWasmModule(loadModule([0x42, 1, 4]), SHA, { boundsChecks: true }), + ).toThrow(/memory 1/); + }); + + test("local.tee of an i32 into an externref local is refused", () => { + // (func (local externref) i32.const 1 local.tee 0 drop): the operand + // never reaches a reference slot. + const body = [1, 1, 0x6f, 0x41, 1, 0x22, 0, 0x1a, 0x0b]; + const bytes = wasmModule([1, 1, 0x60, 0, 0], [3, 1, 0], [10, 1, body.length, ...body]); + expect(() => translateWasmModule(bytes, SHA, { boundsChecks: false })).toThrow( + /local\.tee operand type does not match/, + ); + }); +}); + +describe("module parsing", () => { + test("reads the parts translation needs", () => { + const m = parseWasmModule(ADD); + expect(m.types).toEqual([{ params: [0x7f, 0x7f], results: [0x7f] }]); + expect(m.exports.map((e) => e.name)).toEqual(["add", "mem"]); + expect(m.memories).toEqual([{ min: 1, max: null, imported: false }]); + expect(m.datas[0]!.mode).toBe("active"); + expect([...m.declaredRefs]).toEqual([0]); + }); + + test("decodes signed 64-bit LEB128 into two's-complement halves", () => { + const halves = (bytes: number[]): [number, number] => + new WasmCursor(new Uint8Array(bytes), 0).s64(); + expect(halves([0x7f])).toEqual([0xffffffff, 0xffffffff]); + expect(halves([0x80, 0x80, 0x80, 0x80, 0x10])).toEqual([0, 1]); + expect(halves([0xff, 0xff, 0xff, 0xff, 0x0f])).toEqual([0xffffffff, 0]); + expect(halves([0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f])).toEqual([ + 0, 0x80000000, + ]); + expect(halves([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00])).toEqual([ + 0xffffffff, 0x7fffffff, + ]); + }); +}); + +describe("stack checks", () => { + test("only functions through which the stack can grow without bound check it", () => { + // f0 <-> f1 recurse mutually, f2 recurses on itself, f3 calls f0 once. + const calls = new Uint8Array([ + 0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 5, 4, 0, 0, 0, 0, 10, 21, 4, 4, 0, 16, 1, + 11, 4, 0, 16, 0, 11, 4, 0, 16, 2, 11, 4, 0, 16, 0, 11, + ]); + expect([...stackCheckedFunctions(parseWasmModule(calls))].sort()).toEqual([0, 1, 2]); + expect(stackCheckedFunctions(parseWasmModule(ADD)).size).toBe(0); + // Only code without the fault handler checks explicitly. + const checked = translateWasmModule(calls, SHA, { boundsChecks: true }).text.join("\n"); + expect(checked.split("@scr_wasm_stack_limit").length - 1).toBe(3); + const guarded = translateWasmModule(calls, SHA, { boundsChecks: false }).text.join("\n"); + expect(guarded).not.toContain("@scr_wasm_stack_limit"); + }); +}); + +describe("helpers", () => { + test("equal signatures share their identity across modules", () => { + expect(signatureHash("ii:i")).toBe(signatureHash("ii:i")); + expect(signatureHash("ii:i")).not.toBe(signatureHash("i:i")); + expect(signatureHash(":")).toMatch(/^u0x[0-9a-f]{16}$/); + }); + + test("base64 round-trips module bytes", () => { + expect(encodeBase64(ADD)).toBe(Buffer.from(ADD).toString("base64")); + expect(decodeBase64(encodeBase64(ADD))).toEqual(ADD); + expect(encodeBase64(new Uint8Array([1]))).toBe("AQ=="); + expect(encodeBase64(new Uint8Array([1, 2]))).toBe("AQI="); + }); +}); diff --git a/packages/compiler/src/wasm/translate.ts b/packages/compiler/src/wasm/translate.ts new file mode 100644 index 000000000..4c02499ca --- /dev/null +++ b/packages/compiler/src/wasm/translate.ts @@ -0,0 +1,2296 @@ +/* WebAssembly to LLVM IR. + * + * Translates one validated module (binary.ts accepted it within the + * translatable feature level) to LLVM assembly that the program's emitter + * appends to its own module, so the native helper optimizes, partitions and + * caches it with the program. The runtime (scr_wasm.c) implements the + * JavaScript API over the descriptor this emits; scr_wasm_abi.h is the + * contract both sides follow. + * + * Shape of the output: + * - one internal function per wasm function, taking the instance as its + * first parameter (`ptr %inst`) and the wasm values directly (i32, i64, + * float, double; references are pointers). Several results return as a + * literal struct. An imported function is a stub that packs its + * arguments and calls scr_wasm_import_call. + * - one thunk per exported function, `void (ptr inst, ptr args, ptr + * results)` over 64-bit slots, which the runtime calls under its trap + * guard; + * - an init function (globals, table initializers, active element and + * data segments); the descriptor names the start function; + * - constant data (segments, names, signatures) and the descriptor. + * + * The instance layout (scr_wasm_abi.h): host at 0, memory base at 8, + * memory object at 16, function reference records at 24, then one pointer + * per table and one cell pointer per global. + * + * Memory accesses compute `base + zext(address) + offset` in 64 bits. With + * guard pages (64-bit hosts) the runtime reserves enough address space that + * every such address past the memory faults, so accesses are unchecked and + * the base is loaded once per function (the memory never moves). With + * explicit bounds checks (sanitizer builds and hosts without the fault + * handler) the base and byte length live in function-local slots that are + * reloaded after every call and memory.grow. + * + * Locals, block results and loop parameters are allocas; LLVM promotes them + * to SSA values. Traps call scr_wasm_trap, which never returns. Plain + * TypeScript without bigint, so the native compiler compiles it too. */ +import { + KIND_FUNC, + KIND_GLOBAL, + KIND_MEMORY, + KIND_TABLE, + VAL_EXTERNREF, + VAL_F32, + VAL_F64, + VAL_FUNCREF, + VAL_I32, + VAL_I64, + WasmCursor, + parseWasmModule, + readMemargFlags, + skipImmediates, + type WasmFuncType, + type WasmModule, + type WasmRange, +} from "./module.js"; + +/** Trap codes (scr_wasm_abi.h SCR_WASM_TRAP_*). */ +export const TRAP_UNREACHABLE = 1; +export const TRAP_MEMORY_OOB = 2; +export const TRAP_DIV_BY_ZERO = 3; +export const TRAP_DIV_UNREPRESENTABLE = 4; +export const TRAP_REM_BY_ZERO = 5; +export const TRAP_FLOAT_UNREPRESENTABLE = 6; +export const TRAP_TABLE_OOB = 7; +export const TRAP_SIGNATURE = 8; +export const TRAP_NULL = 9; +/** scr_wasm_abi.h SCR_WASM_TRAP_EXHAUSTION. */ +export const TRAP_EXHAUSTION = 100; + +/** The descriptor ABI version both sides agree on. */ +export const WASM_ABI_VERSION = 2; + +/** Instance slot offsets (bytes). */ +const OFF_MEMBASE = 8; +const OFF_MEMORY = 16; +const OFF_FUNCREFS = 24; +const OFF_TABLES = 32; +/** sizeof(ScrWasmFuncRef). */ +const FUNCREF_SIZE = 24; + +export interface WasmTranslateOptions { + /** Explicit bounds checks instead of guard pages. */ + boundsChecks: boolean; + /** Keep the custom sections, for WebAssembly.Module.customSections. */ + customSections?: boolean; +} + +export interface WasmTranslation { + /** External declarations, one line each. */ + decls: string[]; + /** Definitions in the emitter's top-level grammar. */ + text: string[]; + /** The descriptor's symbol (internal). */ + descriptor: string; +} + +/** A construct the translator does not support in an otherwise valid module. */ +export class WasmTranslateError extends Error {} + +/** Attributes of translated functions. Recursion must exhaust the stack + * as it does in V8, so LLVM may not turn self-recursion into loops (tail + * recursion elimination, including its accumulator form, which would make + * `f(x) + 1` recursion loop forever) or calls into jumps. */ +const FUNC_ATTRS = `nounwind "disable-tail-calls"="true"`; + +const RUNTIME_DECLS = [ + "declare void @scr_wasm_trap(i32) noreturn nounwind cold", + "declare void @scr_wasm_import_call(ptr, i32, ptr, ptr)", + "declare i32 @scr_wasm_memory_grow(ptr, i32)", + "declare void @scr_wasm_memory_fill(ptr, i32, i32, i32)", + "declare void @scr_wasm_memory_copy(ptr, i32, i32, i32)", + "declare void @scr_wasm_memory_init(ptr, i32, i32, i32, i32)", + "declare void @scr_wasm_data_drop(ptr, i32)", + "declare ptr @scr_wasm_table_get(ptr, i32, i32)", + "declare void @scr_wasm_table_set(ptr, i32, i32, ptr)", + "declare i32 @scr_wasm_table_grow(ptr, i32, ptr, i32)", + "declare void @scr_wasm_table_fill(ptr, i32, i32, ptr, i32)", + "declare void @scr_wasm_table_copy(ptr, i32, i32, i32, i32, i32)", + "declare void @scr_wasm_table_init(ptr, i32, i32, i32, i32, i32)", + "declare void @scr_wasm_elem_drop(ptr, i32)", + "declare void @scr_wasm_global_set_ref(ptr, ptr)", +]; + +function llType(t: number): string { + switch (t) { + case VAL_I32: + return "i32"; + case VAL_I64: + return "i64"; + case VAL_F32: + return "float"; + case VAL_F64: + return "double"; + case VAL_FUNCREF: + case VAL_EXTERNREF: + return "ptr"; + default: + throw new WasmTranslateError(`value type 0x${t.toString(16)} is not supported`); + } +} + +function zeroOf(t: number): string { + switch (t) { + case VAL_I32: + case VAL_I64: + return "0"; + case VAL_F32: + case VAL_F64: + return "0.0"; + default: + return "null"; + } +} + +/** The runtime's signature letter of a value type. */ +function sigChar(t: number): string { + switch (t) { + case VAL_I32: + return "i"; + case VAL_I64: + return "I"; + case VAL_F32: + return "f"; + case VAL_F64: + return "F"; + case VAL_FUNCREF: + return "r"; + case VAL_EXTERNREF: + return "x"; + default: + throw new WasmTranslateError(`value type 0x${t.toString(16)} is not supported`); + } +} + +function sigString(ft: WasmFuncType): string { + let s = ""; + for (const p of ft.params) s += sigChar(p); + s += ":"; + for (const r of ft.results) s += sigChar(r); + return s; +} + +function retType(results: readonly number[]): string { + if (results.length === 0) return "void"; + if (results.length === 1) return llType(results[0]!); + return `{ ${results.map(llType).join(", ")} }`; +} + +function hex8(n: number): string { + return (n >>> 0).toString(16).padStart(8, "0"); +} + +/** An i64 constant from its halves, in LLVM's unsigned hexadecimal form. */ +function i64Lit(lo: number, hi: number): string { + return `u0x${hex8(hi)}${hex8(lo)}`; +} + +/** The canonical signature identity call_indirect compares: two + * independent 32-bit FNV-1a hashes of the signature text. Equal signatures + * in different modules get the same identity. */ +export function signatureHash(sig: string): string { + let a = 0x811c9dc5; + let b = 0x01000193 ^ 0x5bd1e995; + for (let i = 0; i < sig.length; i++) { + const c = sig.charCodeAt(i); + a = Math.imul(a ^ c, 0x01000193); + b = Math.imul(b ^ c, 0x01000193) ^ (b >>> 13); + } + return i64Lit(a >>> 0, b >>> 0); +} + +/** UTF-8 bytes of a string. */ +function utf8Bytes(text: string): number[] { + const out: number[] = []; + for (let i = 0; i < text.length; i++) { + const cp = text.codePointAt(i)!; + if (cp > 0xffff) i++; + if (cp < 0x80) out.push(cp); + else if (cp < 0x800) out.push(0xc0 | (cp >> 6), 0x80 | (cp & 0x3f)); + else if (cp < 0x10000) + out.push(0xe0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f)); + else { + out.push( + 0xf0 | (cp >> 18), + 0x80 | ((cp >> 12) & 0x3f), + 0x80 | ((cp >> 6) & 0x3f), + 0x80 | (cp & 0x3f), + ); + } + } + return out; +} + +/** An LLVM c"..." string body for bytes. */ +function cBytes(bytes: Uint8Array, start: number, end: number): string { + let s = ""; + for (let i = start; i < end; i++) { + const b = bytes[i]!; + if (b >= 0x20 && b < 0x7f && b !== 0x22 && b !== 0x5c) s += String.fromCharCode(b); + else s += "\\" + (b < 16 ? "0" : "") + b.toString(16).toUpperCase(); + } + return s; +} + +interface StackValue { + v: string; + t: number; + /** The line of the load that produced the value (this.lines). */ + load?: number; +} + +interface Frame { + kind: "block" | "loop" | "if" | "func"; + params: number[]; + results: number[]; + /** Branch target: the loop header, or the end label. */ + target: string; + end: string; + /** Allocas the values of a branch to this frame are stored in: the loop + * parameters for a loop, the results otherwise. */ + branchSlots: string[]; + /** Allocas of the results at the frame's end (loops: their own). */ + resultSlots: string[]; + height: number; + /** An if whose else branch has not started. */ + elseLabel: string | null; + /** The parameter values of an if, for its implicit else. */ + ifParams: StackValue[]; + /** The start of the frame was reachable. */ + live: boolean; +} + +class Lines { + lines: string[] = []; + add(line: string): void { + this.lines.push(line); + } +} + +/** One module's translation. */ +class ModuleTranslator { + m: WasmModule; + p: string; + opts: WasmTranslateOptions; + text: string[] = []; + /** Functions that need a reference record. */ + refs: Set; + /** Functions whose entry checks the stack (stackCheckedFunctions). */ + stackChecked: Set; + typeHashes: string[]; + memoryCount: number; + + constructor(m: WasmModule, prefix: string, opts: WasmTranslateOptions) { + this.m = m; + this.p = prefix; + this.opts = opts; + this.refs = new Set(m.declaredRefs); + this.typeHashes = m.types.map((t) => signatureHash(sigString(t))); + this.memoryCount = m.memories.length; + if (m.memories.length > 1) throw new WasmTranslateError("multiple memories are not supported"); + this.stackChecked = stackCheckedFunctions(m); + } + + fn(index: number): string { + return `@${this.p}_f${index}`; + } + + funcType(index: number): WasmFuncType { + return this.m.types[this.m.funcTypes[index]!]!; + } + + globalSlot(index: number): number { + return OFF_TABLES + 8 * (this.m.tables.length + index); + } + + tableSlot(index: number): number { + return OFF_TABLES + 8 * index; + } + + signature(ft: WasmFuncType, name: string): string { + const params = ["ptr %inst"]; + for (let i = 0; i < ft.params.length; i++) params.push(`${llType(ft.params[i]!)} %p${i}`); + return `${retType(ft.results)} ${name}(${params.join(", ")})`; + } + + translate(): void { + const m = this.m; + for (let i = 0; i < m.importedFuncs; i++) this.importStub(i); + for (let i = 0; i < m.codes.length; i++) { + const index = m.importedFuncs + i; + const f = new FunctionTranslator(this, index); + this.text.push(...f.translate()); + } + } + + importStub(index: number): void { + const ft = this.funcType(index); + const out: string[] = []; + out.push(`define internal ${this.signature(ft, this.fn(index))} nounwind {`); + out.push(`entry:`); + const np = Math.max(1, ft.params.length); + const nr = Math.max(1, ft.results.length); + out.push(` %args = alloca [${np} x i64], align 8`); + out.push(` %res = alloca [${nr} x i64], align 8`); + for (let i = 0; i < ft.params.length; i++) { + const slot = toSlot(out, ft.params[i]!, `%p${i}`, `a${i}`); + out.push(` %ap${i} = getelementptr i64, ptr %args, i64 ${i}`); + out.push(` store i64 ${slot}, ptr %ap${i}`); + } + out.push(` %host = load ptr, ptr %inst`); + out.push(` call void @scr_wasm_import_call(ptr %host, i32 ${index}, ptr %args, ptr %res)`); + emitSlotReturn(out, ft.results, "%res"); + out.push(`}`); + this.text.push(out.join("\n")); + } + + /** The thunk the runtime calls for exported function `index`. */ + exportThunk(index: number): string { + const name = `@${this.p}_x${index}`; + const ft = this.funcType(index); + const out: string[] = []; + out.push(`define internal void ${name}(ptr %inst, ptr %a, ptr %r) nounwind {`); + out.push(`entry:`); + const args: string[] = ["ptr %inst"]; + for (let i = 0; i < ft.params.length; i++) { + out.push(` %ap${i} = getelementptr i64, ptr %a, i64 ${i}`); + out.push(` %as${i} = load i64, ptr %ap${i}`); + args.push(`${llType(ft.params[i]!)} ${fromSlot(out, ft.params[i]!, `%as${i}`, `a${i}`)}`); + } + const ret = retType(ft.results); + if (ft.results.length === 0) { + out.push(` call void ${this.fn(index)}(${args.join(", ")})`); + } else { + out.push(` %res = call ${ret} ${this.fn(index)}(${args.join(", ")})`); + for (let i = 0; i < ft.results.length; i++) { + let v = "%res"; + if (ft.results.length > 1) { + out.push(` %rv${i} = extractvalue ${ret} %res, ${i}`); + v = `%rv${i}`; + } + const slot = toSlot(out, ft.results[i]!, v, `r${i}`); + out.push(` %rp${i} = getelementptr i64, ptr %r, i64 ${i}`); + out.push(` store i64 ${slot}, ptr %rp${i}`); + } + } + out.push(` ret void`); + out.push(`}`); + this.text.push(out.join("\n")); + return name; + } + + /** A call thunk for signature `type`: `(code, inst, args, results)` + * calls `code` (a function of that signature) with the arguments in + * 64-bit slots. The runtime calls a function reference that reaches + * JavaScript through it when the module does not export the function. */ + callThunk(type: number): string { + const name = `@${this.p}_k${type}`; + const ft = this.m.types[type]!; + const out: string[] = []; + out.push(`define internal void ${name}(ptr %code, ptr %inst, ptr %a, ptr %r) nounwind {`); + out.push(`entry:`); + const args: string[] = ["ptr %inst"]; + for (let i = 0; i < ft.params.length; i++) { + out.push(` %ap${i} = getelementptr i64, ptr %a, i64 ${i}`); + out.push(` %as${i} = load i64, ptr %ap${i}`); + args.push(`${llType(ft.params[i]!)} ${fromSlot(out, ft.params[i]!, `%as${i}`, `a${i}`)}`); + } + const ret = retType(ft.results); + if (ft.results.length === 0) { + out.push(` call void %code(${args.join(", ")})`); + } else { + out.push(` %res = call ${ret} %code(${args.join(", ")})`); + for (let i = 0; i < ft.results.length; i++) { + let v = "%res"; + if (ft.results.length > 1) { + out.push(` %rv${i} = extractvalue ${ret} %res, ${i}`); + v = `%rv${i}`; + } + const slot = toSlot(out, ft.results[i]!, v, `r${i}`); + out.push(` %rp${i} = getelementptr i64, ptr %r, i64 ${i}`); + out.push(` store i64 ${slot}, ptr %rp${i}`); + } + } + out.push(` ret void`); + out.push(`}`); + this.text.push(out.join("\n")); + return name; + } + + /** Emits the init function: globals, table initializers and segments, + * in the specification's order. The runtime calls the start function + * (which takes and returns nothing) separately. */ + initFunction(): string { + const m = this.m; + const name = `@${this.p}_init`; + const f = new FunctionTranslator(this, -1); + const body = f.lines; + // Globals. + for (let g = 0; g < m.globals.length; g++) { + const global = m.globals[g]!; + if (global.imported || global.init === null) continue; + const v = f.constExpr(global.init); + const cell = f.loadPtrSlot(this.globalSlot(g)); + if (global.type === VAL_EXTERNREF) { + // A defined externref global starts as null or another global's + // value; the runtime's setter retains it. + body.add(` call void @scr_wasm_global_set_ref(ptr ${cell}, ptr ${v})`); + } else { + body.add(` store ${llType(global.type)} ${v}, ptr ${cell}`); + } + } + // Defined tables with an initializer. + for (let t = 0; t < m.tables.length; t++) { + const table = m.tables[t]!; + if (table.imported || table.init === null) continue; + const v = f.constExpr(table.init); + body.add( + ` call void @scr_wasm_table_fill(ptr %inst, i32 ${t}, i32 0, ptr ${v}, i32 ${table.min})`, + ); + } + // Element segments: active ones initialize their table and drop; + // declarative ones only drop. + for (let s = 0; s < m.elems.length; s++) { + const seg = m.elems[s]!; + if (seg.mode === "active") { + const offset = f.constExpr(seg.offset!); + body.add( + ` call void @scr_wasm_table_init(ptr %inst, i32 ${seg.table}, i32 ${s}, i32 ${offset}, i32 0, i32 ${seg.funcs.length})`, + ); + } + if (seg.mode !== "passive") body.add(` call void @scr_wasm_elem_drop(ptr %inst, i32 ${s})`); + } + for (let s = 0; s < m.datas.length; s++) { + const seg = m.datas[s]!; + if (seg.mode !== "active") continue; + const offset = f.constExpr(seg.offset!); + const length = seg.payload.end - seg.payload.start; + body.add( + ` call void @scr_wasm_memory_init(ptr %inst, i32 ${s}, i32 ${offset}, i32 0, i32 ${length})`, + ); + body.add(` call void @scr_wasm_data_drop(ptr %inst, i32 ${s})`); + } + body.add(` ret void`); + const out = [`define internal void ${name}(ptr %inst) nounwind {`, `entry:`]; + out.push(...f.entry.lines); + out.push(...body.lines); + out.push(`}`); + this.text.push(out.join("\n")); + return name; + } + + /** Constant data and the descriptor. */ + descriptor(sha256: Uint8Array): string { + const m = this.m; + const p = this.p; + const lines: string[] = []; + // First, so that every function its constant expressions reference has + // a record in the tables below. + const init = this.initFunction(); + let strings = 0; + const cstr = (text: readonly number[]): string => { + const sym = `@${p}_s${strings++}`; + const bytes = new Uint8Array(text); + lines.push( + `${sym} = private unnamed_addr constant [${bytes.length + 1} x i8] c"${cBytes(bytes, 0, bytes.length)}\\00"`, + ); + return sym; + }; + const textStr = (text: string): { sym: string; len: number } => { + const bytes = utf8Bytes(text); + return { sym: cstr(bytes), len: bytes.length }; + }; + // Imports. + const importRows: string[] = []; + let funcImport = 0; + let tableImport = 0; + let memoryImport = 0; + let globalImport = 0; + for (const imp of m.imports) { + const mod = textStr(imp.module); + const name = textStr(imp.name); + let index = 0; + let type = ""; + if (imp.kind === KIND_FUNC) { + index = funcImport++; + type = sigString(m.types[imp.type]!); + } else if (imp.kind === KIND_TABLE) { + index = tableImport++; + type = sigChar(imp.type); + } else if (imp.kind === KIND_MEMORY) { + index = memoryImport++; + } else { + index = globalImport++; + type = sigChar(imp.type); + } + const typeSym = cstr(utf8Bytes(type)); + importRows.push( + `{ ptr, i64, ptr, i64, i64, i64, ptr } { ptr ${mod.sym}, i64 ${mod.len}, ptr ${name.sym}, i64 ${name.len}, i64 ${imp.kind}, i64 ${index}, ptr ${typeSym} }`, + ); + } + const importType = "{ ptr, i64, ptr, i64, i64, i64, ptr }"; + const importsSym = `@${p}_imports`; + lines.push( + `${importsSym} = private constant [${importRows.length} x ${importType}] [${importRows.join(", ")}]`, + ); + // Exports. + const thunks = new Map(); + const exportRows: string[] = []; + for (const ex of m.exports) { + const name = textStr(ex.name); + let type = ""; + let call = "null"; + if (ex.kind === KIND_FUNC) { + type = sigString(this.funcType(ex.index)); + let thunk = thunks.get(ex.index); + if (thunk === undefined) { + thunk = this.exportThunk(ex.index); + thunks.set(ex.index, thunk); + } + call = thunk; + } else if (ex.kind === KIND_TABLE) { + type = sigChar(m.tables[ex.index]!.elemType); + } else if (ex.kind === KIND_GLOBAL) { + type = sigChar(m.globals[ex.index]!.type); + } + const typeSym = cstr(utf8Bytes(type)); + exportRows.push( + `{ ptr, i64, i64, i64, ptr, ptr } { ptr ${name.sym}, i64 ${name.len}, i64 ${ex.kind}, i64 ${ex.index}, ptr ${typeSym}, ptr ${call} }`, + ); + } + const exportType = "{ ptr, i64, i64, i64, ptr, ptr }"; + const exportsSym = `@${p}_exports`; + lines.push( + `${exportsSym} = private constant [${exportRows.length} x ${exportType}] [${exportRows.join(", ")}]`, + ); + // Function references: code, signature identity and signature text for + // every function a reference can name. + const nf = m.funcTypes.length; + const codes: string[] = []; + const callThunks: string[] = []; + const callThunkSyms = new Map(); + const types: string[] = []; + const sigs: string[] = []; + const sigSyms = new Map(); + for (let f = 0; f < nf; f++) { + const referenced = this.refs.has(f); + codes.push(referenced ? `ptr ${this.fn(f)}` : "ptr null"); + // Referenced functions the module does not export reach JavaScript + // through their signature's call thunk. + if (referenced && !thunks.has(f)) { + const type = m.funcTypes[f]!; + let thunk = callThunkSyms.get(type); + if (thunk === undefined) { + thunk = this.callThunk(type); + callThunkSyms.set(type, thunk); + } + callThunks.push(`ptr ${thunk}`); + } else { + callThunks.push("ptr null"); + } + types.push(`i64 ${this.typeHashes[m.funcTypes[f]!]!}`); + const sig = sigString(this.funcType(f)); + let sym = sigSyms.get(sig); + if (sym === undefined) { + sym = cstr(utf8Bytes(sig)); + sigSyms.set(sig, sym); + } + sigs.push(`ptr ${sym}`); + } + const codesSym = `@${p}_codes`; + const typesSym = `@${p}_types`; + const sigsSym = `@${p}_sigs`; + const callThunksSym = `@${p}_calls`; + lines.push(`${codesSym} = private constant [${nf} x ptr] [${codes.join(", ")}]`); + lines.push(`${callThunksSym} = private constant [${nf} x ptr] [${callThunks.join(", ")}]`); + lines.push(`${typesSym} = private constant [${nf} x i64] [${types.join(", ")}]`); + lines.push(`${sigsSym} = private constant [${nf} x ptr] [${sigs.join(", ")}]`); + // Tables. + const tableRows = m.tables.map( + (t) => + `{ i64, i64, i64, i64 } { i64 ${sigChar(t.elemType).charCodeAt(0)}, i64 ${t.min}, i64 ${t.max === null ? -1 : t.max}, i64 ${t.imported ? 1 : 0} }`, + ); + const tablesSym = `@${p}_tables`; + lines.push( + `${tablesSym} = private constant [${tableRows.length} x { i64, i64, i64, i64 }] [${tableRows.join(", ")}]`, + ); + // Globals. + const globalRows = m.globals.map( + (g) => + `{ i64, i64, i64 } { i64 ${sigChar(g.type).charCodeAt(0)}, i64 ${g.mutable ? 1 : 0}, i64 ${g.imported ? 1 : 0} }`, + ); + const globalsSym = `@${p}_globals`; + lines.push( + `${globalsSym} = private constant [${globalRows.length} x { i64, i64, i64 }] [${globalRows.join(", ")}]`, + ); + // Data segments. + const dataRows: string[] = []; + for (let s = 0; s < m.datas.length; s++) { + const seg = m.datas[s]!; + const len = seg.payload.end - seg.payload.start; + if (len === 0) { + dataRows.push(`{ ptr, i64 } { ptr null, i64 0 }`); + continue; + } + const sym = `@${p}_d${s}`; + lines.push( + `${sym} = private unnamed_addr constant [${len} x i8] c"${cBytes(m.bytes, seg.payload.start, seg.payload.end)}", align 1`, + ); + dataRows.push(`{ ptr, i64 } { ptr ${sym}, i64 ${len} }`); + } + const datasSym = `@${p}_datas`; + lines.push( + `${datasSym} = private constant [${dataRows.length} x { ptr, i64 }] [${dataRows.join(", ")}]`, + ); + // Element segments: a function index, -1 for null, or -(2 + g) for the + // value of global g. + const elemRows: string[] = []; + for (let s = 0; s < m.elems.length; s++) { + const seg = m.elems[s]!; + const items: string[] = []; + for (let k = 0; k < seg.funcs.length; k++) { + const f = seg.funcs[k]!; + if (f >= 0) { + items.push(`i64 ${f}`); + continue; + } + items.push(`i64 ${this.elemExprCode(seg.exprs[k]!)}`); + } + let itemsRef = "ptr null"; + if (items.length > 0) { + const sym = `@${p}_e${s}`; + lines.push(`${sym} = private constant [${items.length} x i64] [${items.join(", ")}]`); + itemsRef = `ptr ${sym}`; + } + elemRows.push( + `{ i64, i64, ptr } { i64 ${sigChar(seg.elemType).charCodeAt(0)}, i64 ${items.length}, ${itemsRef} }`, + ); + } + const elemsSym = `@${p}_elems`; + lines.push( + `${elemsSym} = private constant [${elemRows.length} x { i64, i64, ptr }] [${elemRows.join(", ")}]`, + ); + // Custom sections, when the program reads them. + const customRows: string[] = []; + if (this.opts.customSections === true) { + for (let s = 0; s < m.customs.length; s++) { + const custom = m.customs[s]!; + const name = textStr(custom.name); + const len = custom.payload.end - custom.payload.start; + let data = "ptr null"; + if (len > 0) { + const sym = `@${p}_c${s}`; + lines.push( + `${sym} = private unnamed_addr constant [${len} x i8] c"${cBytes(m.bytes, custom.payload.start, custom.payload.end)}", align 1`, + ); + data = `ptr ${sym}`; + } + customRows.push( + `{ ptr, i64, ptr, i64 } { ptr ${name.sym}, i64 ${name.len}, ${data}, i64 ${len} }`, + ); + } + } + const customsSym = `@${p}_customs`; + lines.push( + `${customsSym} = private constant [${customRows.length} x { ptr, i64, ptr, i64 }] [${customRows.join(", ")}]`, + ); + const memory = m.memories[0]; + const sha: string[] = []; + for (let i = 0; i < 32; i++) sha.push(`i8 ${sha256[i]!}`); + const desc = `@${p}_desc`; + const fields = [ + `[32 x i8] [${sha.join(", ")}]`, + `i64 ${m.bytes.length}`, + `i64 ${WASM_ABI_VERSION}`, + `i64 ${this.opts.boundsChecks ? 1 : 0}`, + `i64 ${m.imports.length}`, + `ptr ${importsSym}`, + `i64 ${m.exports.length}`, + `ptr ${exportsSym}`, + `i64 ${nf}`, + `i64 ${m.importedFuncs}`, + `ptr ${codesSym}`, + `ptr ${typesSym}`, + `ptr ${sigsSym}`, + `i64 ${m.tables.length}`, + `ptr ${tablesSym}`, + `i64 ${this.memoryCount}`, + `i64 ${memory === undefined ? 0 : memory.min}`, + `i64 ${memory === undefined || memory.max === null ? -1 : memory.max}`, + `i64 ${m.globals.length}`, + `ptr ${globalsSym}`, + `i64 ${m.datas.length}`, + `ptr ${datasSym}`, + `i64 ${m.elems.length}`, + `ptr ${elemsSym}`, + `ptr ${init}`, + `ptr ${m.start === null ? "null" : this.fn(m.start)}`, + `i64 ${customRows.length}`, + `ptr ${customsSym}`, + `ptr ${callThunksSym}`, + ]; + const descType = + "{ [32 x i8], i64, i64, i64, i64, ptr, i64, ptr, i64, i64, ptr, ptr, ptr, i64, ptr, i64, i64, i64, i64, ptr, i64, ptr, i64, ptr, ptr, ptr, i64, ptr, ptr }"; + lines.push(`${desc} = internal constant ${descType} { ${fields.join(", ")} }`); + this.text.push(lines.join("\n")); + return desc; + } + + /** The element-segment code of a non-ref.func constant expression. */ + elemExprCode(expr: WasmRange): number { + const c = new WasmCursor(this.m.bytes, expr.start); + const op = c.u8(); + if (op === 0xd0) { + c.valType(); + if (c.u8() === 0x0b) return -1; + } else if (op === 0x23) { + const g = c.u32(); + if (c.u8() === 0x0b) return -(2 + g); + } + throw new WasmTranslateError( + "element segment initializers other than ref.null, ref.func and global.get are not supported", + ); + } +} + +/** Store a value as a 64-bit slot; answers the slot operand. */ +function toSlot(out: string[], t: number, v: string, tag: string): string { + switch (t) { + case VAL_I32: + out.push(` %${tag}z = zext i32 ${v} to i64`); + return `%${tag}z`; + case VAL_I64: + return v; + case VAL_F32: + out.push(` %${tag}b = bitcast float ${v} to i32`); + out.push(` %${tag}z = zext i32 %${tag}b to i64`); + return `%${tag}z`; + case VAL_F64: + out.push(` %${tag}z = bitcast double ${v} to i64`); + return `%${tag}z`; + default: + out.push(` %${tag}z = ptrtoint ptr ${v} to i64`); + return `%${tag}z`; + } +} + +function fromSlot(out: string[], t: number, slot: string, tag: string): string { + switch (t) { + case VAL_I32: + out.push(` %${tag}v = trunc i64 ${slot} to i32`); + return `%${tag}v`; + case VAL_I64: + return slot; + case VAL_F32: + out.push(` %${tag}t = trunc i64 ${slot} to i32`); + out.push(` %${tag}v = bitcast i32 %${tag}t to float`); + return `%${tag}v`; + case VAL_F64: + out.push(` %${tag}v = bitcast i64 ${slot} to double`); + return `%${tag}v`; + default: + out.push(` %${tag}v = inttoptr i64 ${slot} to ptr`); + return `%${tag}v`; + } +} + +function emitSlotReturn(out: string[], results: readonly number[], buffer: string): void { + if (results.length === 0) { + out.push(` ret void`); + return; + } + const vals: string[] = []; + for (let i = 0; i < results.length; i++) { + out.push(` %rp${i} = getelementptr i64, ptr ${buffer}, i64 ${i}`); + out.push(` %rs${i} = load i64, ptr %rp${i}`); + vals.push(fromSlot(out, results[i]!, `%rs${i}`, `r${i}`)); + } + if (results.length === 1) { + out.push(` ret ${llType(results[0]!)} ${vals[0]}`); + return; + } + const ret = retType(results); + let agg = "undef"; + for (let i = 0; i < results.length; i++) { + out.push(` %agg${i} = insertvalue ${ret} ${agg}, ${llType(results[i]!)} ${vals[i]}, ${i}`); + agg = `%agg${i}`; + } + out.push(` ret ${ret} ${agg}`); +} + +const LOAD_INFO: Record = { + // opcode: [result type, memory type, bytes, extension] + 0x28: [VAL_I32, "i32", 4, ""], + 0x29: [VAL_I64, "i64", 8, ""], + 0x2a: [VAL_F32, "float", 4, ""], + 0x2b: [VAL_F64, "double", 8, ""], + 0x2c: [VAL_I32, "i8", 1, "sext"], + 0x2d: [VAL_I32, "i8", 1, "zext"], + 0x2e: [VAL_I32, "i16", 2, "sext"], + 0x2f: [VAL_I32, "i16", 2, "zext"], + 0x30: [VAL_I64, "i8", 1, "sext"], + 0x31: [VAL_I64, "i8", 1, "zext"], + 0x32: [VAL_I64, "i16", 2, "sext"], + 0x33: [VAL_I64, "i16", 2, "zext"], + 0x34: [VAL_I64, "i32", 4, "sext"], + 0x35: [VAL_I64, "i32", 4, "zext"], +}; + +const STORE_INFO: Record = { + 0x36: [VAL_I32, "i32", 4], + 0x37: [VAL_I64, "i64", 8], + 0x38: [VAL_F32, "float", 4], + 0x39: [VAL_F64, "double", 8], + 0x3a: [VAL_I32, "i8", 1], + 0x3b: [VAL_I32, "i16", 2], + 0x3c: [VAL_I64, "i8", 1], + 0x3d: [VAL_I64, "i16", 2], + 0x3e: [VAL_I64, "i32", 4], +}; + +/** Translates one function body (index >= 0) or hosts the init function's + * constant expressions (index -1). */ +class FunctionTranslator { + mt: ModuleTranslator; + m: WasmModule; + index: number; + ft: WasmFuncType; + entry = new Lines(); + lines = new Lines(); + stack: StackValue[] = []; + frames: Frame[] = []; + localTypes: number[] = []; + next = 0; + labels = 0; + live = true; + trapLabels = new Map(); + /** Guard mode: the memory base, loaded once in the entry block. */ + baseValue: string | null = null; + checked: boolean; + + constructor(mt: ModuleTranslator, index: number) { + this.mt = mt; + this.m = mt.m; + this.index = index; + this.ft = index >= 0 ? mt.funcType(index) : { params: [], results: [] }; + this.checked = mt.opts.boundsChecks; + } + + tmp(): string { + return `%v${this.next++}`; + } + + label(hint: string): string { + return `${hint}${this.labels++}`; + } + + add(line: string): void { + this.lines.add(line); + } + + emit(expr: string): string { + const v = this.tmp(); + this.add(` ${v} = ${expr}`); + return v; + } + + push(v: string, t: number): void { + this.stack.push({ v, t }); + } + + pop(): StackValue { + const v = this.stack.pop(); + if (v === undefined) throw new Error("wasm translator: operand stack underflow"); + return v; + } + + /** Pops a value that must have type `t`: the type a local, global, + * table or memory store writes. Validation guarantees it; a mismatch + * means the translator and the validator disagree about the code, so + * the module is not translated. */ + popType(t: number, what: string): StackValue { + const v = this.pop(); + if (v.t !== t) throw new WasmTranslateError(`${what} operand type does not match`); + return v; + } + + /** The declared type of local `i` (parameters first). */ + localType(i: number): number { + const t = this.localTypes[i]; + if (t === undefined) throw new WasmTranslateError(`unknown local ${i}`); + return t; + } + + /** The offset of a memarg over memory 0 (the only memory a translatable + * module has). */ + memargOffset(c: WasmCursor): number { + const flags = readMemargFlags(() => c.u32()); + if (flags.align < 0 || flags.memory !== 0) + throw new WasmTranslateError(`memarg of memory ${flags.memory} is not supported`); + return c.u32(); + } + + /** A memory index immediate (memory.size, memory.grow and the bulk + * memory instructions), which must be 0. */ + memoryIndex(c: WasmCursor): void { + const index = c.u32(); + if (index !== 0) throw new WasmTranslateError(`memory ${index} is not supported`); + } + + popN(n: number): StackValue[] { + const out = this.stack.splice(this.stack.length - n, n); + if (out.length !== n) throw new Error("wasm translator: operand stack underflow"); + return out; + } + + /** Starts a new basic block. */ + startBlock(label: string): void { + this.add(`${label}:`); + } + + alloca(type: string): string { + const v = `%s${this.next++}`; + this.entry.add(` ${v} = alloca ${type}`); + return v; + } + + slotPtr(offset: number): string { + if (offset === 0) return "%inst"; + return this.emit(`getelementptr inbounds i8, ptr %inst, i64 ${offset}`); + } + + loadPtrSlot(offset: number): string { + return this.emit(`load ptr, ptr ${this.slotPtr(offset)}, align 8`); + } + + trap(code: number): string { + let label = this.trapLabels.get(code); + if (label === undefined) { + label = `trap${code}`; + this.trapLabels.set(code, label); + } + return label; + } + + // ── memory ────────────────────────────────────────────────────────── + + /** Checked mode: the slots holding the base and byte length. */ + memSlots: { base: string; size: string } | null = null; + + refreshMemory(): void { + if (!this.checked || this.memSlots === null) return; + // From the memory object, not the instance: a memory another instance + // (or JavaScript) grew may have moved. + const mem = this.loadPtrSlot(OFF_MEMORY); + const base = this.emit(`load ptr, ptr ${mem}, align 8`); + this.add(` store ptr ${base}, ptr ${this.memSlots.base}`); + const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${mem}, i64 8`); + const size = this.emit(`load i64, ptr ${sizePtr}, align 8`); + this.add(` store i64 ${size}, ptr ${this.memSlots.size}`); + } + + base(): string { + if (this.checked) { + return this.emit(`load ptr, ptr ${this.memSlots!.base}`); + } + if (this.baseValue === null) { + this.baseValue = "%membase"; + } + return this.baseValue; + } + + /** The address of an access of `bytes` bytes at `addr + offset`. */ + address(addr: string, offset: number, bytes: number): string { + const wide = this.emit(`zext i32 ${addr} to i64`); + const ea = offset === 0 ? wide : this.emit(`add i64 ${wide}, ${offset}`); + if (this.checked) { + const size = this.emit(`load i64, ptr ${this.memSlots!.size}`); + const endAddr = this.emit(`add i64 ${ea}, ${bytes}`); + const oob = this.emit(`icmp ugt i64 ${endAddr}, ${size}`); + const ok = this.label("ok"); + this.add(` br i1 ${oob}, label %${this.trap(TRAP_MEMORY_OOB)}, label %${ok}`); + this.startBlock(ok); + } + return this.emit(`getelementptr i8, ptr ${this.base()}, i64 ${ea}`); + } + + // ── constant expressions ──────────────────────────────────────────── + + constExpr(range: WasmRange): string { + const c = new WasmCursor(this.m.bytes, range.start); + const stack: string[] = []; + for (;;) { + const op = c.u8(); + if (op === 0x0b) break; + switch (op) { + case 0x41: + stack.push(String(c.s32())); + break; + case 0x42: { + const [lo, hi] = c.s64(); + stack.push(i64Lit(lo, hi)); + break; + } + case 0x43: + stack.push(this.emit(`bitcast i32 ${c.u32le() | 0} to float`)); + break; + case 0x44: { + const lo = c.u32le(); + const hi = c.u32le(); + stack.push(this.emit(`bitcast i64 ${i64Lit(lo, hi)} to double`)); + break; + } + case 0x23: { + const g = c.u32(); + const cell = this.loadPtrSlot(this.mt.globalSlot(g)); + stack.push(this.emit(`load ${llType(this.m.globals[g]!.type)}, ptr ${cell}`)); + break; + } + case 0xd0: + c.valType(); + stack.push("null"); + break; + case 0xd2: + stack.push(this.funcRef(c.u32())); + break; + case 0x6a: + case 0x6b: + case 0x6c: + case 0x7c: + case 0x7d: + case 0x7e: { + const b = stack.pop()!; + const a = stack.pop()!; + const t = op < 0x70 ? "i32" : "i64"; + const name = + op === 0x6a || op === 0x7c ? "add" : op === 0x6b || op === 0x7d ? "sub" : "mul"; + stack.push(this.emit(`${name} ${t} ${a}, ${b}`)); + break; + } + default: + throw new WasmTranslateError( + `constant expression opcode 0x${op.toString(16)} is not supported`, + ); + } + } + return stack[stack.length - 1]!; + } + + funcRef(f: number): string { + this.mt.refs.add(f); + const records = this.loadPtrSlot(OFF_FUNCREFS); + return this.emit(`getelementptr inbounds i8, ptr ${records}, i64 ${f * FUNCREF_SIZE}`); + } + + // ── control ───────────────────────────────────────────────────────── + + blockSig(c: WasmCursor): { params: number[]; results: number[] } { + const bt = c.blockType(); + if (bt.kind === "empty") return { params: [], results: [] }; + if (bt.kind === "value") return { params: [], results: [bt.type] }; + const t = this.m.types[bt.index]!; + return { params: t.params.slice(), results: t.results.slice() }; + } + + slotsFor(types: readonly number[]): string[] { + return types.map((t) => this.alloca(llType(t))); + } + + storeTo(slots: readonly string[], values: readonly StackValue[]): void { + for (let i = 0; i < slots.length; i++) { + const v = values[i]!; + this.add(` store ${llType(v.t)} ${v.v}, ptr ${slots[i]}`); + } + } + + loadFrom(slots: readonly string[], types: readonly number[]): void { + for (let i = 0; i < slots.length; i++) { + const t = types[i]!; + this.push(this.emit(`load ${llType(t)}, ptr ${slots[i]}`), t); + } + } + + /** Branch to the frame `depth` levels out (the stack keeps its values). */ + branch(depth: number): void { + const f = this.frames[this.frames.length - 1 - depth]!; + const n = f.branchSlots.length; + if (n > 0) this.storeTo(f.branchSlots, this.stack.slice(this.stack.length - n)); + this.add(` br label %${f.target}`); + } + + /** The label of a block that branches to `depth`, for conditional + * branches and tables: the target itself when no values travel. */ + branchEdge(depth: number, pending: string[]): string { + const f = this.frames[this.frames.length - 1 - depth]!; + if (f.branchSlots.length === 0) return f.target; + const edge = this.label("edge"); + const saved = this.lines; + const edgeLines = new Lines(); + this.lines = edgeLines; + this.startBlock(edge); + this.branch(depth); + this.lines = saved; + pending.push(...edgeLines.lines); + return edge; + } + + goDead(): void { + this.live = false; + } + + pushFrame(kind: Frame["kind"], sig: { params: number[]; results: number[] }): Frame { + const end = this.label("end"); + const frame: Frame = { + kind, + params: sig.params, + results: sig.results, + target: end, + end, + branchSlots: [], + resultSlots: this.slotsFor(sig.results), + height: this.stack.length - sig.params.length, + elseLabel: null, + ifParams: [], + live: true, + }; + frame.branchSlots = frame.resultSlots; + this.frames.push(frame); + return frame; + } + + /** Skips dead code from just after a terminator to the `else` or `end` + * of the current frame; answers the opcode it stopped at (consumed). */ + skipDead(c: WasmCursor): number { + let depth = 0; + for (;;) { + const op = c.u8(); + if (op === 0x02 || op === 0x03 || op === 0x04) { + c.blockType(); + depth++; + continue; + } + if (op === 0x0b) { + if (depth === 0) return op; + depth--; + continue; + } + if (op === 0x05 && depth === 0) return op; + skipImmediates(c, op); + } + } + + translate(): string[] { + const ft = this.ft; + const code = this.m.codes[this.index - this.m.importedFuncs]!; + // Locals. + const localSlots: string[] = []; + this.localTypes = ft.params.concat(code.locals); + for (let i = 0; i < this.localTypes.length; i++) { + const t = this.localTypes[i]!; + const slot = `%l${i}`; + localSlots.push(slot); + this.entry.add(` ${slot} = alloca ${llType(t)}`); + } + for (let i = 0; i < ft.params.length; i++) { + this.entry.add(` store ${llType(ft.params[i]!)} %p${i}, ptr %l${i}`); + } + for (let i = ft.params.length; i < this.localTypes.length; i++) { + const t = this.localTypes[i]!; + this.entry.add(` store ${llType(t)} ${zeroOf(t)}, ptr %l${i}`); + } + if (this.checked && this.mt.memoryCount > 0) { + this.memSlots = { base: "%mbase", size: "%msize" }; + this.entry.add(` %mbase = alloca ptr`); + this.entry.add(` %msize = alloca i64`); + } + // The function frame: branches to it return. + const ret = this.label("ret"); + const fnFrame: Frame = { + kind: "func", + params: [], + results: ft.results, + target: ret, + end: ret, + branchSlots: [], + resultSlots: this.slotsFor(ft.results), + height: 0, + elseLabel: null, + ifParams: [], + live: true, + }; + fnFrame.branchSlots = fnFrame.resultSlots; + this.frames.push(fnFrame); + if (this.memSlots !== null) this.refreshMemory(); + const c = new WasmCursor(this.m.bytes, code.body.start); + this.body(c, code.body.end); + // Epilogue. + const out: string[] = []; + out.push(`define internal ${this.mt.signature(ft, this.mt.fn(this.index))} ${FUNC_ATTRS} {`); + out.push(`entry:`); + out.push(...this.entry.lines); + if (!this.checked && this.baseValue !== null) { + out.push(` %membase.p = getelementptr inbounds i8, ptr %inst, i64 ${OFF_MEMBASE}`); + out.push(` %membase = load ptr, ptr %membase.p, align 8`); + } + if (this.checked && this.mt.stackChecked.has(this.index)) { + // Without the fault handler, stack exhaustion is an explicit check of + // the stack pointer against the runtime's guard for the running stack + // (thread or async fiber), in the functions through which the stack + // can grow without bound (stackCheckedFunctions). + // The address of a frame slot stands for the stack pointer; unlike + // llvm.stacksave it constrains no other code. + out.push(` %sp = alloca i8, align 1`); + out.push(` %sp.i = ptrtoint ptr %sp to i64`); + out.push(` %limit.p = call ptr @llvm.threadlocal.address.p0(ptr @scr_wasm_stack_limit)`); + out.push(` %limit = load i64, ptr %limit.p, align 8`); + out.push(` %exhausted = icmp ult i64 %sp.i, %limit`); + out.push(` br i1 %exhausted, label %exhausted.trap, label %body`); + out.push(`exhausted.trap:`); + out.push(` call void @scr_wasm_trap(i32 ${TRAP_EXHAUSTION})`); + out.push(` unreachable`); + } else { + out.push(` br label %body`); + } + out.push(`body:`); + out.push(...this.lines.lines); + for (const [code2, label] of this.trapLabels) { + out.push(`${label}:`); + out.push(` call void @scr_wasm_trap(i32 ${code2})`); + out.push(` unreachable`); + } + out.push(`}`); + return [out.join("\n")]; + } + + /** The function body; returns at the function's final `end`. */ + body(c: WasmCursor, end: number): void { + while (c.pos < end) { + if (!this.live) { + const op = this.skipDead(c); + if (op === 0x05) this.opElse(); + else if (this.opEnd()) return; + continue; + } + const op = c.u8(); + if (op === 0x0b) { + if (this.opEnd()) return; + continue; + } + this.instruction(c, op); + } + } + + /** `end`: closes the innermost frame; true when that was the function. */ + opEnd(): boolean { + const f = this.frames[this.frames.length - 1]!; + if (this.live) { + const n = f.results.length; + this.storeTo(f.resultSlots, this.stack.slice(this.stack.length - n)); + this.add(` br label %${f.end}`); + } + if (f.elseLabel !== null) { + // An if without else: the implicit else passes its parameters on. + this.startBlock(f.elseLabel); + if (f.live) { + this.storeTo(f.resultSlots, f.ifParams); + } + this.add(` br label %${f.end}`); + f.elseLabel = null; + } + this.frames.pop(); + this.stack.length = f.height; + this.startBlock(f.end); + if (f.kind === "func") { + if (f.results.length === 0) this.add(` ret void`); + else { + this.loadFrom(f.resultSlots, f.results); + const vals = this.popN(f.results.length); + if (vals.length === 1) this.add(` ret ${llType(vals[0]!.t)} ${vals[0]!.v}`); + else { + const rt = retType(f.results); + let agg = "undef"; + for (let i = 0; i < vals.length; i++) { + agg = this.emit(`insertvalue ${rt} ${agg}, ${llType(vals[i]!.t)} ${vals[i]!.v}, ${i}`); + } + this.add(` ret ${rt} ${agg}`); + } + } + return true; + } + this.loadFrom(f.resultSlots, f.results); + this.live = true; + return false; + } + + opElse(): void { + const f = this.frames[this.frames.length - 1]!; + if (this.live) { + const n = f.results.length; + this.storeTo(f.resultSlots, this.stack.slice(this.stack.length - n)); + this.add(` br label %${f.end}`); + } + this.stack.length = f.height; + this.startBlock(f.elseLabel!); + f.elseLabel = null; + for (const v of f.ifParams) this.stack.push(v); + this.live = f.live; + if (!f.live) { + // The if itself was dead: its else branch is too. + this.add(` unreachable`); + } + } + + call(index: number): void { + const ft = this.mt.funcType(index); + const args = this.popN(ft.params.length); + const argText = ["ptr %inst", ...args.map((a) => `${llType(a.t)} ${a.v}`)].join(", "); + this.callWith(this.mt.fn(index), ft, argText); + } + + callWith(callee: string, ft: WasmFuncType, argText: string): void { + const rt = retType(ft.results); + if (ft.results.length === 0) { + this.add(` call void ${callee}(${argText})`); + } else { + const r = this.emit(`call ${rt} ${callee}(${argText})`); + if (ft.results.length === 1) this.push(r, ft.results[0]!); + else + for (let i = 0; i < ft.results.length; i++) + this.push(this.emit(`extractvalue ${rt} ${r}, ${i}`), ft.results[i]!); + } + this.refreshMemory(); + } + + callIndirect(typeIndex: number, table: number): void { + const ft = this.m.types[typeIndex]!; + const idx = this.pop(); + const args = this.popN(ft.params.length); + const t = this.loadPtrSlot(this.mt.tableSlot(table)); + const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${t}, i64 8`); + const size = this.emit(`load i32, ptr ${sizePtr}, align 8`); + const inRange = this.emit(`icmp ult i32 ${idx.v}, ${size}`); + const ok = this.label("ci"); + this.add(` br i1 ${inRange}, label %${ok}, label %${this.trap(TRAP_TABLE_OOB)}`); + this.startBlock(ok); + const elems = this.emit(`load ptr, ptr ${t}, align 8`); + const wide = this.emit(`zext i32 ${idx.v} to i64`); + const slot = this.emit(`getelementptr inbounds ptr, ptr ${elems}, i64 ${wide}`); + const rec = this.emit(`load ptr, ptr ${slot}, align 8`); + const isNull = this.emit(`icmp eq ptr ${rec}, null`); + const nn = this.label("cn"); + this.add(` br i1 ${isNull}, label %${this.trap(TRAP_SIGNATURE)}, label %${nn}`); + this.startBlock(nn); + const typePtr = this.emit(`getelementptr inbounds i8, ptr ${rec}, i64 8`); + const type = this.emit(`load i64, ptr ${typePtr}, align 8`); + const same = this.emit(`icmp eq i64 ${type}, ${this.mt.typeHashes[typeIndex]}`); + const go = this.label("cg"); + this.add(` br i1 ${same}, label %${go}, label %${this.trap(TRAP_SIGNATURE)}`); + this.startBlock(go); + const codePtr = this.emit(`load ptr, ptr ${rec}, align 8`); + const instPtr = this.emit(`getelementptr inbounds i8, ptr ${rec}, i64 16`); + const inst = this.emit(`load ptr, ptr ${instPtr}, align 8`); + const argText = [`ptr ${inst}`, ...args.map((a) => `${llType(a.t)} ${a.v}`)].join(", "); + this.callWith(codePtr, ft, argText); + } + + /** Integer division and remainder with wasm's traps. */ + divRem(op: number, t: string): void { + const b = this.pop(); + const a = this.pop(); + const ty = b.t; + const zero = this.emit(`icmp eq ${t} ${b.v}, 0`); + const signed = op === 0x6d || op === 0x6f || op === 0x7f || op === 0x81; + const rem = op === 0x6f || op === 0x70 || op === 0x81 || op === 0x82; + const nz = this.label("nz"); + this.add( + ` br i1 ${zero}, label %${this.trap(rem ? TRAP_REM_BY_ZERO : TRAP_DIV_BY_ZERO)}, label %${nz}`, + ); + this.startBlock(nz); + const min = t === "i32" ? "-2147483648" : "-9223372036854775808"; + if (signed && !rem) { + const isMin = this.emit(`icmp eq ${t} ${a.v}, ${min}`); + const isNeg1 = this.emit(`icmp eq ${t} ${b.v}, -1`); + const ovf = this.emit(`and i1 ${isMin}, ${isNeg1}`); + const okl = this.label("dv"); + this.add(` br i1 ${ovf}, label %${this.trap(TRAP_DIV_UNREPRESENTABLE)}, label %${okl}`); + this.startBlock(okl); + this.push(this.emit(`sdiv ${t} ${a.v}, ${b.v}`), ty); + return; + } + if (signed && rem) { + // INT_MIN % -1 is 0 in wasm (LLVM's srem would be undefined). + const isNeg1 = this.emit(`icmp eq ${t} ${b.v}, -1`); + const divisor = this.emit(`select i1 ${isNeg1}, ${t} 1, ${t} ${b.v}`); + this.push(this.emit(`srem ${t} ${a.v}, ${divisor}`), ty); + return; + } + this.push(this.emit(`${rem ? "urem" : "udiv"} ${t} ${a.v}, ${b.v}`), ty); + } + + /** Trapping float-to-int conversion. */ + truncTrap(op: number): void { + const x = this.pop(); + const from = x.t === VAL_F32 ? "float" : "double"; + // [lower bound exclusive?, lower, upper (exclusive)] as exact doubles. + let lo = ""; + let loInclusive = false; + let hi = ""; + let to = "i32"; + let signed = true; + switch (op) { + case 0xa8: // i32.trunc_f32_s + case 0xaa: // i32.trunc_f64_s + lo = op === 0xa8 ? "-2147483648.0" : "-2147483649.0"; + loInclusive = op === 0xa8; + hi = "2147483648.0"; + break; + case 0xa9: + case 0xab: + lo = "-1.0"; + hi = "4294967296.0"; + signed = false; + break; + case 0xae: + case 0xb0: + lo = "-9223372036854775808.0"; + loInclusive = true; + hi = "9223372036854775808.0"; + to = "i64"; + break; + default: + lo = "-1.0"; + hi = "18446744073709551616.0"; + to = "i64"; + signed = false; + break; + } + const d = from === "double" ? x.v : this.emit(`fpext float ${x.v} to double`); + const above = this.emit(`fcmp ${loInclusive ? "oge" : "ogt"} double ${d}, ${hexDouble(lo)}`); + const below = this.emit(`fcmp olt double ${d}, ${hexDouble(hi)}`); + const ok = this.emit(`and i1 ${above}, ${below}`); + const okl = this.label("cv"); + this.add(` br i1 ${ok}, label %${okl}, label %${this.trap(TRAP_FLOAT_UNREPRESENTABLE)}`); + this.startBlock(okl); + this.push( + this.emit(`${signed ? "fptosi" : "fptoui"} ${from} ${x.v} to ${to}`), + to === "i32" ? VAL_I32 : VAL_I64, + ); + } + + unaryIntrinsic(name: string, t: string, vt: number, extra = ""): void { + const a = this.pop(); + this.push(this.emit(`call ${t} @llvm.${name}(${t} ${a.v}${extra})`), vt); + } + + binaryIntrinsic(name: string, t: string, vt: number): void { + const b = this.pop(); + const a = this.pop(); + this.push(this.emit(`call ${t} @llvm.${name}(${t} ${a.v}, ${t} ${b.v})`), vt); + } + + binary(instr: string, t: string, vt: number): void { + const b = this.pop(); + const a = this.pop(); + this.push(this.emit(`${instr} ${t} ${a.v}, ${b.v}`), vt); + } + + compare(pred: string, t: string, float: boolean): void { + const b = this.pop(); + const a = this.pop(); + const r = this.emit(`${float ? "fcmp" : "icmp"} ${pred} ${t} ${a.v}, ${b.v}`); + this.push(this.emit(`zext i1 ${r} to i32`), VAL_I32); + } + + eqz(t: string): void { + const a = this.pop(); + const r = this.emit(`icmp eq ${t} ${a.v}, 0`); + this.push(this.emit(`zext i1 ${r} to i32`), VAL_I32); + } + + shift(instr: string, t: string, vt: number): void { + const b = this.pop(); + const a = this.pop(); + const masked = this.emit(`and ${t} ${b.v}, ${t === "i32" ? 31 : 63}`); + this.push(this.emit(`${instr} ${t} ${a.v}, ${masked}`), vt); + } + + rotate(name: string, t: string, vt: number): void { + const b = this.pop(); + const a = this.pop(); + this.push(this.emit(`call ${t} @llvm.${name}.${t}(${t} ${a.v}, ${t} ${a.v}, ${t} ${b.v})`), vt); + } + + convert(instr: string, from: string, to: string, vt: number): void { + const a = this.pop(); + this.push(this.emit(`${instr} ${from} ${a.v} to ${to}`), vt); + } + + instruction(c: WasmCursor, op: number): void { + switch (op) { + case 0x00: // unreachable + this.add(` br label %${this.trap(TRAP_UNREACHABLE)}`); + this.goDead(); + return; + case 0x01: + return; + case 0x02: { + // block + const sig = this.blockSig(c); + this.pushFrame("block", sig); + return; + } + case 0x03: { + // loop + const sig = this.blockSig(c); + const f = this.pushFrame("loop", sig); + f.target = this.label("loop"); + f.branchSlots = this.slotsFor(sig.params); + const params = this.popN(sig.params.length); + this.storeTo(f.branchSlots, params); + this.add(` br label %${f.target}`); + this.startBlock(f.target); + this.loadFrom(f.branchSlots, sig.params); + return; + } + case 0x04: { + // if + const sig = this.blockSig(c); + const cond = this.pop(); + const f = this.pushFrame("if", sig); + f.ifParams = this.stack.slice(this.stack.length - sig.params.length); + const then = this.label("then"); + f.elseLabel = this.label("else"); + const test = this.emit(`icmp ne i32 ${cond.v}, 0`); + this.add(` br i1 ${test}, label %${then}, label %${f.elseLabel}`); + this.startBlock(then); + return; + } + case 0x05: + this.opElse(); + return; + case 0x0c: { + const depth = c.u32(); + this.branch(depth); + this.goDead(); + return; + } + case 0x0d: { + const depth = c.u32(); + const cond = this.pop(); + const test = this.emit(`icmp ne i32 ${cond.v}, 0`); + const pending: string[] = []; + const target = this.branchEdge(depth, pending); + const cont = this.label("br"); + this.add(` br i1 ${test}, label %${target}, label %${cont}`); + for (const line of pending) this.add(line); + this.startBlock(cont); + return; + } + case 0x0e: { + const n = c.u32(); + const depths: number[] = []; + for (let i = 0; i < n; i++) depths.push(c.u32()); + const def = c.u32(); + const idx = this.pop(); + const pending: string[] = []; + const edges = new Map(); + const edgeOf = (d: number): string => { + let e = edges.get(d); + if (e === undefined) { + e = this.branchEdge(d, pending); + edges.set(d, e); + } + return e; + }; + const cases = depths.map((d, i) => `i32 ${i}, label %${edgeOf(d)}`); + this.add(` switch i32 ${idx.v}, label %${edgeOf(def)} [${cases.join(" ")}]`); + for (const line of pending) this.add(line); + this.goDead(); + return; + } + case 0x0f: // return + this.branch(this.frames.length - 1); + this.goDead(); + return; + case 0x10: + this.call(c.u32()); + return; + case 0x11: { + const type = c.u32(); + const table = c.u32(); + this.callIndirect(type, table); + return; + } + case 0x1a: { + // A dropped load must still fault when it is out of bounds: LLVM + // would delete an unused ordinary load. + const v = this.pop(); + if (v.load !== undefined) { + const line = this.lines.lines[v.load]!; + this.lines.lines[v.load] = line.replace(" = load ", " = load volatile "); + } + return; + } + case 0x1b: + case 0x1c: { + if (op === 0x1c) { + const n = c.u32(); + for (let i = 0; i < n; i++) c.valType(); + } + const cond = this.pop(); + const b = this.pop(); + const a = this.pop(); + const test = this.emit(`icmp ne i32 ${cond.v}, 0`); + const t = llType(a.t); + this.push(this.emit(`select i1 ${test}, ${t} ${a.v}, ${t} ${b.v}`), a.t); + return; + } + case 0x20: { + const i = c.u32(); + const t = this.localType(i); + this.push(this.emit(`load ${llType(t)}, ptr %l${i}`), t); + return; + } + case 0x21: { + const i = c.u32(); + const t = this.localType(i); + const v = this.popType(t, "local.set"); + this.add(` store ${llType(t)} ${v.v}, ptr %l${i}`); + return; + } + case 0x22: { + const i = c.u32(); + const t = this.localType(i); + const v = this.popType(t, "local.tee"); + this.stack.push(v); + this.add(` store ${llType(t)} ${v.v}, ptr %l${i}`); + return; + } + case 0x23: { + const g = c.u32(); + const t = this.m.globals[g]!.type; + const cell = this.loadPtrSlot(this.mt.globalSlot(g)); + this.push(this.emit(`load ${llType(t)}, ptr ${cell}, align 8`), t); + return; + } + case 0x24: { + const g = c.u32(); + const global = this.m.globals[g]; + if (global === undefined) throw new WasmTranslateError(`unknown global ${g}`); + const v = this.popType(global.type, "global.set"); + const cell = this.loadPtrSlot(this.mt.globalSlot(g)); + if (v.t === VAL_EXTERNREF) + this.add(` call void @scr_wasm_global_set_ref(ptr ${cell}, ptr ${v.v})`); + else this.add(` store ${llType(v.t)} ${v.v}, ptr ${cell}, align 8`); + return; + } + case 0x25: { + const t = c.u32(); + const i = this.pop(); + this.push( + this.emit(`call ptr @scr_wasm_table_get(ptr %inst, i32 ${t}, i32 ${i.v})`), + this.m.tables[t]!.elemType, + ); + return; + } + case 0x26: { + const t = c.u32(); + const table = this.m.tables[t]; + if (table === undefined) throw new WasmTranslateError(`unknown table ${t}`); + const v = this.popType(table.elemType, "table.set"); + const i = this.popType(VAL_I32, "table.set"); + this.add(` call void @scr_wasm_table_set(ptr %inst, i32 ${t}, i32 ${i.v}, ptr ${v.v})`); + return; + } + case 0x3f: { + this.memoryIndex(c); + const mem = this.loadPtrSlot(OFF_MEMORY); + const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${mem}, i64 8`); + const size = this.emit(`load i64, ptr ${sizePtr}, align 8`); + const pages = this.emit(`lshr i64 ${size}, 16`); + this.push(this.emit(`trunc i64 ${pages} to i32`), VAL_I32); + return; + } + case 0x40: { + this.memoryIndex(c); + const delta = this.pop(); + this.push(this.emit(`call i32 @scr_wasm_memory_grow(ptr %inst, i32 ${delta.v})`), VAL_I32); + this.refreshMemory(); + return; + } + case 0x41: + this.push(String(c.s32()), VAL_I32); + return; + case 0x42: { + const [lo, hi] = c.s64(); + this.push(i64Lit(lo, hi), VAL_I64); + return; + } + case 0x43: + this.push(this.emit(`bitcast i32 ${c.u32le() | 0} to float`), VAL_F32); + return; + case 0x44: { + const lo = c.u32le(); + const hi = c.u32le(); + this.push(this.emit(`bitcast i64 ${i64Lit(lo, hi)} to double`), VAL_F64); + return; + } + case 0xd0: + c.valType(); + this.push("null", c.bytes[c.pos - 1]!); + return; + case 0xd1: { + const a = this.pop(); + const r = this.emit(`icmp eq ptr ${a.v}, null`); + this.push(this.emit(`zext i1 ${r} to i32`), VAL_I32); + return; + } + case 0xd2: + this.push(this.funcRef(c.u32()), VAL_FUNCREF); + return; + case 0xfc: + this.prefixed(c); + return; + default: + break; + } + if (op >= 0x28 && op <= 0x35) { + const offset = this.memargOffset(c); + const [vt, mt, bytes, ext] = LOAD_INFO[op]!; + const addr = this.popType(VAL_I32, "load address"); + const p = this.address(addr.v, offset, bytes); + const line = this.lines.lines.length; + const loaded = this.emit(`load ${mt}, ptr ${p}, align 1`); + if (ext === "") this.stack.push({ v: loaded, t: vt, load: line }); + else { + const extended = this.emit(`${ext} ${mt} ${loaded} to ${llType(vt)}`); + this.stack.push({ v: extended, t: vt, load: line }); + } + return; + } + if (op >= 0x36 && op <= 0x3e) { + const offset = this.memargOffset(c); + const [vt, mt, bytes] = STORE_INFO[op]!; + const v = this.popType(vt, "store"); + const addr = this.popType(VAL_I32, "store address"); + const p = this.address(addr.v, offset, bytes); + let value = v.v; + if (mt === "i8" || mt === "i16" || (mt === "i32" && v.t === VAL_I64)) { + value = this.emit(`trunc ${llType(v.t)} ${v.v} to ${mt}`); + } + this.add(` store ${mt} ${value}, ptr ${p}, align 1`); + return; + } + this.numeric(op); + } + + numeric(op: number): void { + const I32 = VAL_I32; + const I64 = VAL_I64; + const F32 = VAL_F32; + const F64 = VAL_F64; + switch (op) { + case 0x45: + return this.eqz("i32"); + case 0x46: + return this.compare("eq", "i32", false); + case 0x47: + return this.compare("ne", "i32", false); + case 0x48: + return this.compare("slt", "i32", false); + case 0x49: + return this.compare("ult", "i32", false); + case 0x4a: + return this.compare("sgt", "i32", false); + case 0x4b: + return this.compare("ugt", "i32", false); + case 0x4c: + return this.compare("sle", "i32", false); + case 0x4d: + return this.compare("ule", "i32", false); + case 0x4e: + return this.compare("sge", "i32", false); + case 0x4f: + return this.compare("uge", "i32", false); + case 0x50: + return this.eqz("i64"); + case 0x51: + return this.compare("eq", "i64", false); + case 0x52: + return this.compare("ne", "i64", false); + case 0x53: + return this.compare("slt", "i64", false); + case 0x54: + return this.compare("ult", "i64", false); + case 0x55: + return this.compare("sgt", "i64", false); + case 0x56: + return this.compare("ugt", "i64", false); + case 0x57: + return this.compare("sle", "i64", false); + case 0x58: + return this.compare("ule", "i64", false); + case 0x59: + return this.compare("sge", "i64", false); + case 0x5a: + return this.compare("uge", "i64", false); + case 0x5b: + return this.compare("oeq", "float", true); + case 0x5c: + return this.compare("une", "float", true); + case 0x5d: + return this.compare("olt", "float", true); + case 0x5e: + return this.compare("ogt", "float", true); + case 0x5f: + return this.compare("ole", "float", true); + case 0x60: + return this.compare("oge", "float", true); + case 0x61: + return this.compare("oeq", "double", true); + case 0x62: + return this.compare("une", "double", true); + case 0x63: + return this.compare("olt", "double", true); + case 0x64: + return this.compare("ogt", "double", true); + case 0x65: + return this.compare("ole", "double", true); + case 0x66: + return this.compare("oge", "double", true); + case 0x67: + return this.unaryIntrinsic("ctlz.i32", "i32", I32, ", i1 false"); + case 0x68: + return this.unaryIntrinsic("cttz.i32", "i32", I32, ", i1 false"); + case 0x69: + return this.unaryIntrinsic("ctpop.i32", "i32", I32); + case 0x6a: + return this.binary("add", "i32", I32); + case 0x6b: + return this.binary("sub", "i32", I32); + case 0x6c: + return this.binary("mul", "i32", I32); + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + return this.divRem(op, "i32"); + case 0x71: + return this.binary("and", "i32", I32); + case 0x72: + return this.binary("or", "i32", I32); + case 0x73: + return this.binary("xor", "i32", I32); + case 0x74: + return this.shift("shl", "i32", I32); + case 0x75: + return this.shift("ashr", "i32", I32); + case 0x76: + return this.shift("lshr", "i32", I32); + case 0x77: + return this.rotate("fshl", "i32", I32); + case 0x78: + return this.rotate("fshr", "i32", I32); + case 0x79: + return this.unaryIntrinsic("ctlz.i64", "i64", I64, ", i1 false"); + case 0x7a: + return this.unaryIntrinsic("cttz.i64", "i64", I64, ", i1 false"); + case 0x7b: + return this.unaryIntrinsic("ctpop.i64", "i64", I64); + case 0x7c: + return this.binary("add", "i64", I64); + case 0x7d: + return this.binary("sub", "i64", I64); + case 0x7e: + return this.binary("mul", "i64", I64); + case 0x7f: + case 0x80: + case 0x81: + case 0x82: + return this.divRem(op, "i64"); + case 0x83: + return this.binary("and", "i64", I64); + case 0x84: + return this.binary("or", "i64", I64); + case 0x85: + return this.binary("xor", "i64", I64); + case 0x86: + return this.shift("shl", "i64", I64); + case 0x87: + return this.shift("ashr", "i64", I64); + case 0x88: + return this.shift("lshr", "i64", I64); + case 0x89: + return this.rotate("fshl", "i64", I64); + case 0x8a: + return this.rotate("fshr", "i64", I64); + case 0x8b: + return this.unaryIntrinsic("fabs.f32", "float", F32); + case 0x8c: { + const a = this.pop(); + this.push(this.emit(`fneg float ${a.v}`), F32); + return; + } + case 0x8d: + return this.unaryIntrinsic("ceil.f32", "float", F32); + case 0x8e: + return this.unaryIntrinsic("floor.f32", "float", F32); + case 0x8f: + return this.unaryIntrinsic("trunc.f32", "float", F32); + case 0x90: + return this.unaryIntrinsic("roundeven.f32", "float", F32); + case 0x91: + return this.unaryIntrinsic("sqrt.f32", "float", F32); + case 0x92: + return this.binary("fadd", "float", F32); + case 0x93: + return this.binary("fsub", "float", F32); + case 0x94: + return this.binary("fmul", "float", F32); + case 0x95: + return this.binary("fdiv", "float", F32); + case 0x96: + return this.binaryIntrinsic("minimum.f32", "float", F32); + case 0x97: + return this.binaryIntrinsic("maximum.f32", "float", F32); + case 0x98: + return this.binaryIntrinsic("copysign.f32", "float", F32); + case 0x99: + return this.unaryIntrinsic("fabs.f64", "double", F64); + case 0x9a: { + const a = this.pop(); + this.push(this.emit(`fneg double ${a.v}`), F64); + return; + } + case 0x9b: + return this.unaryIntrinsic("ceil.f64", "double", F64); + case 0x9c: + return this.unaryIntrinsic("floor.f64", "double", F64); + case 0x9d: + return this.unaryIntrinsic("trunc.f64", "double", F64); + case 0x9e: + return this.unaryIntrinsic("roundeven.f64", "double", F64); + case 0x9f: + return this.unaryIntrinsic("sqrt.f64", "double", F64); + case 0xa0: + return this.binary("fadd", "double", F64); + case 0xa1: + return this.binary("fsub", "double", F64); + case 0xa2: + return this.binary("fmul", "double", F64); + case 0xa3: + return this.binary("fdiv", "double", F64); + case 0xa4: + return this.binaryIntrinsic("minimum.f64", "double", F64); + case 0xa5: + return this.binaryIntrinsic("maximum.f64", "double", F64); + case 0xa6: + return this.binaryIntrinsic("copysign.f64", "double", F64); + case 0xa7: + return this.convert("trunc", "i64", "i32", I32); + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + return this.truncTrap(op); + case 0xac: + return this.convert("sext", "i32", "i64", I64); + case 0xad: + return this.convert("zext", "i32", "i64", I64); + case 0xb2: + return this.convert("sitofp", "i32", "float", F32); + case 0xb3: + return this.convert("uitofp", "i32", "float", F32); + case 0xb4: + return this.convert("sitofp", "i64", "float", F32); + case 0xb5: + return this.convert("uitofp", "i64", "float", F32); + case 0xb6: + return this.convert("fptrunc", "double", "float", F32); + case 0xb7: + return this.convert("sitofp", "i32", "double", F64); + case 0xb8: + return this.convert("uitofp", "i32", "double", F64); + case 0xb9: + return this.convert("sitofp", "i64", "double", F64); + case 0xba: + return this.convert("uitofp", "i64", "double", F64); + case 0xbb: + return this.convert("fpext", "float", "double", F64); + case 0xbc: + return this.convert("bitcast", "float", "i32", I32); + case 0xbd: + return this.convert("bitcast", "double", "i64", I64); + case 0xbe: + return this.convert("bitcast", "i32", "float", F32); + case 0xbf: + return this.convert("bitcast", "i64", "double", F64); + case 0xc0: + case 0xc1: { + const a = this.pop(); + const n = op === 0xc0 ? "i8" : "i16"; + const narrow = this.emit(`trunc i32 ${a.v} to ${n}`); + this.push(this.emit(`sext ${n} ${narrow} to i32`), I32); + return; + } + case 0xc2: + case 0xc3: + case 0xc4: { + const a = this.pop(); + const n = op === 0xc2 ? "i8" : op === 0xc3 ? "i16" : "i32"; + const narrow = this.emit(`trunc i64 ${a.v} to ${n}`); + this.push(this.emit(`sext ${n} ${narrow} to i64`), I64); + return; + } + default: + throw new WasmTranslateError(`opcode 0x${op.toString(16)} is not supported`); + } + } + + prefixed(c: WasmCursor): void { + const sub = c.u32(); + if (sub <= 7) { + // Saturating float-to-int conversions. + const a = this.pop(); + const from = a.t === VAL_F32 ? "f32" : "f64"; + const fromTy = a.t === VAL_F32 ? "float" : "double"; + const to = sub < 4 ? "i32" : "i64"; + const signed = (sub & 1) === 0; + this.push( + this.emit( + `call ${to} @llvm.${signed ? "fptosi" : "fptoui"}.sat.${to}.${from}(${fromTy} ${a.v})`, + ), + to === "i32" ? VAL_I32 : VAL_I64, + ); + return; + } + switch (sub) { + case 8: { + // memory.init + const seg = c.u32(); + this.memoryIndex(c); + const n = this.pop(); + const s = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_memory_init(ptr %inst, i32 ${seg}, i32 ${d.v}, i32 ${s.v}, i32 ${n.v})`, + ); + return; + } + case 9: + this.add(` call void @scr_wasm_data_drop(ptr %inst, i32 ${c.u32()})`); + return; + case 10: { + this.memoryIndex(c); + this.memoryIndex(c); + const n = this.pop(); + const s = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_memory_copy(ptr %inst, i32 ${d.v}, i32 ${s.v}, i32 ${n.v})`, + ); + return; + } + case 11: { + this.memoryIndex(c); + const n = this.pop(); + const v = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_memory_fill(ptr %inst, i32 ${d.v}, i32 ${v.v}, i32 ${n.v})`, + ); + return; + } + case 12: { + const seg = c.u32(); + const t = c.u32(); + const n = this.pop(); + const s = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_table_init(ptr %inst, i32 ${t}, i32 ${seg}, i32 ${d.v}, i32 ${s.v}, i32 ${n.v})`, + ); + return; + } + case 13: + this.add(` call void @scr_wasm_elem_drop(ptr %inst, i32 ${c.u32()})`); + return; + case 14: { + const dt = c.u32(); + const st = c.u32(); + const n = this.pop(); + const s = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_table_copy(ptr %inst, i32 ${dt}, i32 ${st}, i32 ${d.v}, i32 ${s.v}, i32 ${n.v})`, + ); + return; + } + case 15: { + const t = c.u32(); + const n = this.pop(); + const v = this.pop(); + this.push( + this.emit(`call i32 @scr_wasm_table_grow(ptr %inst, i32 ${t}, ptr ${v.v}, i32 ${n.v})`), + VAL_I32, + ); + return; + } + case 16: { + const t = c.u32(); + const tab = this.loadPtrSlot(this.mt.tableSlot(t)); + const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${tab}, i64 8`); + this.push(this.emit(`load i32, ptr ${sizePtr}, align 8`), VAL_I32); + return; + } + case 17: { + const t = c.u32(); + const n = this.pop(); + const v = this.pop(); + const d = this.pop(); + this.add( + ` call void @scr_wasm_table_fill(ptr %inst, i32 ${t}, i32 ${d.v}, ptr ${v.v}, i32 ${n.v})`, + ); + return; + } + default: + throw new WasmTranslateError(`opcode 0xfc ${sub} is not supported`); + } + } +} + +/** The functions through which the stack can grow without bound, which + * check the stack pointer on entry: members of a cycle of direct calls + * (including self-recursion), and functions that call through a table or + * call an import (an indirect call can reach any function, and an import + * can call back into an export). Every other call chain is finite, so its + * depth is bounded by the module and the guard's reserve absorbs it. */ +export function stackCheckedFunctions(m: WasmModule): Set { + const n = m.funcTypes.length; + const callees: number[][] = []; + const checked = new Set(); + for (let f = 0; f < n; f++) callees.push([]); + for (let i = 0; i < m.codes.length; i++) { + const f = m.importedFuncs + i; + const body = m.codes[i]!.body; + const c = new WasmCursor(m.bytes, body.start); + while (c.pos < body.end) { + const op = c.u8(); + if (op === 0x10) { + const callee = c.u32(); + if (callee < m.importedFuncs) checked.add(f); + else callees[f]!.push(callee); + } else if (op === 0x11) { + c.u32(); + c.u32(); + checked.add(f); + } else { + skipImmediates(c, op); + } + } + } + // Tarjan's strongly connected components, iteratively. + const index = new Int32Array(n).fill(-1); + const low = new Int32Array(n); + const onStack = new Uint8Array(n); + const stack: number[] = []; + let next = 0; + for (let root = m.importedFuncs; root < n; root++) { + if (index[root] !== -1) continue; + const workNode: number[] = [root]; + const workEdge: number[] = [0]; + index[root] = low[root] = next++; + stack.push(root); + onStack[root] = 1; + while (workNode.length > 0) { + const top = workNode.length - 1; + const v = workNode[top]!; + const edges = callees[v]!; + const e = workEdge[top]!; + if (e < edges.length) { + workEdge[top] = e + 1; + const w = edges[e]!; + if (index[w] === -1) { + index[w] = low[w] = next++; + stack.push(w); + onStack[w] = 1; + workNode.push(w); + workEdge.push(0); + } else if (onStack[w]) { + low[v] = Math.min(low[v]!, index[w]!); + } + continue; + } + workNode.pop(); + workEdge.pop(); + if (workNode.length > 0) { + const parent = workNode[workNode.length - 1]!; + low[parent] = Math.min(low[parent]!, low[v]!); + } + if (low[v] === index[v]) { + const component: number[] = []; + for (;;) { + const w = stack.pop()!; + onStack[w] = 0; + component.push(w); + if (w === v) break; + } + if (component.length > 1 || edges.includes(v)) for (const w of component) checked.add(w); + } + } + } + return checked; +} + +/** A decimal double literal as LLVM's exact hexadecimal form. */ +function hexDouble(text: string): string { + const view = new DataView(new ArrayBuffer(8)); + view.setFloat64(0, Number(text)); + return `0x${hex8(view.getUint32(0))}${hex8(view.getUint32(4))}`.toUpperCase().replace("0X", "0x"); +} + +function hexOf(bytes: Uint8Array): string { + let s = ""; + for (let i = 0; i < bytes.length; i++) s += bytes[i]!.toString(16).padStart(2, "0"); + return s; +} + +/** The symbol prefix of a module's translation. */ +export function wasmSymbolPrefix(sha256: Uint8Array): string { + return `scr_wasm_${hexOf(sha256).slice(0, 16)}`; +} + +/** Translates a module that binary.ts validated within the translatable + * feature level. Throws WasmTranslateError for constructs outside it. */ +export function translateWasmModule( + bytes: Uint8Array, + sha256: Uint8Array, + opts: WasmTranslateOptions, +): WasmTranslation { + const m = parseWasmModule(bytes); + for (const t of m.tables) llType(t.elemType); + const mt = new ModuleTranslator(m, wasmSymbolPrefix(sha256), opts); + mt.translate(); + const descriptor = mt.descriptor(sha256); + const decls = RUNTIME_DECLS.slice(); + // Initial-exec: executables and their static runtime define it, so the + // check is one thread-pointer-relative load, not a TLS lookup call. + if (opts.boundsChecks) + decls.push("@scr_wasm_stack_limit = external thread_local(initialexec) global i64"); + return { decls, text: mt.text, descriptor }; +} diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 01d12dedc..cb908d408 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -1,6 +1,6 @@ { "schemaVersion": 2, - "compilerVersion": "0.3.6", + "compilerVersion": "0.4.0", "coverage": [ "Entries are projected mechanically from the compiler's own decision tables: the diagnostics registry, the unsupported-syntax dispatch tables, the stdlib and node-builtin lowering tables, and the supported-builtin-module list. Nothing is hand-maintained; the manifest regenerates byte-identically from the source tree at this version.", "Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.", @@ -20,7 +20,7 @@ "name": "package imports", "status": "unsupported", "code": "SC1010", - "note": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "note": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "id": "diagnostic.sc1012", @@ -823,6 +823,13 @@ "status": "static", "note": "recognized module (bare and node:-prefixed specifiers)" }, + { + "id": "node-builtin.dns.promises", + "kind": "node-builtin", + "name": "dns/promises", + "status": "static", + "note": "recognized module (bare and node:-prefixed specifiers)" + }, { "id": "node-builtin.events", "kind": "node-builtin", @@ -1860,6 +1867,13 @@ "status": "static", "note": "recognized module (bare and node:-prefixed specifiers)" }, + { + "id": "node-builtin.perf_hooks.monitorEventLoopDelay", + "kind": "node-builtin", + "name": "perf_hooks.monitorEventLoopDelay", + "status": "static", + "note": "an unref'd sampling interval records the delay histogram; enable, disable, reset, percentile and the min, max, mean, stddev, count and exceeds readings are supported" + }, { "id": "node-builtin.perf_hooks.performance.clearMarks", "kind": "node-builtin", @@ -1872,6 +1886,13 @@ "name": "perf_hooks.performance.clearMeasures", "status": "static" }, + { + "id": "node-builtin.perf_hooks.performance.eventLoopUtilization", + "kind": "node-builtin", + "name": "perf_hooks.performance.eventLoopUtilization", + "status": "static", + "note": "active time is the main thread's CPU time since the loop started; the one- and two-argument difference forms are supported" + }, { "id": "node-builtin.perf_hooks.performance.getEntries", "kind": "node-builtin", @@ -1988,7 +2009,7 @@ "kind": "node-builtin", "name": "process.exitCode", "status": "static", - "note": "numeric writes in statement position set the implicit exit status; process.exit() reads it" + "note": "numeric writes in statement position set the implicit exit status; reads answer the assigned code or undefined; process.exit() reads it" }, { "id": "node-builtin.process.getBuiltinModule", @@ -2275,6 +2296,13 @@ "status": "static", "note": "recognized module (bare and node:-prefixed specifiers)" }, + { + "id": "node-builtin.stream.web", + "kind": "node-builtin", + "name": "stream/web", + "status": "static", + "note": "recognized module (bare and node:-prefixed specifiers)" + }, { "id": "node-builtin.string_decoder", "kind": "node-builtin", @@ -2532,7 +2560,7 @@ "name": "util.promisify", "status": "unsupported", "code": "SC2020", - "note": "the lowered shapes are const bindings over child_process.execFile, child_process.exec, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions, dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart; call the bound function directly" + "note": "the lowered shapes are const bindings over child_process.execFile, child_process.exec, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions, dns.lookup, dns.lookupService and the fs callback functions with an fs/promises counterpart, plus typed callback-last function values whose callback takes (error?: Error | null, value?); call the bound function directly" }, { "id": "node-builtin.util.stripVTControlCharacters", @@ -2805,6 +2833,19 @@ "name": "util/types.isWeakSet", "status": "static" }, + { + "id": "node-builtin.v8", + "kind": "node-builtin", + "name": "v8", + "status": "static", + "note": "recognized module (bare and node:-prefixed specifiers)" + }, + { + "id": "node-builtin.v8.getHeapStatistics", + "kind": "node-builtin", + "name": "v8.getHeapStatistics", + "status": "static" + }, { "id": "node-builtin.worker_threads", "kind": "node-builtin", @@ -4097,7 +4138,7 @@ "kind": "stdlib", "name": "Date constructor", "status": "static", - "note": "zero arguments, or one milliseconds/date-string argument; values are the read-only TimeClip scalar slice" + "note": "zero arguments, one milliseconds/date-string argument, or local-time year/month fields; values are the read-only TimeClip scalar slice" }, { "id": "stdlib.date.getDate", @@ -4220,7 +4261,7 @@ "kind": "stdlib", "name": "Date.parse", "status": "static", - "note": "one date string, using the bounded parser shared with new Date(dateString).getTime()" + "note": "one date string, using V8's date parser shared with new Date(dateString)" }, { "id": "stdlib.date.toDateString", @@ -5672,9 +5713,8 @@ "id": "stdlib.webassembly", "kind": "stdlib", "name": "globalThis.WebAssembly", - "status": "unsupported", - "code": "SC2020", - "note": "WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations)" + "status": "static", + "note": "compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, instanceof, the error classes, exports, imports and Module transfer to workers, over the modules translated at build time; the rest refuses with SC2020 (the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations))" }, { "id": "stdlib.webcrypto.CryptoKey", diff --git a/packages/compiler/test/bytes-field-borrow-emission.test.ts b/packages/compiler/test/bytes-field-borrow-emission.test.ts new file mode 100644 index 000000000..168cd78ac --- /dev/null +++ b/packages/compiler/test/bytes-field-borrow-emission.test.ts @@ -0,0 +1,94 @@ +import { expect, test } from "vitest"; +import { emitLlvmModule } from "../src/backend/llvm/emitter.js"; +import { + F64, + VOID, + bytesOf, + type IrExpr, + type IrFunction, + type IrModule, + type IrStmt, +} from "../src/ir/ir.js"; +import { validateModule } from "../src/ir/validate.js"; + +const loc = { file: "bytes-field-borrow.ts", start: 0, end: 0 }; +const units = bytesOf("u16"); +const scanner = { kind: "object" as const, className: "Scanner" }; +const self = (): IrExpr => ({ kind: "varRef", localId: "this", type: scanner, loc }); +const chars = (): IrExpr => ({ + kind: "fieldGet", + obj: self(), + className: "Scanner", + field: "chars", + type: units, + loc, +}); +const local = (): IrExpr => ({ kind: "varRef", localId: "c", type: units, loc }); +const read = (index: number): IrExpr => ({ + kind: "bytesIntrinsic", + method: "get", + receiver: local(), + args: [{ kind: "numLit", value: index, type: F64, loc }], + type: F64, + loc, +}); + +/** `const c = this.chars; ...between; return c[0] + c[1]`. */ +function method(name: string, between: IrStmt[]): IrFunction { + return { + name, + params: [{ localId: "this", name: "this", type: scanner }], + returnType: F64, + locals: [ + { id: "this", name: "this", type: scanner, mutable: false }, + { id: "c", name: "c", type: units, mutable: false }, + ], + body: [ + { kind: "varDecl", localId: "c", init: chars(), loc }, + ...between, + { + kind: "return", + value: { kind: "bin", op: "+", left: read(0), right: read(1), type: F64 }, + loc, + }, + ], + loc, + } as IrFunction; +} + +test("a typed-array local read from a field the rest of its block never writes borrows it", () => { + const replace: IrStmt = { + kind: "fieldSet", + obj: self(), + className: "Scanner", + field: "chars", + value: { + kind: "bytesNew", + source: { kind: "numLit", value: 4, type: F64, loc }, + type: units, + loc, + }, + loc, + }; + const mod: IrModule = { + irVersion: 16, + sourceFile: loc.file, + entry: "main", + functions: [ + { name: "main", params: [], returnType: VOID, locals: [], body: [], loc }, + method("%Scanner.peek", []), + method("%Scanner.swap", [replace]), + ], + classes: [{ name: "Scanner", fields: [{ name: "chars", type: units }], methods: [], loc }], + }; + expect(validateModule(mod)).toEqual([]); + const llvm = emitLlvmModule(mod, { pointerBits: 64 }); + const body = (name: string): string => + new RegExp(`^define [^\\n]*@sc_bf__x25_Scanner_${name}\\b[^]*?^}`, "m").exec(llvm)?.[0] ?? ""; + expect(body("peek")).not.toBe(""); + // The field keeps the array alive while the borrowed local reads it. + expect(body("peek")).not.toContain("bytes_retain"); + expect(body("peek")).not.toContain("bytes_release"); + // Replacing the field could free the array the local holds: it keeps a reference. + expect(body("swap")).toContain("call ptr @scr_bytes_retain_v("); +}); diff --git a/packages/compiler/test/class-attributes.test.ts b/packages/compiler/test/class-attributes.test.ts new file mode 100644 index 000000000..1a6fea654 --- /dev/null +++ b/packages/compiler/test/class-attributes.test.ts @@ -0,0 +1,84 @@ +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule } from "../src/index.js"; +import type { IrModule } from "../src/ir/ir.js"; + +async function lowerModule(source: string, file = "main.ts"): Promise { + const dir = await mkdtemp(join(tmpdir(), "scriptc-class-attributes-")); + try { + const entry = join(dir, file); + const outPath = join(dir, "main.ir.json"); + await writeFile(entry, source); + const result = await compile(entry, { outDir: dir, outPath, outputKind: "ir" }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return deserializeModule(await readFile(outPath, "utf8")); + } finally { + await rm(dir, { recursive: true, force: true }); + } +} + +function fieldsOf(mod: IrModule, className: string): string[] { + const cls = (mod.classes ?? []).find( + (c) => c.name === className || c.name.endsWith(`.${className}`), + ); + if (!cls) throw new Error(`no class ${className}`); + return cls.fields.map((f) => f.name); +} + +function functionsCalling(mod: IrModule, fn: string): string[] { + return mod.functions + .filter((f) => JSON.stringify(f.body).includes(`"fn":"${fn}"`)) + .map((f) => f.name); +} + +test("only hierarchies passed to an integrity call get attribute state and write checks", async () => { + const mod = await lowerModule(` +export {}; +class Point { x = 1; } +class Other { x = 1; } +function movePoint(p: Point): void { p.x = 2; } +function moveOther(o: Other): void { o.x = 2; } +const p = new Point(); +Object.freeze(p); +movePoint(p); +moveOther(new Other()); +`); + expect(fieldsOf(mod, "Point")).toContain("%attributes"); + expect(fieldsOf(mod, "Other")).not.toContain("%attributes"); + const checked = functionsCalling(mod, "class.throwReadOnly"); + expect(checked).toContain("movePoint"); + expect(checked).not.toContain("moveOther"); +}); + +test("a redefined field gets its own attribute word", async () => { + const mod = await lowerModule(` +export {}; +class Config { mode = "dev"; level = 1; } +const c = new Config(); +Object.defineProperty(c, "mode", { writable: false }); +`); + const fields = fieldsOf(mod, "Config"); + expect(fields).toContain("%attributes"); + expect(fields).toContain("%attr:mode"); + expect(fields).not.toContain("%attr:level"); +}); + +test("sloppy-mode writes skip a read-only field without throwing", async () => { + const mod = await lowerModule( + ` +class Box { constructor() { this.x = 1; } } +const b = new Box(); +Object.freeze(b); +b.x = 2; +`, + "main.cjs", + ); + expect(fieldsOf(mod, "Box")).toContain("%attributes"); + // Class bodies are strict mode code; the top-level write is not. + const throwing = functionsCalling(mod, "class.throwReadOnly"); + expect(throwing.length).toBeGreaterThan(0); + expect(throwing.every((name) => name.includes("constructor"))).toBe(true); +}); diff --git a/packages/compiler/test/class-method-mutations.test.ts b/packages/compiler/test/class-method-mutations.test.ts index 2258bd54a..b508a3949 100644 --- a/packages/compiler/test/class-method-mutations.test.ts +++ b/packages/compiler/test/class-method-mutations.test.ts @@ -114,3 +114,24 @@ test("a computed key in an Object.assign source keeps checked dispatch", async ( .replace("LITERAL", "Object.assign(target, { [key]: value })"); expect(await callersOf(source, "%dyn.class.properties")).not.toEqual([]); }); + +const range = ` +class Range { + hi: number; + constructor(hi: number) { this.hi = hi; } + *[Symbol.iterator](): Generator { for (let i = 0; i < this.hi; i++) yield i; } +} +const r = new Range(3); +console.log(r[Symbol.iterator]().next().value); +`; + +test("a well-known symbol method call lowers to the class's method", async () => { + expect(await callersOf(range, "%Range.sym:iterator")).not.toEqual([]); +}); + +test("a reflection call with a well-known symbol key keeps checked dispatch", async () => { + const write = "Object.defineProperty(r, Symbol.iterator, { value: function* () {} });\n"; + // The checked path reads the declared method through its value thunk. + const callers = await callersOf(`${range}${write}`, "%Range.sym:iterator"); + expect(callers.filter((name) => !name.startsWith("%method.value:"))).toEqual([]); +}); diff --git a/packages/compiler/test/local-class-private-boundaries.test.ts b/packages/compiler/test/local-class-private-boundaries.test.ts new file mode 100644 index 000000000..fdbe2e8f8 --- /dev/null +++ b/packages/compiler/test/local-class-private-boundaries.test.ts @@ -0,0 +1,63 @@ +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { analyze } from "../src/index.js"; + +function refusals(file: string, source: string): string[] { + const dir = mkdtempSync(join(tmpdir(), "scriptc-local-class-private-")); + try { + const entry = join(dir, file); + writeFileSync(entry, source); + const { coverage } = analyze(entry); + return [...coverage.diagnostics, ...(coverage.runtimeFences ?? [])].map((d) => d.message); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +// Function-local JavaScript classes compile private fields used only on +// `this` when the class is a named declaration: each evaluation's prototype +// methods check the receiver's evaluation against that binding. Private +// methods and accessors, class expressions, private names on other objects, +// and TypeScript classes keep the refusal. +test.each([ + [ + "a private method", + "main.js", + "function make() { class Box { #read() { return 1; } read() { return this.#read(); } } return new Box(); } console.log(make().read());", + ], + [ + "a private accessor", + "main.js", + "function make() { class Box { get #value() { return 1; } read() { return this.#value; } } return new Box(); } console.log(make().read());", + ], + [ + "a class expression", + "main.js", + "function make() { const Box = class { #value = 1; read() { return this.#value; } }; return new Box(); } console.log(make().read());", + ], + [ + "a private name on another object", + "main.js", + "function make() { class Box { #value = 1; has(other) { return #value in other; } } return new Box(); } console.log(make().has({}));", + ], + [ + "a TypeScript class", + "main.ts", + "function make() { class Box { #value = 1; read(): number { return this.#value; } } return new Box(); } console.log(make().read());", + ], +] as const)("function-local private names keep the refusal with %s", (_name, file, source) => { + expect(refusals(file, source)).toContainEqual( + expect.stringContaining("function-local classes with private brands"), + ); +}); + +test("function-local private fields used on this compile", () => { + expect( + refusals( + "main.js", + "function make() { class Box { #value = 1; read() { return this.#value; } has() { return #value in this; } } return new Box(); } const box = make(); console.log(box.read(), box.has());", + ), + ).toEqual([]); +}); diff --git a/packages/compiler/test/native-callable-values.test.ts b/packages/compiler/test/native-callable-values.test.ts index e64a9319e..6e65b0c99 100644 --- a/packages/compiler/test/native-callable-values.test.ts +++ b/packages/compiler/test/native-callable-values.test.ts @@ -102,19 +102,19 @@ test.each(["llvm"] as const)( writeFileSync( join(pkg, "index.js"), ` -const inspect = (value) => eval(value); +const inspect = (value) => process.on("__proto__", value); function stopped(value) { - eval(value); + process.on("__proto__", value); return { done() { return "wrong"; } }; } -try { console.log(stopped("0").done()); } -catch (error) { console.log(String(error).includes("SC2009")); } +try { console.log(stopped(() => {}).done()); } +catch (error) { console.log(String(error).includes("SC2020")); } const first = { inspect }; const second = { inspect }; console.log(typeof first.inspect, first.inspect === second.inspect); for (const callback of [first.inspect, second.inspect]) { - try { callback({}); } - catch (error) { console.log(String(error).includes("SC2009")); } + try { callback(() => {}); } + catch (error) { console.log(String(error).includes("SC2020")); } } `, ); diff --git a/packages/compiler/test/native-class-descriptors.test.ts b/packages/compiler/test/native-class-descriptors.test.ts index d4aff3949..bd0a06dac 100644 --- a/packages/compiler/test/native-class-descriptors.test.ts +++ b/packages/compiler/test/native-class-descriptors.test.ts @@ -12,7 +12,7 @@ function fixture(source: string) { } test.each([ - "Object.defineProperty(value, 'x', {value: 2, writable: false});", + "Object.defineProperty(value, 'x', {get() { return 2; }});", "Object.defineProperties(value, {x: {value: 2}});", ])("keeps unsafe native descriptor changes fenced: %s", (operation) => { const { dir, entry } = fixture( @@ -30,6 +30,42 @@ test.each([ } }); +test("data descriptor changes on native fields match Node", async () => { + const { dir, entry } = fixture(` +class Value { constructor() { this.x = 1; } } +const value = new Value(); +Object.defineProperty(value, 'x', { value: 2, writable: false }); +console.log(value.x, JSON.stringify(Object.getOwnPropertyDescriptor(value, 'x'))); +value.x = 3; +console.log(value.x, Object.keys(value)); +`); + try { + const result = await compile(entry, { + outDir: dir, + outPath: join(dir, "program"), + sanitize: process.env["SCRIPTC_SAN"] === "1", + }); + if (!result.ok) throw new Error(JSON.stringify(result.diagnostics)); + const run = spawnSync(result.binaryPath, { encoding: "utf8", timeout: 30_000 }); + const oracle = spawnSync(process.execPath, [entry], { encoding: "utf8", timeout: 30_000 }); + expect(run.error).toBeUndefined(); + expect(oracle.status).toBe(0); + expect({ + status: run.status, + signal: run.signal, + stdout: run.stdout, + stderr: run.stderr, + }).toEqual({ + status: oracle.status, + signal: oracle.signal, + stdout: oracle.stdout, + stderr: oracle.stderr, + }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + test("JS overrides with different return types match Node", async () => { const { dir, entry } = fixture(` class Base { copy(value = 1) { return value; } } diff --git a/packages/compiler/test/speculative-hoisted-functions.test.ts b/packages/compiler/test/speculative-hoisted-functions.test.ts index a58310b2a..79a56dcde 100644 --- a/packages/compiler/test/speculative-hoisted-functions.test.ts +++ b/packages/compiler/test/speculative-hoisted-functions.test.ts @@ -9,7 +9,9 @@ import type { IrModule } from "../src/ir/ir.js"; // list above the constructor. The initializer lowers speculatively first, // which hoists the constructor and the helper it calls. Those bodies are // kept, so their failing statements must keep runtime fences; dropping -// them left `isNum` without its return (an SC9002 trap at load). +// them left `isNum` without its return (an SC9002 trap at load). isNum's +// return reads the prototype through Reflect.getPrototypeOf, which has no +// lowering, so it fences. const source = ` function clone(config) { var P = (Num.prototype = { constructor: Num, toString: null }), @@ -20,7 +22,7 @@ function clone(config) { x.v = isNum(v) ? 0 : v * unit; } function isNum(v) { - return v instanceof Num || (!!v && v._isNum === true); + return Reflect.getPrototypeOf(v) === P || (!!v && v._isNum === true); } P._isNum = true; return Num; @@ -59,6 +61,6 @@ test("speculatively hoisted JavaScript functions keep their statement fences", a const mod = await lower(); const isNum = mod.functions.find((fn) => fn.name.endsWith("_isNum")); expect(isNum).toBeDefined(); - expect(fenceCodes(isNum!.body)).toEqual(["SC1090"]); + expect(fenceCodes(isNum!.body)).toEqual(["SC2020"]); expect(mod.functions.flatMap((fn) => fenceCodes(fn.body))).not.toContain("SC9002"); }); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5467-typed-array-loop-versions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5467-typed-array-loop-versions.ts.json new file mode 100644 index 000000000..a4be59696 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5467-typed-array-loop-versions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5467-typed-array-loop-versions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5468-typed-array-loop-aliasing.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5468-typed-array-loop-aliasing.ts.json new file mode 100644 index 000000000..517cbab4c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5468-typed-array-loop-aliasing.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5468-typed-array-loop-aliasing.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts.json new file mode 100644 index 000000000..79c7d0c63 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5607-string-char-compare.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5607-string-char-compare.ts.json new file mode 100644 index 000000000..eef427a5e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5607-string-char-compare.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5607-string-char-compare.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5608-string-char-switch.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5608-string-char-switch.ts.json new file mode 100644 index 000000000..855057006 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5608-string-char-switch.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5608-string-char-switch.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5609-string-switch-optional.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5609-string-switch-optional.ts.json new file mode 100644 index 000000000..7f0bf061d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5609-string-switch-optional.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5609-string-switch-optional.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5730-thread-slots-widened-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5730-thread-slots-widened-values.ts.json new file mode 100644 index 000000000..001c7ea32 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5730-thread-slots-widened-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5730-thread-slots-widened-values.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5750-string-from-char-codes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5750-string-from-char-codes.ts.json new file mode 100644 index 000000000..fab2fd504 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5750-string-from-char-codes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5750-string-from-char-codes.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5751-string-length-cache.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5751-string-length-cache.ts.json new file mode 100644 index 000000000..0eebe2326 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5751-string-length-cache.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5751-string-length-cache.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5775-md5-lengths.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5775-md5-lengths.ts.json new file mode 100644 index 000000000..84c394e4a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5775-md5-lengths.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5775-md5-lengths.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5779-sha512-lengths.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5779-sha512-lengths.ts.json new file mode 100644 index 000000000..60582ec60 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5779-sha512-lengths.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5779-sha512-lengths.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5816-fixed-into-unknown-rest.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5816-fixed-into-unknown-rest.ts.json new file mode 100644 index 000000000..8354f5c48 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5816-fixed-into-unknown-rest.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5816-fixed-into-unknown-rest.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5817-constructor-function-instanceof.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5817-constructor-function-instanceof.cjs.json new file mode 100644 index 000000000..d9ad97beb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5817-constructor-function-instanceof.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5817-constructor-function-instanceof.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5818-constructor-function-emitter-defaults.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5818-constructor-function-emitter-defaults.cjs.json new file mode 100644 index 000000000..f22e1aaeb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5818-constructor-function-emitter-defaults.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5818-constructor-function-emitter-defaults.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts.json new file mode 100644 index 000000000..85cf02e2e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/5819-node-stream-dyn-direct-pipe/sources.js", + "/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts.json new file mode 100644 index 000000000..49f4052b1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js", + "/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5876-readline-dispose-instanceof.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5876-readline-dispose-instanceof.ts.json new file mode 100644 index 000000000..959c704b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5876-readline-dispose-instanceof.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5876-readline-dispose-instanceof.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5877-readline-question-query.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5877-readline-question-query.cjs.json new file mode 100644 index 000000000..fe2bcfeee --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5877-readline-question-query.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5877-readline-question-query.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5878-readline-dynamic-streams.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5878-readline-dynamic-streams.mjs.json new file mode 100644 index 000000000..ddd665241 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5878-readline-dynamic-streams.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5878-readline-dynamic-streams.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5891-typed-array-field-borrows.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5891-typed-array-field-borrows.ts.json new file mode 100644 index 000000000..efd0044d0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5891-typed-array-field-borrows.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5891-typed-array-field-borrows.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5916-dyn-error-stack-property.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5916-dyn-error-stack-property.ts.json new file mode 100644 index 000000000..fa5b5460f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5916-dyn-error-stack-property.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5916-dyn-error-stack-property.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5983-runtime-constructor.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5983-runtime-constructor.ts.json new file mode 100644 index 000000000..004b1e350 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/5983-runtime-constructor.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/5983-runtime-constructor.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6004-class-instance-integrity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6004-class-instance-integrity.ts.json new file mode 100644 index 000000000..4d21236c0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6004-class-instance-integrity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6004-class-instance-integrity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6005-class-instance-integrity-sloppy.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6005-class-instance-integrity-sloppy.cjs.json new file mode 100644 index 000000000..1101d2180 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6005-class-instance-integrity-sloppy.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6005-class-instance-integrity-sloppy.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6055-class-generator-iterables.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6055-class-generator-iterables.ts.json new file mode 100644 index 000000000..661995e3f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6055-class-generator-iterables.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6055-class-generator-iterables.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6056-class-symbol-method-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6056-class-symbol-method-calls.ts.json new file mode 100644 index 000000000..445389b5e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6056-class-symbol-method-calls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6056-class-symbol-method-calls.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6058-class-view-asserted-bindings.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6058-class-view-asserted-bindings.ts.json new file mode 100644 index 000000000..b2a73c8a3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6058-class-view-asserted-bindings.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6058-class-view-asserted-bindings.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6061-type-only-class-fields/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6061-type-only-class-fields/main.ts.json new file mode 100644 index 000000000..68bdcec61 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6061-type-only-class-fields/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6061-type-only-class-fields/holder.ts", + "/tests/corpus/6061-type-only-class-fields/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6063-union-class-field-checks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6063-union-class-field-checks.ts.json new file mode 100644 index 000000000..9e48b2910 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6063-union-class-field-checks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6063-union-class-field-checks.ts" + ], + "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 index 5de83361a..36f02246c 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json @@ -12,7 +12,7 @@ "end": 376 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-date-parse-legacy.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-date-parse-legacy.ts.json new file mode 100644 index 000000000..b7cad9619 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6230-date-parse-legacy.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6230-date-parse-legacy.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6231-date-local-fields.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6231-date-local-fields.ts.json new file mode 100644 index 000000000..6f078d295 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6231-date-local-fields.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6231-date-local-fields.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6232-date-fields-js.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6232-date-fields-js.cjs.json new file mode 100644 index 000000000..10f48c83a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6232-date-fields-js.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6232-date-fields-js.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6233-object-identity-dynamic.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6233-object-identity-dynamic.cjs.json new file mode 100644 index 000000000..a5dd87184 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6233-object-identity-dynamic.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6233-object-identity-dynamic.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6234-object-prototype-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6234-object-prototype-values.cjs.json new file mode 100644 index 000000000..bce305944 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6234-object-prototype-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6234-object-prototype-values.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6235-object-identity-typed.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6235-object-identity-typed.ts.json new file mode 100644 index 000000000..c48f4164b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6235-object-identity-typed.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6235-object-identity-typed.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6236-builtin-function-values.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6236-builtin-function-values.cjs.json new file mode 100644 index 000000000..21b9f85c2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6236-builtin-function-values.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6236-builtin-function-values.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6341-throw-array-identity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6341-throw-array-identity.ts.json new file mode 100644 index 000000000..b2ecfcfa8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6341-throw-array-identity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6341-throw-array-identity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6342-throw-builtin-identity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6342-throw-builtin-identity.ts.json new file mode 100644 index 000000000..09b036461 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6342-throw-builtin-identity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6342-throw-builtin-identity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6345-inspect-dyn-holes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6345-inspect-dyn-holes.ts.json new file mode 100644 index 000000000..d9027a759 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6345-inspect-dyn-holes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6345-inspect-dyn-holes.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6346-inspect-dyn-dataview.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6346-inspect-dyn-dataview.ts.json new file mode 100644 index 000000000..3e144e748 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6346-inspect-dyn-dataview.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6346-inspect-dyn-dataview.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6347-dataview-methods-js.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6347-dataview-methods-js.cjs.json new file mode 100644 index 000000000..91c408e7a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6347-dataview-methods-js.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6347-dataview-methods-js.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6348-errno-code-own-field.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6348-errno-code-own-field.ts.json new file mode 100644 index 000000000..b5c392112 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6348-errno-code-own-field.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6348-errno-code-own-field.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6356-format-integer-dyn.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6356-format-integer-dyn.ts.json new file mode 100644 index 000000000..6e1082a83 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6356-format-integer-dyn.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6356-format-integer-dyn.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6380-variadic-wrapper-arguments-apply.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6380-variadic-wrapper-arguments-apply.ts.json new file mode 100644 index 000000000..df0124219 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6380-variadic-wrapper-arguments-apply.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6380-variadic-wrapper-arguments-apply.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6381-variadic-wrapper-async.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6381-variadic-wrapper-async.ts.json new file mode 100644 index 000000000..2138df43d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6381-variadic-wrapper-async.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6381-variadic-wrapper-async.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6382-variadic-wrapper-methods-this.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6382-variadic-wrapper-methods-this.ts.json new file mode 100644 index 000000000..2135f8b6f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6382-variadic-wrapper-methods-this.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6382-variadic-wrapper-methods-this.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6383-variadic-wrapper-rest-tuple.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6383-variadic-wrapper-rest-tuple.ts.json new file mode 100644 index 000000000..a2f7f32c4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6383-variadic-wrapper-rest-tuple.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6383-variadic-wrapper-rest-tuple.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6384-variadic-wrapper-nested.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6384-variadic-wrapper-nested.ts.json new file mode 100644 index 000000000..d69618e3a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6384-variadic-wrapper-nested.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6384-variadic-wrapper-nested.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6385-variadic-wrapper-shared-local-names.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6385-variadic-wrapper-shared-local-names.ts.json new file mode 100644 index 000000000..5791565f2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6385-variadic-wrapper-shared-local-names.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6385-variadic-wrapper-shared-local-names.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6390-catch-binding-predicate.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6390-catch-binding-predicate.ts.json new file mode 100644 index 000000000..fcfb2852f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6390-catch-binding-predicate.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6390-catch-binding-predicate.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6391-nullish-function-default.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6391-nullish-function-default.ts.json new file mode 100644 index 000000000..8200d301e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6391-nullish-function-default.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6391-nullish-function-default.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6392-optional-chain-protocol-calls.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6392-optional-chain-protocol-calls.ts.json new file mode 100644 index 000000000..f2343714c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6392-optional-chain-protocol-calls.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6392-optional-chain-protocol-calls.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6393-function-types-any-params.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6393-function-types-any-params.ts.json new file mode 100644 index 000000000..af0a9abe7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6393-function-types-any-params.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6393-function-types-any-params.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6394-in-key-error-subclasses.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6394-in-key-error-subclasses.ts.json new file mode 100644 index 000000000..9df1e1014 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6394-in-key-error-subclasses.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6394-in-key-error-subclasses.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6395-generic-object-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6395-generic-object-entries.ts.json new file mode 100644 index 000000000..879a09388 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6395-generic-object-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6395-generic-object-entries.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6396-generic-any-instantiation.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6396-generic-any-instantiation.ts.json new file mode 100644 index 000000000..da41452ad --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6396-generic-any-instantiation.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6396-generic-any-instantiation.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6397-event-loop-metrics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6397-event-loop-metrics.ts.json new file mode 100644 index 000000000..0a74534b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6397-event-loop-metrics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6397-event-loop-metrics.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6398-conditional-fixed-arity.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6398-conditional-fixed-arity.ts.json new file mode 100644 index 000000000..878f45b09 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6398-conditional-fixed-arity.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6398-conditional-fixed-arity.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-dyn-error-stack-property-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-dyn-error-stack-property-js.js.json new file mode 100644 index 000000000..7bea48883 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-dyn-error-stack-property-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6400-dyn-error-stack-property-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-override-any-base.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-override-any-base.ts.json new file mode 100644 index 000000000..a885a5175 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6400-override-any-base.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6400-override-any-base.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-dyn-error-own-property-names.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-dyn-error-own-property-names.ts.json new file mode 100644 index 000000000..cf96c4f96 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-dyn-error-own-property-names.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6401-dyn-error-own-property-names.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-override-param-variance.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-override-param-variance.ts.json new file mode 100644 index 000000000..ee14b5af3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6401-override-param-variance.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6401-override-param-variance.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-dyn-error-own-property-names-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-dyn-error-own-property-names-js.js.json new file mode 100644 index 000000000..49cf88884 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-dyn-error-own-property-names-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6402-dyn-error-own-property-names-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-override-js-base/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-override-js-base/main.ts.json new file mode 100644 index 000000000..3ac1bf1fe --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6402-override-js-base/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6402-override-js-base/pool.js", + "/tests/corpus/6402-override-js-base/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-code-write.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-code-write.ts.json new file mode 100644 index 000000000..b673f7920 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-code-write.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6403-error-code-write.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-own-name-order.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-own-name-order.ts.json new file mode 100644 index 000000000..da39be0a0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6403-error-own-name-order.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6403-error-own-name-order.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-in-and-assign.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-in-and-assign.ts.json new file mode 100644 index 000000000..c880bc57b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-in-and-assign.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6404-error-in-and-assign.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-own-name-order-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-own-name-order-js.js.json new file mode 100644 index 000000000..b19997ac3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6404-error-own-name-order-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6404-error-own-name-order-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6405-wrapped-default-parameters.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6405-wrapped-default-parameters.ts.json new file mode 100644 index 000000000..8be83f858 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6405-wrapped-default-parameters.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6405-wrapped-default-parameters.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6406-npm-instance-receiver/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6406-npm-instance-receiver/main.ts.json new file mode 100644 index 000000000..e651808f7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6406-npm-instance-receiver/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6406-npm-instance-receiver/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-dyn-bytes-in.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-dyn-bytes-in.ts.json new file mode 100644 index 000000000..e4b1b5d88 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6407-dyn-bytes-in.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6407-dyn-bytes-in.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-dyn-bytes-in-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-dyn-bytes-in-js.js.json new file mode 100644 index 000000000..30bd05d7e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6408-dyn-bytes-in-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6408-dyn-bytes-in-js.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6409-catch-binding-shapes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6409-catch-binding-shapes.ts.json new file mode 100644 index 000000000..57e3aeaf1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6409-catch-binding-shapes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6409-catch-binding-shapes.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-json-mixed-scalar-tables/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-json-mixed-scalar-tables/main.ts.json new file mode 100644 index 000000000..c7ec1ab56 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-json-mixed-scalar-tables/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6410-json-mixed-scalar-tables/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-wrapper-untyped-results.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-wrapper-untyped-results.ts.json new file mode 100644 index 000000000..20c1be33a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6410-wrapper-untyped-results.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6410-wrapper-untyped-results.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-prototype-reset-constructors/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-prototype-reset-constructors/main.ts.json new file mode 100644 index 000000000..adfb3989d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-prototype-reset-constructors/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6411-prototype-reset-constructors/issues.js", + "/tests/corpus/6411-prototype-reset-constructors/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-union-array-tuple-destructuring.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-union-array-tuple-destructuring.ts.json new file mode 100644 index 000000000..94d8b78e7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6411-union-array-tuple-destructuring.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6411-union-array-tuple-destructuring.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6412-js-error-tostring-override/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6412-js-error-tostring-override/main.ts.json new file mode 100644 index 000000000..145c02428 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6412-js-error-tostring-override/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6412-js-error-tostring-override/errors.js", + "/tests/corpus/6412-js-error-tostring-override/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6413-override-js-async-base/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6413-override-js-async-base/main.ts.json new file mode 100644 index 000000000..de5fe5afb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6413-override-js-async-base/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6413-override-js-async-base/lock.js", + "/tests/corpus/6413-override-js-async-base/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6414-catch-values-roundtrip.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6414-catch-values-roundtrip.ts.json new file mode 100644 index 000000000..797bd8ca2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6414-catch-values-roundtrip.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6414-catch-values-roundtrip.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6415-error-code-dynamic-writes.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6415-error-code-dynamic-writes.ts.json new file mode 100644 index 000000000..6500a5b3b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6415-error-code-dynamic-writes.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6415-error-code-dynamic-writes.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6416-tls-session-reused.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6416-tls-session-reused.ts.json new file mode 100644 index 000000000..8b84ce6d3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6416-tls-session-reused.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6416-tls-session-reused.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6417-object-assign-class-target.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6417-object-assign-class-target.ts.json new file mode 100644 index 000000000..5832b91f9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6417-object-assign-class-target.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6417-object-assign-class-target.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6418-nullish-assign-index-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6418-nullish-assign-index-record.ts.json new file mode 100644 index 000000000..7a030cd19 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6418-nullish-assign-index-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6418-nullish-assign-index-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6420-process-send-presence.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6420-process-send-presence.ts.json new file mode 100644 index 000000000..69db221e2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6420-process-send-presence.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6420-process-send-presence.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6421-process-send-presence-fork/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6421-process-send-presence-fork/main.ts.json new file mode 100644 index 000000000..abde055bf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6421-process-send-presence-fork/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6421-process-send-presence-fork/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6422-process-exitcode-read.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6422-process-exitcode-read.ts.json new file mode 100644 index 000000000..15abd3cdc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6422-process-exitcode-read.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6422-process-exitcode-read.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6423-process-exitcode-default.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6423-process-exitcode-default.ts.json new file mode 100644 index 000000000..31bf2584e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6423-process-exitcode-default.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6423-process-exitcode-default.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6424-process-exit-keeps-exitcode.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6424-process-exit-keeps-exitcode.ts.json new file mode 100644 index 000000000..ce051e3fa --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6424-process-exit-keeps-exitcode.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6424-process-exit-keeps-exitcode.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6425-stream-hook-wider-chunk.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6425-stream-hook-wider-chunk.ts.json new file mode 100644 index 000000000..4c6db1400 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6425-stream-hook-wider-chunk.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6425-stream-hook-wider-chunk.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6426-promise-race-void-entry.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6426-promise-race-void-entry.ts.json new file mode 100644 index 000000000..e4d66ecf4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6426-promise-race-void-entry.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6426-promise-race-void-entry.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6427-http-request-options-type.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6427-http-request-options-type.ts.json new file mode 100644 index 000000000..3e403ece6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6427-http-request-options-type.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6427-http-request-options-type.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6428-promisify-callback-functions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6428-promisify-callback-functions.ts.json new file mode 100644 index 000000000..76c3e65d0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6428-promisify-callback-functions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6428-promisify-callback-functions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6429-esm-js-dirname.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6429-esm-js-dirname.mjs.json new file mode 100644 index 000000000..0168f85ee --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6429-esm-js-dirname.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6429-esm-js-dirname.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6430-npm-constructor-function-class/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6430-npm-constructor-function-class/main.ts.json new file mode 100644 index 000000000..76bd8e813 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6430-npm-constructor-function-class/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6430-npm-constructor-function-class/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6431-cjs-exports-rebinding/main.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6431-cjs-exports-rebinding/main.cjs.json new file mode 100644 index 000000000..8fc4a2acc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6431-cjs-exports-rebinding/main.cjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/corpus/6431-cjs-exports-rebinding/lib.cjs", + "/tests/corpus/6431-cjs-exports-rebinding/extended.cjs", + "/tests/corpus/6431-cjs-exports-rebinding/main.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6440-http-socket-path.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6440-http-socket-path.ts.json new file mode 100644 index 000000000..63142a38a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6440-http-socket-path.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6440-http-socket-path.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6441-http-request-options-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6441-http-request-options-record.ts.json new file mode 100644 index 000000000..d596d3442 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6441-http-request-options-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6441-http-request-options-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6442-https-request-options-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6442-https-request-options-record.ts.json new file mode 100644 index 000000000..ccc648aee --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6442-https-request-options-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6442-https-request-options-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6443-net-lookup-http-close-text.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6443-net-lookup-http-close-text.ts.json new file mode 100644 index 000000000..cad9675b2 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6443-net-lookup-http-close-text.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6443-net-lookup-http-close-text.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6445-rest-function-fixed-slot.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6445-rest-function-fixed-slot.ts.json new file mode 100644 index 000000000..b39c2fa38 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6445-rest-function-fixed-slot.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6445-rest-function-fixed-slot.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6446-rest-function-nested-declaration.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6446-rest-function-nested-declaration.ts.json new file mode 100644 index 000000000..41c034395 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6446-rest-function-nested-declaration.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6446-rest-function-nested-declaration.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-builtin-base-class-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-builtin-base-class-values.ts.json new file mode 100644 index 000000000..bdd8fcbc8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-builtin-base-class-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6450-builtin-base-class-values.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json new file mode 100644 index 000000000..bb709e7f3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6450-promise-race-never-entry.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6450-promise-race-never-entry.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-array-length-fill-spread.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-array-length-fill-spread.ts.json new file mode 100644 index 000000000..0302374fc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-array-length-fill-spread.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6451-array-length-fill-spread.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-builtin-base-class-expressions.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-builtin-base-class-expressions.cjs.json new file mode 100644 index 000000000..34cde5811 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6451-builtin-base-class-expressions.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6451-builtin-base-class-expressions.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6453-constructor-function-emitter.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6453-constructor-function-emitter.cjs.json new file mode 100644 index 000000000..ae5c0050e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6453-constructor-function-emitter.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6453-constructor-function-emitter.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6454-constructor-function-stream-bases.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6454-constructor-function-stream-bases.cjs.json new file mode 100644 index 000000000..bc48a7f35 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6454-constructor-function-stream-bases.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6454-constructor-function-stream-bases.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6460-in-union-optional-slots.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6460-in-union-optional-slots.ts.json new file mode 100644 index 000000000..8491507eb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6460-in-union-optional-slots.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6460-in-union-optional-slots.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6461-predicate-intersection-narrowing.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6461-predicate-intersection-narrowing.ts.json new file mode 100644 index 000000000..3ce078006 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6461-predicate-intersection-narrowing.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6461-predicate-intersection-narrowing.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6462-assertion-attached-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6462-assertion-attached-members.ts.json new file mode 100644 index 000000000..36e333373 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6462-assertion-attached-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6462-assertion-attached-members.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6463-node-readable-stream-producers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6463-node-readable-stream-producers.ts.json new file mode 100644 index 000000000..e413951ae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6463-node-readable-stream-producers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6463-node-readable-stream-producers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6464-node-writable-stream-producers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6464-node-writable-stream-producers.ts.json new file mode 100644 index 000000000..fd805a2af --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6464-node-writable-stream-producers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6464-node-writable-stream-producers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-node-stream-js-boundary/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-node-stream-js-boundary/main.ts.json new file mode 100644 index 000000000..95278eab8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-node-stream-js-boundary/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6465-node-stream-js-boundary/consume.js", + "/tests/corpus/6465-node-stream-js-boundary/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-optional-members-over-index-signature.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-optional-members-over-index-signature.ts.json new file mode 100644 index 000000000..91a157207 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6465-optional-members-over-index-signature.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6465-optional-members-over-index-signature.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-never-assertion-argument.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-never-assertion-argument.ts.json new file mode 100644 index 000000000..17f25aa22 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-never-assertion-argument.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6466-never-assertion-argument.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-node-stdio-stream-surface.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-node-stdio-stream-surface.ts.json new file mode 100644 index 000000000..8ebcddd22 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6466-node-stdio-stream-surface.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6466-node-stdio-stream-surface.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-fromentries-record-assertion.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-fromentries-record-assertion.ts.json new file mode 100644 index 000000000..21ca66953 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-fromentries-record-assertion.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6467-fromentries-record-assertion.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-node-stream-untyped-sources/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-node-stream-untyped-sources/main.ts.json new file mode 100644 index 000000000..dc843c047 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6467-node-stream-untyped-sources/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6467-node-stream-untyped-sources/sources.js", + "/tests/corpus/6467-node-stream-untyped-sources/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json new file mode 100644 index 000000000..0b4118c70 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-generic-unknown-record-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6468-generic-unknown-record-entries.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-node-stdio-writable-slot.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-node-stdio-writable-slot.ts.json new file mode 100644 index 000000000..baf428dee --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6468-node-stdio-writable-slot.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6468-node-stdio-writable-slot.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts.json new file mode 100644 index 000000000..60b1360b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js", + "/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json new file mode 100644 index 000000000..eb1120cae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-any-destructuring.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6470-any-destructuring.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-promise-reject-async-return.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-promise-reject-async-return.ts.json new file mode 100644 index 000000000..dda9bd1e8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-promise-reject-async-return.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6470-promise-reject-async-return.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-stream-transform-any-data-callback.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-stream-transform-any-data-callback.ts.json new file mode 100644 index 000000000..3d4a39486 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6470-stream-transform-any-data-callback.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6470-stream-transform-any-data-callback.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json new file mode 100644 index 000000000..463bc3a67 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-any-method-chains.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6471-any-method-chains.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-stream-data-buffer-or-string.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-stream-data-buffer-or-string.ts.json new file mode 100644 index 000000000..e8ff1e084 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-stream-data-buffer-or-string.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6471-stream-data-buffer-or-string.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-union-literal-results-into-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-union-literal-results-into-record.ts.json new file mode 100644 index 000000000..1a02071a1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6471-union-literal-results-into-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6471-union-literal-results-into-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-asserted-member-read.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-asserted-member-read.ts.json new file mode 100644 index 000000000..dfb88018b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-asserted-member-read.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6472-asserted-member-read.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-child-data-unknown-chunk.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-child-data-unknown-chunk.ts.json new file mode 100644 index 000000000..3c7183bb6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-child-data-unknown-chunk.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6472-child-data-unknown-chunk.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json new file mode 100644 index 000000000..ec90234c8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6472-namespace-destructuring/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6472-namespace-destructuring/lib.ts", + "/tests/corpus/6472-namespace-destructuring/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json new file mode 100644 index 000000000..1747655f0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-any-parameter-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6473-any-parameter-values.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-unknown-spread-into-index-record.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-unknown-spread-into-index-record.ts.json new file mode 100644 index 000000000..abf47b7db --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6473-unknown-spread-into-index-record.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6473-unknown-spread-into-index-record.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json new file mode 100644 index 000000000..6a7e7a966 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-async-any-callbacks/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6474-async-any-callbacks/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-index-record-spread-literals.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-index-record-spread-literals.ts.json new file mode 100644 index 000000000..8991f227d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6474-index-record-spread-literals.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6474-index-record-spread-literals.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-dynamic-spread-literals.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-dynamic-spread-literals.ts.json new file mode 100644 index 000000000..29207c929 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-dynamic-spread-literals.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6475-dynamic-spread-literals.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json new file mode 100644 index 000000000..d27bf5fd6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6475-js-promise-resolve-adoption/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6475-js-promise-resolve-adoption/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-boolean-callbacks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-boolean-callbacks.ts.json new file mode 100644 index 000000000..3279178b8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-boolean-callbacks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6480-boolean-callbacks.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json new file mode 100644 index 000000000..38b99f9fc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-catch-typeof-untyped-throws.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6480-catch-typeof-untyped-throws.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-generic-callback-extended-results.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-generic-callback-extended-results.ts.json new file mode 100644 index 000000000..d3b5c41b7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-generic-callback-extended-results.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6480-generic-callback-extended-results.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-node-stream-dyn-readable/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-node-stream-dyn-readable/main.ts.json new file mode 100644 index 000000000..b3698b91c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6480-node-stream-dyn-readable/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6480-node-stream-dyn-readable/sources.js", + "/tests/corpus/6480-node-stream-dyn-readable/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-enum-runtime-keys.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-enum-runtime-keys.ts.json new file mode 100644 index 000000000..7fc1885aa --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-enum-runtime-keys.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6481-enum-runtime-keys.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-node-stream-dyn-writable/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-node-stream-dyn-writable/main.ts.json new file mode 100644 index 000000000..45b8ebb8a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6481-node-stream-dyn-writable/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6481-node-stream-dyn-writable/sources.js", + "/tests/corpus/6481-node-stream-dyn-writable/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-node-stream-dyn-helpers/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-node-stream-dyn-helpers/main.ts.json new file mode 100644 index 000000000..b45958c33 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-node-stream-dyn-helpers/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6482-node-stream-dyn-helpers/sources.js", + "/tests/corpus/6482-node-stream-dyn-helpers/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-promise-all-tuples.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-promise-all-tuples.ts.json new file mode 100644 index 000000000..5ceea99a5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6482-promise-all-tuples.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6482-promise-all-tuples.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6483-nullable-regex-receivers.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6483-nullable-regex-receivers.ts.json new file mode 100644 index 000000000..6a6263bbc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6483-nullable-regex-receivers.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6483-nullable-regex-receivers.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json new file mode 100644 index 000000000..9c99e2ab0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-events-once-signal.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6490-events-once-signal.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json new file mode 100644 index 000000000..dd95018f0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-js-named-promise-executor/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6490-js-named-promise-executor/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-literal-key-field-updates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-literal-key-field-updates.ts.json new file mode 100644 index 000000000..1e4e1e939 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6490-literal-key-field-updates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6490-literal-key-field-updates.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json new file mode 100644 index 000000000..bc69a66e3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-catch-any-bindings.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6491-catch-any-bindings.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json new file mode 100644 index 000000000..4fdbe7a1d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-events-once-streams.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6491-events-once-streams.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-index-record-updates.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-index-record-updates.ts.json new file mode 100644 index 000000000..babda8c3c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6491-index-record-updates.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6491-index-record-updates.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json new file mode 100644 index 000000000..8fa2ceb9a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6492-promise-race-combined.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6492-promise-race-combined.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json new file mode 100644 index 000000000..bb755b9a7 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6493-stream-function-callbacks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6493-stream-function-callbacks.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-default-export-class-members/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-default-export-class-members/main.ts.json new file mode 100644 index 000000000..24634cff1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-default-export-class-members/main.ts.json @@ -0,0 +1,9 @@ +{ + "order": [ + "/tests/corpus/6494-default-export-class-members/lib/date.js", + "/tests/corpus/6494-default-export-class-members/lib/error.js", + "/tests/corpus/6494-default-export-class-members/lib/index.js", + "/tests/corpus/6494-default-export-class-members/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json new file mode 100644 index 000000000..77f43443e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6494-events-once-dyn-stream-slot/main.ts.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/6494-events-once-dyn-stream-slot/sources.js", + "/tests/corpus/6494-events-once-dyn-stream-slot/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json new file mode 100644 index 000000000..48964b0a6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6560-constructor-function-readable.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6560-constructor-function-readable.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json new file mode 100644 index 000000000..603b083d1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6561-constructor-function-writable.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6561-constructor-function-writable.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json new file mode 100644 index 000000000..c5d3d1a62 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6562-constructor-function-transform.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6562-constructor-function-transform.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json new file mode 100644 index 000000000..6e7f693c1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6563-constructor-function-stream-errors.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6563-constructor-function-stream-errors.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json new file mode 100644 index 000000000..52d8e55ff --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6564-constructor-function-stream-late-hooks.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6564-constructor-function-stream-late-hooks.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json new file mode 100644 index 000000000..aa907b2ae --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/761-any-objects.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/761-any-objects.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-class-hierarchy-npm/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-class-hierarchy-npm/main.ts.json new file mode 100644 index 000000000..f44d1965c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-class-hierarchy-npm/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/cjs-class-hierarchy-npm/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-open-options-npm/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-open-options-npm/main.ts.json new file mode 100644 index 000000000..3c3043a09 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-open-options-npm/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/cjs-open-options-npm/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-strict-arguments.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-strict-arguments.cjs.json new file mode 100644 index 000000000..4e52c7e57 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-strict-arguments.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/cjs-strict-arguments.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-symbol-key-collisions/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-symbol-key-collisions/main.js.json new file mode 100644 index 000000000..205da46f3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-symbol-key-collisions/main.js.json @@ -0,0 +1,10 @@ +{ + "order": [ + "/tests/corpus/cjs-symbol-key-collisions/core-symbols.js", + "/tests/corpus/cjs-symbol-key-collisions/client.js", + "/tests/corpus/cjs-symbol-key-collisions/mock-symbols.js", + "/tests/corpus/cjs-symbol-key-collisions/mock-client.js", + "/tests/corpus/cjs-symbol-key-collisions/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-symbol-key-table/main.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-symbol-key-table/main.js.json new file mode 100644 index 000000000..3975cce3a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-symbol-key-table/main.js.json @@ -0,0 +1,9 @@ +{ + "order": [ + "/tests/corpus/cjs-symbol-key-table/symbols.js", + "/tests/corpus/cjs-symbol-key-table/queue.js", + "/tests/corpus/cjs-symbol-key-table/upper-queue.js", + "/tests/corpus/cjs-symbol-key-table/main.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-unmapped-arguments.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-unmapped-arguments.cjs.json new file mode 100644 index 000000000..bff6dd797 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/cjs-unmapped-arguments.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/cjs-unmapped-arguments.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.cjs.json new file mode 100644 index 000000000..04ce82284 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/code-generation-disallowed.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.ts.json new file mode 100644 index 000000000..919dee207 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/code-generation-disallowed.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/code-generation-disallowed.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/comma-null-union-return.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/comma-null-union-return.cjs.json new file mode 100644 index 000000000..1fad3c559 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/comma-null-union-return.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/comma-null-union-return.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-lookup.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-lookup.ts.json new file mode 100644 index 000000000..ef705d6a8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-lookup.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dns-promises-lookup.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-untyped.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-untyped.cjs.json new file mode 100644 index 000000000..010755d6b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/dns-promises-untyped.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/dns-promises-untyped.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-detached-methods.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-detached-methods.js.json new file mode 100644 index 000000000..9eb8fdf73 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-detached-methods.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/function-local-detached-methods.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-detached-methods.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-detached-methods.ts.json new file mode 100644 index 000000000..6ef52390d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-detached-methods.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/function-local-detached-methods.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-private-evaluations.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-private-evaluations.js.json new file mode 100644 index 000000000..0216d2c2f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-private-evaluations.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/function-local-private-evaluations.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-private-fields.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-private-fields.js.json new file mode 100644 index 000000000..d63dfd6a6 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/function-local-private-fields.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/function-local-private-fields.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/graceful-fs-alias/main.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/graceful-fs-alias/main.cjs.json new file mode 100644 index 000000000..7551f04a5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/graceful-fs-alias/main.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/graceful-fs-alias/main.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/local-class-detached-method.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/local-class-detached-method.ts.json new file mode 100644 index 000000000..6d311fce9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/local-class-detached-method.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/local-class-detached-method.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-accessors-dyn.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-accessors-dyn.ts.json new file mode 100644 index 000000000..ffe0c75d3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/object-literal-accessors-dyn.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/object-literal-accessors-dyn.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/private-in-checked-primitives.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/private-in-checked-primitives.js.json new file mode 100644 index 000000000..1e5b83999 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/private-in-checked-primitives.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/private-in-checked-primitives.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/required-module-truthiness.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/required-module-truthiness.cjs.json new file mode 100644 index 000000000..101bb2d57 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/required-module-truthiness.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/required-module-truthiness.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-readable.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-readable.ts.json new file mode 100644 index 000000000..f511fabff --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-readable.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/stream-web-readable.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-require.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-require.cjs.json new file mode 100644 index 000000000..e29438aa8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/stream-web-require.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/stream-web-require.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/strict-arguments-parameters.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/strict-arguments-parameters.cjs.json new file mode 100644 index 000000000..8c9cc9279 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/strict-arguments-parameters.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/strict-arguments-parameters.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent-probe.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent-probe.js.json new file mode 100644 index 000000000..8ab7b473c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent-probe.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/temporal-absent-probe.js" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent.ts.json new file mode 100644 index 000000000..9c7899eca --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/temporal-absent.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/temporal-absent.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-heap-statistics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-heap-statistics.ts.json new file mode 100644 index 000000000..6dddee82c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-heap-statistics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/v8-heap-statistics.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-require-optional.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-require-optional.cjs.json new file mode 100644 index 000000000..ca65df774 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/v8-require-optional.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/v8-require-optional.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-api.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-api.mjs.json new file mode 100644 index 000000000..f3e1a711b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-api.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-api.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-dylink/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-dylink/main.mjs.json new file mode 100644 index 000000000..14b064864 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-dylink/main.mjs.json @@ -0,0 +1,7 @@ +{ + "order": [ + "/tests/corpus/webassembly-dylink/extensions.js", + "/tests/corpus/webassembly-dylink/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-externref-lifetimes.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-externref-lifetimes.mjs.json new file mode 100644 index 000000000..7d264b2eb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-externref-lifetimes.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-externref-lifetimes.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-jsapi.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-jsapi.mjs.json new file mode 100644 index 000000000..4f8ee17cf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-jsapi.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-jsapi.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-lifetimes.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-lifetimes.mjs.json new file mode 100644 index 000000000..c9ab8d500 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-lifetimes.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-lifetimes.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-control/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-control/main.mjs.json new file mode 100644 index 000000000..58a263265 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-control/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-control/data.mjs", + "/tests/corpus/webassembly-spec-control/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-float/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-float/main.mjs.json new file mode 100644 index 000000000..e871d3b1d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-float/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-float/data.mjs", + "/tests/corpus/webassembly-spec-float/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-imports/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-imports/main.mjs.json new file mode 100644 index 000000000..a23646c06 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-imports/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-imports/data.mjs", + "/tests/corpus/webassembly-spec-imports/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-int/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-int/main.mjs.json new file mode 100644 index 000000000..d19226a63 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-int/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-int/data.mjs", + "/tests/corpus/webassembly-spec-int/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-memory/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-memory/main.mjs.json new file mode 100644 index 000000000..4510ade3a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-memory/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-memory/data.mjs", + "/tests/corpus/webassembly-spec-memory/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-table/main.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-table/main.mjs.json new file mode 100644 index 000000000..50409b1d0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-spec-table/main.mjs.json @@ -0,0 +1,8 @@ +{ + "order": [ + "/tests/wasm-spec/runner.mjs", + "/tests/corpus/webassembly-spec-table/data.mjs", + "/tests/corpus/webassembly-spec-table/main.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-typed.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-typed.ts.json new file mode 100644 index 000000000..7a3adb475 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-typed.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-typed.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-worker.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-worker.ts.json new file mode 100644 index 000000000..2b4ffd4ba --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-worker.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-worker.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-wpt-jsapi.mjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-wpt-jsapi.mjs.json new file mode 100644 index 000000000..dd80857d8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/webassembly-wpt-jsapi.mjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/webassembly-wpt-jsapi.mjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/boot-path-node-surface-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/boot-path-node-surface-fences.ts.json new file mode 100644 index 000000000..de58c3423 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/boot-path-node-surface-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/boot-path-node-surface-fences.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-base-class-value-arguments.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-base-class-value-arguments.ts.json new file mode 100644 index 000000000..622882c24 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-base-class-value-arguments.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/builtin-base-class-value-arguments.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-constructor-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-constructor-members.ts.json new file mode 100644 index 000000000..ae3a963f3 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/builtin-constructor-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/builtin-constructor-members.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/cjs-member-alias-writes/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/cjs-member-alias-writes/main.ts.json new file mode 100644 index 000000000..bd3ba799d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/cjs-member-alias-writes/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/cjs-member-alias-writes/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/class-field-accessor-descriptor.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/class-field-accessor-descriptor.ts.json new file mode 100644 index 000000000..3d6639169 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/class-field-accessor-descriptor.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/class-field-accessor-descriptor.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/fromentries-aliased-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/fromentries-aliased-entries.ts.json new file mode 100644 index 000000000..7388f2b71 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/fromentries-aliased-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/fromentries-aliased-entries.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/imports.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/imports.ts.json index 7b18f03a0..980cd1fd7 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/imports.ts.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/imports.ts.json @@ -21,7 +21,7 @@ "end": 41 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-conditional.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-conditional.ts.json new file mode 100644 index 000000000..2014c8620 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-conditional.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/inferred-callback-drops-conditional.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-satisfies.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-satisfies.ts.json new file mode 100644 index 000000000..44c5e8bbc --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops-satisfies.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/inferred-callback-drops-satisfies.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops.ts.json new file mode 100644 index 000000000..a6b592336 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/inferred-callback-drops.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/inferred-callback-drops.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/module-out-of-scope.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/module-out-of-scope.ts.json index 1937e357b..8ec8127e2 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/module-out-of-scope.ts.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/module-out-of-scope.ts.json @@ -5,11 +5,11 @@ "diags": [ { "code": "SC0001", - "message": "Cannot find name 'node:v8'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", + "message": "Cannot find name 'node:vm'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 381, - "end": 390 + "start": 384, + "end": 393 } }, { @@ -17,8 +17,8 @@ "message": "Cannot find name 'domain'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 415, - "end": 423 + "start": 418, + "end": 426 } }, { @@ -26,8 +26,8 @@ "message": "Cannot find name 'node:sqlite'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 454, - "end": 467 + "start": 457, + "end": 470 } }, { @@ -35,8 +35,8 @@ "message": "Cannot find module '_http_common' or its corresponding type declarations.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 496, - "end": 510 + "start": 499, + "end": 513 } }, { @@ -44,84 +44,84 @@ "message": "Cannot find name 'node:inspector'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 533, - "end": 549 + "start": 536, + "end": 552 } }, { "code": "SC0001", - "message": "Cannot find name 'node:stream/web'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", + "message": "Cannot find name 'node:repl'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 582, - "end": 599 + "start": 576, + "end": 587 } }, { "code": "SC1010", - "message": "the 'node:v8' module (it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe) is not supported yet", + "message": "the 'node:vm' module (it compiles and runs JavaScript source at runtime, and a compiled binary embeds no JavaScript engine to run it) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 347, - "end": 391 + "start": 352, + "end": 394 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", "message": "the 'domain' module (deprecated in Node and slated for removal; its implicit error interception hooks every async callback at engine level, which is not modeled) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 392, - "end": 424 + "start": 395, + "end": 427 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", "message": "the 'node:sqlite' module (it wraps the SQLite library bundled into the node executable, and scriptc binaries bundle no SQLite engine) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 425, - "end": 468 + "start": 428, + "end": 471 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", "message": "the '_http_common' module (a Node-internal module: it exposes Node's own implementation objects, which the scriptc runtime does not replicate) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 469, - "end": 511 + "start": 472, + "end": 514 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", "message": "the 'node:inspector' module (it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect) is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 512, - "end": 550 + "start": 515, + "end": 553 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", - "message": "the 'node:stream/web' module is not supported yet", + "message": "the 'node:repl' module is not supported yet", "loc": { "file": "/tests/diagnostics/module-out-of-scope.ts", - "start": 551, - "end": 600 + "start": 554, + "end": 588 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-assign-class-copy.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-assign-class-copy.ts.json new file mode 100644 index 000000000..085f01796 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-assign-class-copy.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/object-assign-class-copy.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-literal-accessor-values.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-literal-accessor-values.ts.json new file mode 100644 index 000000000..08ae4c161 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/object-literal-accessor-values.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/object-literal-accessor-values.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pkg-import-fences/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pkg-import-fences/main.ts.json index 50b586123..cfe16cdb3 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pkg-import-fences/main.ts.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/pkg-import-fences/main.ts.json @@ -12,7 +12,7 @@ "end": 476 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", @@ -23,7 +23,7 @@ "end": 415 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" }, { "code": "SC1010", @@ -34,7 +34,7 @@ "end": 448 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json deleted file mode 100644 index ed04bcd16..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/retired-engine-761-any-objects.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/diagnostics/retired-engine-761-any-objects.ts" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/stream-completion-callback-error.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/stream-completion-callback-error.ts.json new file mode 100644 index 000000000..af31bb8cf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/stream-completion-callback-error.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/stream-completion-callback-error.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/variadic-wrapper-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/variadic-wrapper-fences.ts.json new file mode 100644 index 000000000..c3e44b2e5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/variadic-wrapper-fences.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/variadic-wrapper-fences.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-fences.ts.json deleted file mode 100644 index cfa602a2e..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-fences.ts.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/fixtures/node-types/child-stdin-fences.ts" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-streams.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-streams.ts.json new file mode 100644 index 000000000..2bb443d2a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/child-stdin-streams.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/child-stdin-streams.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/event-loop-metrics.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/event-loop-metrics.ts.json new file mode 100644 index 000000000..66246403d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/event-loop-metrics.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/event-loop-metrics.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json new file mode 100644 index 000000000..e5c444b78 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/events-once-signal-race.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/events-once-signal-race.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/fetch-init-members.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/fetch-init-members.ts.json new file mode 100644 index 000000000..3d8a7bef5 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/fetch-init-members.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/fetch-init-members.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/http-socket-path.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/http-socket-path.ts.json new file mode 100644 index 000000000..9147244bf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/http-socket-path.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/http-socket-path.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/import-fences.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/import-fences.ts.json index 52a865199..05b272052 100644 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/import-fences.ts.json +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/import-fences.ts.json @@ -5,14 +5,14 @@ "diags": [ { "code": "SC1010", - "message": "the 'v8' module (it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe) is not supported yet", + "message": "the 'inspector' module (it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect) is not supported yet", "loc": { "file": "/tests/fixtures/node-types/import-fences.ts", - "start": 422, - "end": 461 + "start": 432, + "end": 465 }, "milestone": "M4", - "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" + "hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported" } ] } diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/lookup-and-header-entries.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/lookup-and-header-entries.ts.json new file mode 100644 index 000000000..13a440abb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/lookup-and-header-entries.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/lookup-and-header-entries.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/node-stream-interfaces.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/node-stream-interfaces.ts.json new file mode 100644 index 000000000..cfb5c1a10 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/node-stream-interfaces.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/node-stream-interfaces.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/process-versions-absent.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/process-versions-absent.ts.json new file mode 100644 index 000000000..1d842a3fe --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/process-versions-absent.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/process-versions-absent.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/promisify-callback-functions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/promisify-callback-functions.ts.json new file mode 100644 index 000000000..a4a753c3d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/promisify-callback-functions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/promisify-callback-functions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-construction.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-construction.ts.json new file mode 100644 index 000000000..35dc0be0d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-construction.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/stream-construction.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-subclass-hooks.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-subclass-hooks.ts.json new file mode 100644 index 000000000..295d7deda --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/stream-subclass-hooks.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/stream-subclass-hooks.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/timeout-type.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/timeout-type.ts.json new file mode 100644 index 000000000..46f0b6e38 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/timeout-type.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/timeout-type.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-session-timings.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-session-timings.ts.json new file mode 100644 index 000000000..953193649 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/node-types/tls-session-timings.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/node-types/tls-session-timings.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts.json new file mode 100644 index 000000000..cc5bc69c8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts.json new file mode 100644 index 000000000..382eef7b4 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts.json new file mode 100644 index 000000000..f609f128e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts.json new file mode 100644 index 000000000..be3b2af16 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts.json new file mode 100644 index 000000000..68cde3de1 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts.json new file mode 100644 index 000000000..953a75d7d --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json new file mode 100644 index 000000000..90e304cbe --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json new file mode 100644 index 000000000..6443d170b --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json new file mode 100644 index 000000000..f51a29951 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts.json new file mode 100644 index 000000000..0739a6f0c --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json new file mode 100644 index 000000000..c3e0442f9 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2580-lazy-barrel/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2580-lazy-barrel/main.ts.json new file mode 100644 index 000000000..08a17ffe0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2580-lazy-barrel/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2580-lazy-barrel/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts.json new file mode 100644 index 000000000..6fdd9687e --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts.json new file mode 100644 index 000000000..8ab899372 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts.json new file mode 100644 index 000000000..caf2144ad --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts.json new file mode 100644 index 000000000..e255c754a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/cjs-exports-var/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/cjs-exports-var/main.ts.json new file mode 100644 index 000000000..9ec4bbdeb --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/cjs-exports-var/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/cjs-exports-var/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/tsc-default-object/main.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/tsc-default-object/main.ts.json new file mode 100644 index 000000000..7ce71c66f --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/fixtures/npm/cases/tsc-default-object/main.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/fixtures/npm/cases/tsc-default-object/main.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/parity.test.ts b/packages/compiler/test/ts7/parity.test.ts index 4904f2720..978de5cc2 100644 --- a/packages/compiler/test/ts7/parity.test.ts +++ b/packages/compiler/test/ts7/parity.test.ts @@ -1,4 +1,5 @@ import { Ts7Host } from "../../src/frontend/ts7/program-adapter.js"; +import { symbolName, symbolNameOf } from "../../src/frontend/ts7/semantic-model.js"; /* The two-world agreement battery: every fixture program is parsed and * checked through BOTH worlds — typescript@5.9.3 in-process and the TS7 * adapter over tsgo — and the worlds must agree on structure (walk order, @@ -331,7 +332,7 @@ for (const battery of ALL_BATTERIES) { if (s5[a] !== undefined && isFixtureSymbol(w(), s5[a]!) && s5[a]!.name !== "export=") { // finding 5: the module.exports receiver's symbol NAME differs in // JS files (5.9.3 "export=", tsgo "module") — pinned separately. - expect(s7[a]!.name, `${file}#${ids[a]} symbol name`).toBe(s5[a]!.name); + expect(symbolName(s7[a]!), `${file}#${ids[a]} symbol name`).toBe(s5[a]!.name); } for (let b = a + 1; b < ids.length; b++) { if (s5[a] === undefined || s5[b] === undefined) continue; @@ -432,7 +433,7 @@ test("finding pinned: tsgo does not synthesize CJS expando members; module resol const moduleIdx = n5.findIndex((n) => ts5.isIdentifier(n) && n.getText() === "module"); expect(moduleIdx).toBeGreaterThanOrEqual(0); expect(w.c5.getSymbolAtLocation(n5[moduleIdx]!)?.name).toBe("export="); - expect(w.c7.getSymbolAtLocation(n7[moduleIdx]!)?.name).toBe("module"); + expect(symbolNameOf(w.c7.getSymbolAtLocation(n7[moduleIdx]!))).toBe("module"); }); test("getAwaitedType shim agrees with 5.9.3 across the async battery (pinned limitation aside)", () => { @@ -487,7 +488,7 @@ test("symbol declarations resolve back into the same client AST", () => { const sym7 = w.c7.getSymbolAtLocation(n7[i]!); expect(sym7).toBeDefined(); const aliased = w.c7.getAliasedSymbol(sym7!); - expect(aliased.name).toBe("answer"); + expect(symbolName(aliased)).toBe("answer"); const declHandle = aliased.declarationHandles[0]; expect(declHandle).toBeDefined(); const decl = declHandle!.resolve(w.p7.project); diff --git a/packages/compiler/test/ts7/rpc-api.test.ts b/packages/compiler/test/ts7/rpc-api.test.ts index 28b63ec34..a39c71321 100644 --- a/packages/compiler/test/ts7/rpc-api.test.ts +++ b/packages/compiler/test/ts7/rpc-api.test.ts @@ -14,7 +14,21 @@ import { SemanticSignature, SemanticSymbol, SemanticType, + symbolName, } from "../../src/frontend/ts7/semantic-model.js"; +import { ts7Diagnostic, type Ts7Diagnostic } from "../../src/frontend/ts7/diagnostic.js"; +import type { Ts7DiagnosticData } from "../../src/frontend/ts7/session-schema.generated.js"; + +// The SDK's symbols have `name`; scriptc's give it with symbolName, which reads `escapedName`, as the +// SDK's symbols also have it. +function nameOf(symbol: SdkSymbol): string { + return symbolName(symbol as unknown as SemanticSymbol); +} + +// scriptc's diagnostics have every member set; the SDK's leave absent ones out. +function diagnostics(list: readonly unknown[]): Ts7Diagnostic[] { + return list.map((item) => ts7Diagnostic(item as Ts7DiagnosticData)); +} // scriptc's symbols keep their declarations' node handles in // `declarationHandles`, and the SDK's in `declarations`. @@ -51,12 +65,12 @@ function programFacts(snapshot: Snapshot, config: string, path: string) { options: project.program.getCompilerOptions(), text: file.text, type: checker.typeToString(type), - symbol: symbol.name, + symbol: nameOf(symbol), declaration: declarationHandlesOf(symbol)[0]!.resolve()!.getText(file), - syntactic: project.program.getSyntacticDiagnostics(path), - semantic: project.program.getSemanticDiagnostics(path), - globals: project.program.getGlobalDiagnostics(), - config: project.program.getConfigFileParsingDiagnostics(), + syntactic: diagnostics(project.program.getSyntacticDiagnostics(path)), + semantic: diagnostics(project.program.getSemanticDiagnostics(path)), + globals: diagnostics(project.program.getGlobalDiagnostics()), + config: diagnostics(project.program.getConfigFileParsingDiagnostics()), defaultLibrary: project.program.isSourceFileDefaultLibrary(file), externalLibrary: project.program.isSourceFileFromExternalLibrary(file), }; @@ -71,7 +85,16 @@ test("production RPC bridge preserves pinned SDK parser/checker behavior", () => JSON.stringify({ compilerOptions: { strict: true, noEmit: true, types: [] }, files: [path] }), ); writeFileSync(path, 'export const answer = 42;\nexport const invalid: number = "wrong";\n'); - const nativeClient = new Ts7Api({ cwd: dir, fs: fallback }); + // Compared with the pinned SDK, which runs tsgo, so the bridge runs tsgo too. + const checker = process.env["SCRIPTC_CHECKER"]; + process.env["SCRIPTC_CHECKER"] = "tsgo"; + let nativeClient: Ts7Api; + try { + nativeClient = new Ts7Api({ cwd: dir, fs: fallback }); + } finally { + if (checker === undefined) delete process.env["SCRIPTC_CHECKER"]; + else process.env["SCRIPTC_CHECKER"] = checker; + } const sdk = new API({ cwd: dir }); try { expect(nativeClient.parseConfigFile(config)).toEqual(sdk.parseConfigFile(config)); @@ -224,7 +247,7 @@ test("native semantic registries preserve project scope and snapshot lifetimes", .declarationList.declarations, )[0], texts: [a.checker.typeToString(typeA), b.checker.typeToString(typeB)], - parameters: signatureA.getParameters().map((symbol) => symbol.name), + parameters: signatureA.getParameters().map(nameOf), }, symbolA, typeA, @@ -249,7 +272,7 @@ test("native semantic registries preserve project scope and snapshot lifetimes", first.dispose(); expect(() => actual.typeA.getSymbol()).toThrow("disposed"); expect(() => declarationHandlesOf(actual.symbolA)[0]!.resolve()).toThrow("disposed"); - expect(next.signatureA.getParameters()[0]!.name).toBe("value"); + expect(nameOf(next.signatureA.getParameters()[0]!)).toBe("value"); expect(second.getProject(firstConfig)!.checker.typeToString(next.typeA)).toBe("42"); second.dispose(); expect(() => next.signatureA.getParameters()).toThrow("disposed"); diff --git a/packages/experimental/package.json b/packages/experimental/package.json index 327f6fc97..f32a1085b 100644 --- a/packages/experimental/package.json +++ b/packages/experimental/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/experimental", - "version": "0.3.6", + "version": "0.4.0", "description": "Experimental scriptc APIs, with Node.js implementations: lifetime regions for ProgramLifetime objects, intern tables shared between threads, and per-thread slots on shared objects", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-darwin-arm64/package.json b/packages/llvm-darwin-arm64/package.json index 28865dc35..5330a5397 100644 --- a/packages/llvm-darwin-arm64/package.json +++ b/packages/llvm-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-darwin-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on macOS arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-darwin-x64/package.json b/packages/llvm-darwin-x64/package.json index 8d03e25cb..6282710b9 100644 --- a/packages/llvm-darwin-x64/package.json +++ b/packages/llvm-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-darwin-x64", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on macOS x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-arm64-gnu/package.json b/packages/llvm-linux-arm64-gnu/package.json index a6782dce1..59b4af11f 100644 --- a/packages/llvm-linux-arm64-gnu/package.json +++ b/packages/llvm-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-arm64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on Linux arm64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-arm64-musl/package.json b/packages/llvm-linux-arm64-musl/package.json index c4ac49e9b..5ea482253 100644 --- a/packages/llvm-linux-arm64-musl/package.json +++ b/packages/llvm-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-arm64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Statically linked LLVM code-generation helper for scriptc on Linux arm64 musl", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-x64-gnu/package.json b/packages/llvm-linux-x64-gnu/package.json index 6136a8e07..a17b6f393 100644 --- a/packages/llvm-linux-x64-gnu/package.json +++ b/packages/llvm-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-x64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on Linux x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-linux-x64-musl/package.json b/packages/llvm-linux-x64-musl/package.json index 02d994cdd..161ca0cb7 100644 --- a/packages/llvm-linux-x64-musl/package.json +++ b/packages/llvm-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-linux-x64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Statically linked LLVM code-generation helper for scriptc on Linux x64 musl", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/llvm-win32-x64-msvc/package.json b/packages/llvm-win32-x64-msvc/package.json index fe1329515..cfcc28a01 100644 --- a/packages/llvm-win32-x64-msvc/package.json +++ b/packages/llvm-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/llvm-win32-x64-msvc", - "version": "0.3.6", + "version": "0.4.0", "description": "Pinned LLVM code-generation helper for scriptc on Windows x64", "license": "Apache-2.0 WITH LLVM-exception", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-android-arm64/package.json b/packages/runtime-android-arm64/package.json index 155c6ce87..2d8e22c92 100644 --- a/packages/runtime-android-arm64/package.json +++ b/packages/runtime-android-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-android-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc library runtime pack for Android arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-darwin-arm64/package.json b/packages/runtime-darwin-arm64/package.json index a36cd273b..68c81b246 100644 --- a/packages/runtime-darwin-arm64/package.json +++ b/packages/runtime-darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-darwin-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for macOS arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs b/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs index 23c803879..3fdfd7ff5 100644 --- a/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs +++ b/packages/runtime-darwin-arm64/runtime-pack-matrix.mjs @@ -1,5 +1,10 @@ import { withWorkerRuntimeVariants } from "../runtime-pack-common/worker-matrix.mjs"; import { withLibraryRuntimeFlavors } from "../runtime-pack-common/library-matrix.mjs"; +import { + BASE_RUNTIME_SOURCES, + OPTIONAL_RUNTIME_SOURCES, + variantsFor, +} from "../runtime-pack-common/runtime-pack-matrix.mjs"; /** * The executable runtime-pack matrix. This is the single source of truth for @@ -9,45 +14,6 @@ import { withLibraryRuntimeFlavors } from "../runtime-pack-common/library-matrix */ const any = (...features) => ({ any: features }); -const all = (...features) => ({ all: features }); - -const BASE_RUNTIME_SOURCES = [ - "scr_context.c", - "scr_shared.c", - "scr_number.c", - "scr_bigint.c", - "scr_string.c", - "scr_grapheme.c", - "scr_array.c", - "scr_bytes.c", - "scr_bytes_io.c", - "scr_fs_cp.c", - "scr_map.c", - "scr_closure.c", - "scr_ffi.c", - "scr_object.c", - "scr_union.c", - "scr_exception.c", - "scr_error.c", - "scr_console.c", - "scr_lib.c", - "scr_hash.c", - "scr_path.c", - "scr_os.c", - "scr_url.c", - "scr_url_params.c", - "scr_json.c", - "scr_node_builtin.c", - "scr_async.c", - "scr_crypto_async.c", - "scr_child.c", - "scr_cycle.c", - "scr_publish.c", - "scr_region.c", - "scr_itable.c", - "scr_slots.c", - "scr_alloc.c", -]; // ld64 dead-strips Mach-O symbol subsections without an ELF-style compile // flag. Keep this checked-in metadata alongside the matrix so pack build and @@ -57,107 +23,6 @@ export const EXECUTABLE_SECTION_ELIMINATION = { link_flags: ["-Wl,-dead_strip"], }; -const optional = [ - ["scr_copying.c", "copying"], - ["scr_file_handle.c", "fileHandle"], - ["scr_regex.c", "regex"], - ["scr_glob.c", "regex"], - ["scr_idna.c", "regex"], - ["scr_assert.c", any("assert", "regex", "symbol")], - ["scr_inspect.c", "inspect"], - ["scr_console_native.c", "inspect"], - ["scr_dyn_invoke.c", any("dynInvoke", "nativeFetch")], - ["scr_dc.c", "dc"], - // The dynamic tier's jsval values (two-tier design I3): linked only into - // programs whose IR has dynamic-tier operations. - ["scr_js_value.c", "dynTier"], - ["scr_event_target.c", any("eventTarget", "nativeFetch")], - ["scr_async_dyn.c", any("dynAsync", "dynInvoke", "dc", "fileHandle", "nativeFetch")], - // The performance timeline emits its buffer warning through scr_async_dyn.c. - ["scr_perf.c", "dynAsync"], - // Blob and File box their reads as dyn promises through scr_async_dyn.c. - ["scr_blob.c", "dynAsync"], - ["scr_zlib.c", "zlib"], - ["scr_events.c", "events"], - ["scr_readline.c", "events"], - ["scr_readline_tty.c", all("events", "regex")], - ["scr_events_emitter.c", "emitter"], - ["scr_dyn_handle.c", any("emitter", "netEffective")], - ["scr_symbol.c", "symbol"], - ["scr_bigint_assert.c", all("assert", "bigint")], - ["scr_qs.c", "qs"], - // The legacy url unit lowercases and normalizes through the regex unit - // and warns through the dyn-async one. - ["scr_url_legacy.c", all("regex", "qs", "dynAsync")], - ["scr_util.c", "parseArgs"], - // Utility objects must only reference runtime families selected with them. - ["scr_util_compare.c", all("parseArgs", "symbol")], - ["scr_util_style.c", all("parseArgs", "inspect", "dynAsync")], - ["scr_util_callable.c", all("parseArgs", "inspect", "dynAsync", "dynInvoke", "regex")], - ["scr_stream.c", "stream"], - // The zlib stream classes reference both the zlib engine and the stream unit. - ["scr_zlib_stream.c", all("zlib", "stream")], - // The Readable operators await through the dyn-async unit. - ["scr_stream_ops.c", all("stream", "dynAsync")], - // fs.ReadStream / fs.WriteStream reference only the stream unit and base units. - ["scr_fs_stream.c", "stream"], - // FileHandle streams reference both the stream unit and FileHandle. - ["scr_fs_stream_fh.c", all("stream", "fileHandle")], - ["scr_loop_kqueue.c", any("netEffective", "dgram")], - ["scr_loop_epoll.c", any("netEffective", "dgram")], - ["scr_loop_wsapoll.c", any("netEffective", "dgram")], - ["scr_net.c", "netEffective"], - ["scr_http.c", "httpEffective"], - ["scr_http2.c", "http2"], - ["scr_dgram.c", "dgram"], - ["scr_watch.c", "watch"], - ["scr_ffi_queue.c", "foreignFfi"], - ["scr_test.c", "nodeTest"], - ["scr_tls_ca.c", "tlsCaEffective"], - ["scr_tls.c", "tlsEffective"], - ["scr_cipher.c", "cipher"], - ["scr_webcrypto.c", "webcrypto"], - ["scr_fetch.c", "nativeFetch"], -]; - -function variantsFor(source) { - const variants = [{ id: "default", when: {}, defines: [] }]; - if (source === "scr_bytes.c") { - variants.push({ - id: "text-decoder-legacy", - when: { textDecoderLegacy: true }, - defines: ["SCR_TEXT_DECODER_LEGACY"], - }); - } - // Programs that assign process.emitWarning link the replacement-aware - // warning paths; every other program keeps the default object. - if (source === "scr_async_dyn.c") { - variants.push({ - id: "warning-replace", - when: { warningReplace: true }, - defines: ["SCR_WARNING_REPLACE"], - }); - } - // stdin setEncoding, string data listeners and stdio 'resize' events. - if (source === "scr_events.c") { - variants.push({ - id: "stdio-extended", - when: { stdioExtended: true }, - defines: ["SCR_STDIO_EXTENDED"], - }); - } - if (source === "scr_cycle.c") { - // The young-in nursery (SCRIPTC_YOUNG_NURSERY=1): the collector with - // nursery collections; programs built without it link the default. - variants.push({ - id: "young-nursery", - when: { youngNursery: true }, - defines: ["SCR_YIN"], - }); - } - return variants; -} - export const RUNTIME_PACK_MATRIX = withLibraryRuntimeFlavors( withWorkerRuntimeVariants({ schema: "scriptc.runtime-pack-matrix.v1", @@ -172,7 +37,7 @@ export const RUNTIME_PACK_MATRIX = withLibraryRuntimeFlavors( executable_section_elimination: EXECUTABLE_SECTION_ELIMINATION, runtime_units: [ ...BASE_RUNTIME_SOURCES.map((source) => ({ source, predicate: true })), - ...optional.map(([source, predicate]) => ({ source, predicate })), + ...OPTIONAL_RUNTIME_SOURCES.map(([source, predicate]) => ({ source, predicate })), ].map((unit) => ({ ...unit, variants: variantsFor(unit.source) })), archives: [ { id: "quickjs", predicate: false }, diff --git a/packages/runtime-darwin-x64/package.json b/packages/runtime-darwin-x64/package.json index f81f21139..54c0d7835 100644 --- a/packages/runtime-darwin-x64/package.json +++ b/packages/runtime-darwin-x64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-darwin-x64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for macOS x64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-ios-arm64/package.json b/packages/runtime-ios-arm64/package.json index c76226c6f..eb3d0ee27 100644 --- a/packages/runtime-ios-arm64/package.json +++ b/packages/runtime-ios-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-ios-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc library runtime pack for iOS arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-ios-simulator-arm64/package.json b/packages/runtime-ios-simulator-arm64/package.json index ec916922e..3b805bb6f 100644 --- a/packages/runtime-ios-simulator-arm64/package.json +++ b/packages/runtime-ios-simulator-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-ios-simulator-arm64", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc library runtime pack for iOS simulator arm64", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-arm64-gnu/package.json b/packages/runtime-linux-arm64-gnu/package.json index bb972ebbe..ad50f2fe3 100644 --- a/packages/runtime-linux-arm64-gnu/package.json +++ b/packages/runtime-linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-arm64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Linux arm64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-arm64-musl/package.json b/packages/runtime-linux-arm64-musl/package.json index 69110991a..4d4c23fdd 100644 --- a/packages/runtime-linux-arm64-musl/package.json +++ b/packages/runtime-linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-arm64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Linux arm64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-x64-gnu/package.json b/packages/runtime-linux-x64-gnu/package.json index ceb7b975f..ab8780c39 100644 --- a/packages/runtime-linux-x64-gnu/package.json +++ b/packages/runtime-linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-x64-gnu", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Linux x64 glibc", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-linux-x64-musl/package.json b/packages/runtime-linux-x64-musl/package.json index 21f83ccee..862b46187 100644 --- a/packages/runtime-linux-x64-musl/package.json +++ b/packages/runtime-linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-linux-x64-musl", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Linux x64 musl", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index d7b4118e8..258df8dec 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -9,7 +9,11 @@ import { withLibraryRuntimeFlavors } from "./library-matrix.mjs"; const any = (...features) => ({ any: features }); const all = (...features) => ({ all: features }); -const BASE_RUNTIME_SOURCES = [ +/* Every runtime pack matrix uses these lists, so a new runtime source is + * one line here. Their order is the link order of the units an executable + * selects: keep a new unit next to the units it references, and treat any + * reordering as a change to every executable's layout. */ +export const BASE_RUNTIME_SOURCES = [ "scr_context.c", "scr_shared.c", "scr_number.c", @@ -47,7 +51,7 @@ const BASE_RUNTIME_SOURCES = [ "scr_alloc.c", ]; -const OPTIONAL = [ +export const OPTIONAL_RUNTIME_SOURCES = [ ["scr_copying.c", "copying"], ["scr_file_handle.c", "fileHandle"], ["scr_regex.c", "regex"], @@ -65,6 +69,8 @@ const OPTIONAL = [ ["scr_async_dyn.c", any("dynAsync", "dynInvoke", "dc", "fileHandle", "nativeFetch")], // The performance timeline emits its buffer warning through scr_async_dyn.c. ["scr_perf.c", "dynAsync"], + // perf_hooks event-loop metrics, only for programs that read them. + ["scr_perf_loop.c", "perfLoop"], // Blob and File box their reads as dyn promises through scr_async_dyn.c. ["scr_blob.c", "dynAsync"], ["scr_zlib.c", "zlib"], @@ -72,6 +78,8 @@ const OPTIONAL = [ ["scr_readline.c", "events"], ["scr_readline_tty.c", all("events", "regex")], ["scr_events_emitter.c", "emitter"], + // events.once with options registers through the emitter and event-target units. + ["scr_events_once_signal.c", "eventsOnceSignal"], ["scr_dyn_handle.c", any("emitter", "netEffective")], ["scr_symbol.c", "symbol"], ["scr_bigint_assert.c", all("assert", "bigint")], @@ -84,11 +92,30 @@ const OPTIONAL = [ ["scr_util_compare.c", all("parseArgs", "symbol")], ["scr_util_style.c", all("parseArgs", "inspect", "dynAsync")], ["scr_util_callable.c", all("parseArgs", "inspect", "dynAsync", "dynInvoke", "regex")], + // Untyped EventEmitter and stream constructor values call listeners + // through the dyn-invoke unit, inspect unhandled 'error' payloads and + // create EventEmitter's private symbols. + ["scr_dyn_builtin_class.c", all("dynBuiltinClass", "inspect", "dynInvoke", "symbol", "emitter")], + // Their stream half also references the stream unit. + [ + "scr_dyn_builtin_stream.c", + all( + "dynBuiltinStream", + "dynBuiltinClass", + "stream", + "inspect", + "dynInvoke", + "symbol", + "emitter", + ), + ], ["scr_stream.c", "stream"], // The zlib stream classes reference both the zlib engine and the stream unit. ["scr_zlib_stream.c", all("zlib", "stream")], // The Readable operators await through the dyn-async unit. ["scr_stream_ops.c", all("stream", "dynAsync")], + // Checked-dynamic stream slots call JavaScript members through the dyn-invoke unit. + ["scr_stream_dyn.c", "streamDyn"], // fs.ReadStream / fs.WriteStream reference only the stream unit and base units. ["scr_fs_stream.c", "stream"], // FileHandle streams reference both the stream unit and FileHandle. @@ -100,17 +127,20 @@ const OPTIONAL = [ ["scr_http.c", "httpEffective"], ["scr_http2.c", "http2"], ["scr_dgram.c", "dgram"], + // The dns promise forms run over the dgram unit's callback forms. + ["scr_dns_promises.c", all("dgram", "dnsPromises")], ["scr_watch.c", "watch"], ["scr_ffi_queue.c", "foreignFfi"], ["scr_test.c", "nodeTest"], ["scr_tls_ca.c", "tlsCaEffective"], ["scr_tls.c", "tlsEffective"], ["scr_cipher.c", "cipher"], + ["scr_wasm.c", "wasm"], ["scr_webcrypto.c", "webcrypto"], ["scr_fetch.c", "nativeFetch"], ]; -function variantsFor(source) { +export function variantsFor(source) { const variants = [{ id: "default", when: {}, defines: [] }]; if (source === "scr_bytes.c") { variants.push({ @@ -175,7 +205,7 @@ export function createRuntimePackMatrix({ (source) => ({ source, predicate: true }), ), ...extraRuntimeSources.map((source) => ({ source, predicate: true })), - ...OPTIONAL.filter(([source]) => !omitOptionalSources.includes(source)).map( + ...OPTIONAL_RUNTIME_SOURCES.filter(([source]) => !omitOptionalSources.includes(source)).map( ([source, predicate]) => ({ source, predicate }), ), ].map((unit) => ({ ...unit, variants: variantsFor(unit.source) })), diff --git a/packages/runtime-wasm32-wasi/package.json b/packages/runtime-wasm32-wasi/package.json index fa073ecf2..48c57d59a 100644 --- a/packages/runtime-wasm32-wasi/package.json +++ b/packages/runtime-wasm32-wasi/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-wasm32-wasi", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for WASI Preview 1", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime-wasm32-wasi/scripts/build.mjs b/packages/runtime-wasm32-wasi/scripts/build.mjs index ca0786607..64d28f15f 100644 --- a/packages/runtime-wasm32-wasi/scripts/build.mjs +++ b/packages/runtime-wasm32-wasi/scripts/build.mjs @@ -16,10 +16,12 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({ "scr_http.c", "scr_http2.c", "scr_dgram.c", + "scr_dns_promises.c", "scr_watch.c", "scr_tls_ca.c", "scr_tls.c", "scr_cipher.c", + "scr_wasm.c", "scr_fetch.c", ], omitArchives: ["mbedtls"], diff --git a/packages/runtime-win32-x64-msvc/package.json b/packages/runtime-win32-x64-msvc/package.json index 4f6017b29..fe9ba6892 100644 --- a/packages/runtime-win32-x64-msvc/package.json +++ b/packages/runtime-win32-x64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime-win32-x64-msvc", - "version": "0.3.6", + "version": "0.4.0", "description": "Precompiled scriptc runtime pack for Windows x64 MSVC", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 0b9559931..b63f4fad3 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/runtime", - "version": "0.3.6", + "version": "0.4.0", "description": "scriptc native runtime sources and vendored dependencies", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/runtime/src/scr_alloc.c b/packages/runtime/src/scr_alloc.c index 246d27f0a..935736612 100644 --- a/packages/runtime/src/scr_alloc.c +++ b/packages/runtime/src/scr_alloc.c @@ -935,12 +935,19 @@ static void scr_sa_trim(void) { * interrupts every running thread of the process to flush its TLB, and * glibc's trim locks the arenas other threads allocate from, so discards * wait for the last worker of a group, which hands them to one helper; - * - the helper waits SCR_SA_DISCARD_DELAY_MS (100 by default) and stops if + * - the helper waits a delay (scr_sa_discard_delay_ms: 1 s to start, + * doubled each time workers start again soon after a discard) and stops if * a worker started meanwhile: a program that starts its next workers or * exits within that time does not return pages it would fault back in or * never touch again, and the last worker of the next group hands the * discards to a new helper; a thread that finds every arena claimed * discards one waiting arena and claims it; + * - once the event loop of the thread that is not a worker runs, the + * helper then discards one arena at a time, and only while that loop + * waits with nothing ready (scr_sa_loop_waiting): a program that does + * its next work on that thread alone (a compiler rebuilding one file in + * watch mode) would otherwise have it slowed by the discards. A loop that + * does not wait gets them SCR_SA_DISCARD_DELAY_MAX_MS after the delay; * - the helper then returns the pages * it carved (MADV_DONTNEED; the start of the first page, which it may * share with older blocks, is cleared), frees the recorded system blocks, @@ -1214,12 +1221,20 @@ void scr_sa_thread_shares(void) { /* Worker threads that started and have not parked. */ static _Atomic(unsigned) scr_sa_workers_live; +#ifdef SCR_SA_TRIM +static void scr_sa_group_starting(void); +#endif + /* Library archives run on their embedder's threads and keep their memory. */ static void scr_sa_worker_starting(void) { #ifndef SCR_LIB scr_sa_worker = true; +#ifdef SCR_SA_TRIM + if (atomic_fetch_add_explicit(&scr_sa_workers_live, 1, memory_order_relaxed) == 0) scr_sa_group_starting(); +#else atomic_fetch_add_explicit(&scr_sa_workers_live, 1, memory_order_relaxed); #endif +#endif } #define SCR_SA_WORKER_STARTING() scr_sa_worker_starting() #endif @@ -1309,28 +1324,91 @@ static bool scr_sa_drain_some(bool one) { static bool scr_sa_drain(void) { return scr_sa_drain_some(false); } -/* SCR_SA_DISCARD_DELAY_MS: how long the trim helper waits after the last - * worker parks before it returns their memory (default 100). */ -static long scr_sa_discard_delay_ms(void) { - const char *delay = getenv("SCR_SA_DISCARD_DELAY_MS"); - if (!delay) return 100; - long ms = strtol(delay, NULL, 10); +/* How long the trim helper waits after the last worker parks before it + * returns their memory: SCR_SA_DISCARD_DELAY_MS (1000 by default) to start. + * Each time workers start again within ten delays of a discard, the delay + * doubles, up to SCR_SA_DISCARD_DELAY_MAX_MS (10000 by default): a program + * that keeps starting workers (a compiler rebuilding in watch mode, a + * server) would fault the returned pages straight back in. Memory that + * stays idle for the grown delay still goes back. */ +static _Atomic(long) scr_sa_delay_grown_ms; /* 0 until a discard proved too early */ +static _Atomic(long) scr_sa_discarded_ms; /* when the helper last discarded; 0: not since */ + +static long scr_sa_env_ms(const char *name, long fallback) { + const char *value = getenv(name); + if (!value) return fallback; + long ms = strtol(value, NULL, 10); return ms < 0 ? 0 : ms; } +static long scr_sa_discard_delay_ms(void) { + long start = scr_sa_env_ms("SCR_SA_DISCARD_DELAY_MS", 1000); + long max = scr_sa_env_ms("SCR_SA_DISCARD_DELAY_MAX_MS", 10000); + long grown = atomic_load_explicit(&scr_sa_delay_grown_ms, memory_order_relaxed); + long delay = grown > start ? grown : start; + return max >= start && delay > max ? max : delay; +} + +static long scr_sa_now_ms(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (long)now.tv_sec * 1000 + now.tv_nsec / 1000000 + 1; +} + +/* The first worker of a group starts: a discard shortly before means the + * delay was too short for this program. */ +static void scr_sa_group_starting(void) { + long at = atomic_exchange_explicit(&scr_sa_discarded_ms, 0, memory_order_relaxed); + if (!at) return; + long delay = scr_sa_discard_delay_ms(); + if (scr_sa_now_ms() - at >= 10 * (delay > 10 ? delay : 10)) return; + long max = scr_sa_env_ms("SCR_SA_DISCARD_DELAY_MAX_MS", 10000); + long grown = delay > 0 ? 2 * delay : 1; + if (grown > max) grown = max; + if (grown > atomic_load_explicit(&scr_sa_delay_grown_ms, memory_order_relaxed)) + atomic_store_explicit(&scr_sa_delay_grown_ms, grown, memory_order_relaxed); +} + +/* The event loop of the thread that is not a worker: an odd count while it + * waits with nothing ready, an even one while it runs, 0 until it first + * waits. Only that thread writes it. */ +static _Atomic(unsigned long) scr_sa_loop_turns; + +void scr_sa_loop_waiting(bool waiting) { + if (scr_sa_worker) return; + unsigned long turns = atomic_load_explicit(&scr_sa_loop_turns, memory_order_relaxed); + if ((bool)(turns & 1) != waiting) atomic_store_explicit(&scr_sa_loop_turns, turns + 1, memory_order_relaxed); +} + /* Waits out the delay first, and stops if a worker starts meanwhile: the * discards then wait for the last worker of that group. A program that exits * within the delay does not spend its last milliseconds returning pages, and * one that starts its next workers within it does not return pages while - * they start. */ + * they start. Once the event loop runs, each discard then waits until the + * loop has been in one wait for a step, or until the maximum delay has + * passed. */ static void *scr_sa_drain_main(void *arg) { (void)arg; - for (long left = scr_sa_discard_delay_ms(); left > 0; left -= 5) { - struct timespec pause = {0, (left < 5 ? left : 5) * 1000000L}; + long delay = scr_sa_discard_delay_ms(), step = delay > 200 ? 20 : 5; + for (long left = delay; left > 0; left -= step) { + struct timespec pause = {0, (left < step ? left : step) * 1000000L}; nanosleep(&pause, NULL); if (atomic_load_explicit(&scr_sa_workers_live, memory_order_acquire) != 0) return NULL; } - if (atomic_load_explicit(&scr_sa_workers_live, memory_order_acquire) == 0) scr_sa_drain(); + long max = scr_sa_env_ms("SCR_SA_DISCARD_DELAY_MAX_MS", 10000); + unsigned long seen = 0; + for (long waited = 0; atomic_load_explicit(&scr_sa_workers_live, memory_order_acquire) == 0;) { + unsigned long turns = atomic_load_explicit(&scr_sa_loop_turns, memory_order_relaxed); + if (turns == 0 || ((turns & 1) && turns == seen) || waited >= max) { + if (!scr_sa_drain_some(true)) break; + atomic_store_explicit(&scr_sa_discarded_ms, scr_sa_now_ms(), memory_order_relaxed); + continue; + } + seen = turns; + struct timespec pause = {0, step * 1000000L}; + nanosleep(&pause, NULL); + waited += step; + } return NULL; } @@ -1361,6 +1439,7 @@ void scr_sa_wait_trims(void) { #else void scr_sa_wait_trims(void) {} void scr_sa_thread_shares(void) {} +void scr_sa_loop_waiting(bool waiting) { (void)waiting; } #endif static void scr_sa_park_arena(void); @@ -1564,6 +1643,7 @@ void *scr_mem_realloc(void *p, size_t n) { return realloc(p, n); } void scr_sa_thread_park(void) {} void scr_sa_wait_trims(void) {} void scr_sa_thread_shares(void) {} +void scr_sa_loop_waiting(bool waiting) { (void)waiting; } void scr_sa_free_foreign(void *p) { free(p); } #endif #endif diff --git a/packages/runtime/src/scr_alloc.test.c b/packages/runtime/src/scr_alloc.test.c index aa6b0b60b..6c83afdf9 100644 --- a/packages/runtime/src/scr_alloc.test.c +++ b/packages/runtime/src/scr_alloc.test.c @@ -700,11 +700,91 @@ static void check_discard_delay(void) { assert(pthread_join(holder, NULL) == 0); assert(pthread_join(plain, NULL) == 0); scr_sa_wait_trims(); - unsetenv("SCR_SA_DISCARD_DELAY_MS"); + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); assert(resident_pages(first, discard_hi) < total / 2); discard_in_old_arena = true; } +/* A discard that workers follow within ten delays was too early: the next + * delay is twice as long, and memory idle for that long still goes back. */ +static void *plain_discard_worker(void *arg) { + (void)arg; + scr_sa_thread_starting(); + allocate_discardable(); + scr_sa_thread_park(); + return NULL; +} + +static void check_discard_delay_grows(void) { + size_t page = (size_t)sysconf(_SC_PAGESIZE); + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "10000", 1); + discard_blocks = malloc(DISCARD_BLOCKS * sizeof *discard_blocks); + assert(discard_blocks); + /* A plain thread holds the arena whose slice the earlier checks filled. */ + atomic_store(&discard_step, 0); + pthread_t plain; + assert(pthread_create(&plain, NULL, holding_thread, NULL) == 0); + wait_step(1); + run(plain_discard_worker); /* returned after the delay */ + pthread_t t; + assert(pthread_create(&t, NULL, plain_discard_worker, NULL) == 0); + assert(pthread_join(t, NULL) == 0); + uintptr_t first = (discard_lo + page - 1) & ~(uintptr_t)(page - 1); + size_t total = (discard_hi - first) / page; + struct timespec pause = {0, 140000000L}; + nanosleep(&pause, NULL); + assert(resident_pages(first, discard_hi) > total / 2); /* the delay is 200 ms or more now */ + scr_sa_wait_trims(); + assert(resident_pages(first, discard_hi) < total / 2); + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "0", 1); + atomic_store(&discard_step, 3); + assert(pthread_join(plain, NULL) == 0); + free(discard_blocks); +} + +/* Once the event loop runs, discards wait for it to wait with nothing + * ready; a loop that does not wait gets them SCR_SA_DISCARD_DELAY_MAX_MS + * after the delay. The test's thread plays the loop's. */ +static void check_discard_waits_for_loop(void) { + size_t page = (size_t)sysconf(_SC_PAGESIZE); + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "10000", 1); + discard_blocks = malloc(DISCARD_BLOCKS * sizeof *discard_blocks); + assert(discard_blocks); + atomic_store(&discard_step, 0); + pthread_t plain; + assert(pthread_create(&plain, NULL, holding_thread, NULL) == 0); + wait_step(1); + scr_sa_loop_waiting(true); + scr_sa_loop_waiting(false); /* the loop runs */ + pthread_t t; + assert(pthread_create(&t, NULL, plain_discard_worker, NULL) == 0); + assert(pthread_join(t, NULL) == 0); + uintptr_t first = (discard_lo + page - 1) & ~(uintptr_t)(page - 1); + size_t total = (discard_hi - first) / page; + struct timespec pause = {0, 900000000L}; + nanosleep(&pause, NULL); + assert(resident_pages(first, discard_hi) > total / 2); /* past the delay */ + scr_sa_loop_waiting(true); + scr_sa_wait_trims(); + assert(resident_pages(first, discard_hi) < total / 2); + /* A loop that keeps running. */ + scr_sa_loop_waiting(false); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "300", 1); + assert(pthread_create(&t, NULL, plain_discard_worker, NULL) == 0); + assert(pthread_join(t, NULL) == 0); + first = (discard_lo + page - 1) & ~(uintptr_t)(page - 1); + total = (discard_hi - first) / page; + scr_sa_wait_trims(); + assert(resident_pages(first, discard_hi) < total / 2); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "0", 1); + atomic_store(&discard_step, 3); + assert(pthread_join(plain, NULL) == 0); + free(discard_blocks); +} + static void check_discard(void) { discard_blocks = malloc(DISCARD_BLOCKS * sizeof *discard_blocks); assert(discard_blocks); @@ -819,10 +899,15 @@ int main(void) { check_heap_huge_pages(); #endif #if defined(SCR_WORKERS) && SCR_SMALL_ALLOC + /* A short discard delay, which stays at its start until check_discard_delay_grows. */ + setenv("SCR_SA_DISCARD_DELAY_MS", "100", 1); + setenv("SCR_SA_DISCARD_DELAY_MAX_MS", "0", 1); check_threads(); #ifdef __linux__ check_trim(); check_discard(); + check_discard_delay_grows(); + check_discard_waits_for_loop(); #endif printf("allocator checks passed: size-class workers\n"); #else diff --git a/packages/runtime/src/scr_async.c b/packages/runtime/src/scr_async.c index 88ad0f7bd..ef91de6fb 100644 --- a/packages/runtime/src/scr_async.c +++ b/packages/runtime/src/scr_async.c @@ -56,24 +56,6 @@ #include #endif -#ifdef __wasi__ -/* WASI Preview 1 has no process-spawn capability. The child unit is not - * linked for this target, but the generic loop keeps its quiescence hooks; - * their empty implementation makes the absence explicit at link time - * without weakening async/timer support. */ -bool scr_children_pending(void) { return false; } -bool scr_children_ready(void) { return false; } -bool scr_children_reffed_pending(void) { return false; } -bool scr_children_failed_pending(void) { return false; } -void scr_children_poll(void) {} -int scr_children_wake_fd(void) { return -1; } -bool scr_children_wait(double max_wait_ms) { - (void)max_wait_ms; - return false; -} -void scr_children_teardown(void) {} -#endif - #if defined(__has_feature) #if __has_feature(address_sanitizer) #define SCR_ASAN_FIBERS 1 @@ -88,14 +70,6 @@ void __lsan_unregister_root_region(const void *p, size_t size) __attribute__((we #endif #endif -#ifdef SCR_ASAN_FIBERS -#define SCR_FIBER_STACK (8 * 1024 * 1024) -#else -/* Deep enough for ordinary recursion inside async bodies and generators - * once the overflow guard's reserve is taken off; idle and shallow fibers - * only touch the pages they use. */ -#define SCR_FIBER_STACK (512 * 1024) -#endif #if !defined(_WIN32) && !defined(__wasi__) /* Keep stacks out of malloc's size classes. Reuse a bounded number during @@ -4072,6 +4046,13 @@ void scr_loop_set_watch(bool (*pending)(void), void (*dispatch)(void), int (*pol scr_watch_pollfd_fn = pollfd; } +/* The child-process hook (scr_child.c, installed with the first spawned + * child or IPC channel). Programs that never create one keep it NULL and + * do not link the child unit; WASI never links it. */ +static SCR_TL const ScrLoopChildren *scr_loop_children = NULL; + +void scr_loop_set_children(const ScrLoopChildren *hooks) { scr_loop_children = hooks; } + /* The foreign-FFI queue hook (scr_ffi.c when a format-5 descriptor exists). * Its pending count includes live registrations (reffed by default) and * queued deliveries; dispatch runs exactly one callback per loop turn. */ @@ -4326,12 +4307,13 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { * death landing before this sweep fired its 'exit' where Node * deterministically never does. The teardown below releases the * undelivered listeners, so the RC audit stays clean. */ - if (scr_children_pending()) { + if (scr_loop_children != NULL && scr_loop_children->pending()) { bool held = - scr_reffed_timers > 0 || scr_reffed_immediates > 0 || scr_children_reffed_pending() || + scr_reffed_timers > 0 || scr_reffed_immediates > 0 || + scr_loop_children->reffed_pending() || /* spawn failures settle regardless: their 'error' is a * next-tick event, delivered even on an unref'd child */ - scr_children_failed_pending() || + scr_loop_children->failed_pending() || (scr_events_pending_fn != NULL && scr_events_pending_fn()) || (scr_net_pending_fn != NULL && scr_net_pending_fn()) || (scr_dgram_pending_fn != NULL && scr_dgram_pending_fn()) || @@ -4340,7 +4322,7 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { scr_fs_renames_pending() || scr_crypto_bytes_pending() || scr_loop_deferred_pending() || scr_workers_pending(); if (held) { - scr_children_poll(); + scr_loop_children->poll(); if (scr_exc_pending()) return false; /* uncaught throw in a listener */ if (scr_ready_len > 0 || scr_nt_head != NULL) continue; } @@ -4351,7 +4333,8 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { * nextTicks enqueued by a station listener are the same story. */ if (scr_stream_pending_fn != NULL && scr_stream_pending_fn()) continue; if (scr_nt_head != NULL) continue; - bool kids = scr_children_pending(); + bool kids = scr_loop_children != NULL && scr_loop_children->pending(); + bool kids_reffed = scr_loop_children != NULL && scr_loop_children->reffed_pending(); bool events = scr_events_pending_fn != NULL && scr_events_pending_fn(); bool net = scr_net_pending_fn != NULL && scr_net_pending_fn(); /* `net` keeps the process alive; `net_io` (unref'd handles too) decides @@ -4369,7 +4352,7 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { * Children follow the same rule: an unref'd child is still REAPED * while the loop runs (kids drives the sweeps and sleeps above) but * only reffed ones keep the process alive. */ - if (scr_reffed_timers == 0 && scr_reffed_immediates == 0 && !scr_children_reffed_pending() && !events && !net && !dgram && !watch && !ffi && !renames && !crypto && !workers) break; + if (scr_reffed_timers == 0 && scr_reffed_immediates == 0 && !kids_reffed && !events && !net && !dgram && !watch && !ffi && !renames && !crypto && !workers) break; /* Sleep to the earliest deadline, then run every due timer (each may * enqueue microtasks, which the next iteration drains first). Who * sleeps depends on what is pending: @@ -4413,12 +4396,15 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { * station has run. Completed sends and disconnects can also become ready * during that turn. No fd will wake us for this userspace work: return to * dispatch without sleeping, still allowing due timers and immediates. */ - if (scr_children_ready()) due = now; + if (scr_loop_children != NULL && scr_loop_children->ready()) due = now; #ifdef SCR_WORKERS if (scr_workers_ready_fn && scr_workers_ready_fn()) due = now; #endif #if !defined(_WIN32) && !defined(__wasi__) if (due > now) scr_fiber_stacks_clear(); +#endif +#ifdef SCR_WORKERS + if (due > now) scr_sa_loop_waiting(true); #endif bool evw = scr_events_watching_fn != NULL && scr_events_watching_fn(); #ifdef SCR_WORKERS @@ -4522,7 +4508,7 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { } } if (kids) { - int cfd = scr_children_wake_fd(); + int cfd = scr_loop_children->wake_fd(); if (cfd >= 0) { fds[nfds].fd = cfd; fds[nfds].events = POLLIN; @@ -4553,10 +4539,10 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { * unconsumed event keeps the kqueue fd readable and would turn the * next poll into a busy spin while other children still run. The * WNOHANG sweep at the next turn's top stays the reaper. */ - if (kids) (void)scr_children_wait(0); + if (kids) (void)scr_loop_children->wait(0); now = scr_now_ms(); #endif /* !_WIN32 && !__wasi__ */ - } else if (kids && scr_children_wait(due > now ? due - now : 0)) { + } else if (kids && scr_loop_children->wait(due > now ? due - now : 0)) { now = scr_now_ms(); /* woke early on an exit, or hit the deadline */ } else { if (kids && due > now + SCR_CHILD_POLL_MS) due = now + SCR_CHILD_POLL_MS; @@ -4573,6 +4559,9 @@ static bool scr_loop_run_pass(ScrPromise *top_level, bool first_checkpoint) { now = due; } } +#ifdef SCR_WORKERS + scr_sa_loop_waiting(false); +#endif while (scr_ntimers > 0 && scr_timers[0].deadline_ms <= now) { ScrTimer t = scr_timer_pop(); /* Timer callbacks are plain sync closures, run on the main stack. @@ -4710,7 +4699,7 @@ bool scr_loop_run(ScrPromise *top_level) { /* Unref'd children the loop never reaped: release the * registry's references (their listeners never fire — the process is * exiting, Node's behavior; the OS reparents the children). */ - scr_children_teardown(); + if (scr_loop_children != NULL) scr_loop_children->teardown(); scr_fibers_abandoned = scr_fibers_live; scr_note_abandoned_fibers(scr_fibers_abandoned); return rejection_failed; diff --git a/packages/runtime/src/scr_async.h b/packages/runtime/src/scr_async.h index 6f56758ce..041d2ea03 100644 --- a/packages/runtime/src/scr_async.h +++ b/packages/runtime/src/scr_async.h @@ -29,6 +29,20 @@ void *scr_promise_retain_v(void *p); void scr_promise_release_v(void *p); typedef struct ScrFiber ScrFiber; +/* Bytes of each async fiber's native stack (scr_async.c), mapped without a + * guard page: compiled code checks scr_stack_guard instead, which sits + * scr_stack_guard_for's reserve above the mapping's start. Deep enough for + * ordinary recursion inside async bodies and generators once that reserve + * is taken off; idle and shallow fibers only touch the pages they use. + * AddressSanitizer's larger frames get more. */ +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define SCR_FIBER_STACK (8 * 1024 * 1024) +#endif +#endif +#ifndef SCR_FIBER_STACK +#define SCR_FIBER_STACK (512 * 1024) +#endif /* Spawn + run eagerly to the first suspension; returns the promise, +1. */ ScrPromise *scr_async_spawn(void (*entry)(ScrFiber *, void *), void *argpack); /* `await f(...)` of a fiber-backed async f: spawn and await in one call, diff --git a/packages/runtime/src/scr_async_dyn.c b/packages/runtime/src/scr_async_dyn.c index 1cdd466c3..ec4dad794 100644 --- a/packages/runtime/src/scr_async_dyn.c +++ b/packages/runtime/src/scr_async_dyn.c @@ -1437,24 +1437,29 @@ static void scr_dyn_thenable_schedule(ScrPromise *target, const ScrDyn *thenable * a thenable gets a fresh promise and its resolve job, anything else a * fulfilled promise (a throwing `then` read rejects it). The element * joins the combinator right after, so a thenable's job sits between - * the jobs of the entries around it, exactly Node's iteration. */ -static ScrPromise *scr_dyn_promise_entry(ScrCombine *c, ScrDyn *value, const ScrDyn *reader) { + * the jobs of the entries around it, exactly Node's iteration. Without a + * combinator (`c` NULL) this is PromiseResolve for one value. */ +static ScrPromise *scr_dyn_promise_entry(ScrCombine *c, const ScrDyn *value, const ScrDyn *reader) { if (value->kind == SCR_DYN_PROMISE) return scr_promise_retain(value->v.promise); ScrPromise *source = scr_promise_new(); ScrDyn *then = scr_dyn_then_of(value, reader); if (scr_exc_pending()) { scr_promise_reject_pending(source); } else if (then) { - scr_promise_combine_flush(c); + if (c) scr_promise_combine_flush(c); scr_dyn_thenable_schedule(source, value, then, reader); } else { - scr_promise_fulfill_ref(source, scr_dyn_retain(value), scr_dyn_retain_v, scr_dyn_release_v, - scr_dyn_trace_v); + scr_promise_fulfill_ref(source, scr_dyn_retain((ScrDyn *)value), scr_dyn_retain_v, + scr_dyn_release_v, scr_dyn_trace_v); } scr_dyn_release(then); return source; } +ScrPromise *scr_dyn_promise_resolve(const ScrDyn *value, const ScrDyn *reader) { + return scr_dyn_promise_entry(NULL, value, reader); +} + static void scr_dyn_settled_store(ScrArr *a, double i, ScrPromise *src) { ScrDyn *record = scr_dyn_new_obj(); bool fulfilled = !scr_promise_is_rejected(src); diff --git a/packages/runtime/src/scr_child.c b/packages/runtime/src/scr_child.c index 87ead5755..ec685656d 100644 --- a/packages/runtime/src/scr_child.c +++ b/packages/runtime/src/scr_child.c @@ -33,6 +33,7 @@ static void scr_ipc_deliver(void); static void scr_ipc_settle(ScrIpc *ipc); static void scr_child_fire_spawn(ScrChild *c); static void scr_child_drop_spawn_listeners(ScrChild *c); +static void scr_child_loop_install(void); static ScrArr *scr_fork_argv(double target, uintptr_t read_handle, uintptr_t write_handle, ScrArr *args) { @@ -2372,6 +2373,7 @@ ScrChild *scr_spawn_opts(ScrStr *cmd, ScrArr *args, double in_mode, scr_children_reffed_n++; c->next = scr_children; scr_children = scr_child_retain(c); + scr_child_loop_install(); return c; } @@ -5081,6 +5083,7 @@ ScrChild *scr_spawn_opts(ScrStr *cmd, ScrArr *args, double in_mode, scr_children_reffed_n++; c->next = scr_children; scr_children = scr_child_retain(c); + scr_child_loop_install(); return c; } @@ -6087,6 +6090,7 @@ static ScrIpc *scr_ipc_new(ScrChildStream *reader, ScrChildWriter *writer) { ipc->registered = true; ipc->next = scr_ipcs; scr_ipcs = scr_ipc_retain(ipc); /* service-registry reference */ + scr_child_loop_install(); ipc->writer_error_cb = scr_ipc_closure(ipc, (void *)&scr_ipc_writer_error); scr_child_writer_on_error(writer, ipc->writer_error_cb, &scr_ipc_writer_error, false); @@ -6406,6 +6410,7 @@ void scr_child_ipc_on_disconnect(ScrChild *c, ScrClosure *cb, bool once) { bool scr_process_ipc_connected(void) { return scr_process_ipc != NULL && scr_process_ipc->connected; } +bool scr_process_ipc_open(void) { return scr_process_ipc != NULL; } bool scr_process_ipc_send(ScrStr *json) { return scr_ipc_send(scr_process_ipc, json, NULL, NULL); } @@ -6458,7 +6463,7 @@ static ScrDyn *scr_child_bridge_readable(ScrChildStream *reader, ScrDyn **cached return NULL; } if (!*cached) { - ScrStream *stream = scr_child_stream_ops->readable(); + ScrStream *stream = scr_child_stream_ops->readable("Socket"); scr_child_stream_on_data(reader, scr_child_bridge_callback(stream, (void *)scr_child_bridge_data), scr_child_bridge_data, false); scr_child_stream_on_end(reader, scr_child_bridge_callback(stream, (void *)scr_child_bridge_end), false); @@ -6578,7 +6583,7 @@ static ScrDyn *scr_child_bridge_writable(ScrChildWriter *writer, ScrDyn **cached return NULL; } if (!*cached) { - ScrStream *stream = scr_child_stream_ops->writable( + ScrStream *stream = scr_child_stream_ops->writable("Socket", scr_child_writer_bridge_callback(writer, (void *)scr_child_bridge_write), scr_child_bridge_write, scr_child_writer_bridge_callback(writer, (void *)scr_child_bridge_final), scr_child_bridge_final); *cached = scr_child_writable_box(stream); @@ -6755,6 +6760,26 @@ void scr_child_dyn_install(void) { scr_dyn_handle_install(SCR_DYNH_CHILD, &scr_child_dyn_ops); } +static const ScrLoopChildren scr_child_loop_hooks = { + &scr_children_pending, + &scr_children_ready, + &scr_children_reffed_pending, + &scr_children_failed_pending, + &scr_children_poll, + &scr_children_wait, + &scr_children_wake_fd, + &scr_children_teardown, +}; + +/* Points the loop at the child unit (once per thread, like the + * registries) when the first child or IPC channel is created. */ +static void scr_child_loop_install(void) { + static SCR_TL bool installed = false; + if (installed) return; + installed = true; + scr_loop_set_children(&scr_child_loop_hooks); +} + void scr_child_on_dyn(ScrChild *child, ScrStr *event, ScrDyn *listener) { scr_child_dyn_install(); ScrDyn *self = scr_dyn_new_handle(child, SCR_DYNH_CHILD); diff --git a/packages/runtime/src/scr_child.h b/packages/runtime/src/scr_child.h index 1187985c8..f59e02e74 100644 --- a/packages/runtime/src/scr_child.h +++ b/packages/runtime/src/scr_child.h @@ -182,6 +182,9 @@ void scr_child_ipc_on_disconnect(ScrChild *c, ScrClosure *cb /*moves*/, bool once); double scr_process_fork_target(double target_count); bool scr_process_ipc_connected(void); +/* Whether this fork child opened its channel (process.send's presence in + * a program with fork targets; disconnecting keeps it). */ +bool scr_process_ipc_open(void); bool scr_process_ipc_send(ScrStr *json); bool scr_process_ipc_send_cb(ScrStr *json, ScrClosure *cb /*moves*/, ScrIpcSendFn fn); @@ -253,9 +256,13 @@ void scr_child_dyn_install(void); void scr_child_on_dyn(ScrChild *child, ScrStr *event, ScrDyn *listener); /* borrowed */ /* Optional native-stream bridge. The table avoids a stream dependency * in programs that only use the child-process surface. */ +/* readable/writable take the stream's class name (the constructor name + * Node reports: Socket for child stdio, ReadableWorkerStdio and + * WritableWorkerStdio for worker stdio). */ typedef struct ScrChildStreamOps { - ScrStream *(*readable)(void); - ScrStream *(*writable)(ScrClosure *, ScrStreamChunkInv, ScrClosure *, ScrStreamPlainInv); + ScrStream *(*readable)(const char *cls); + ScrStream *(*writable)(const char *cls, ScrClosure *, ScrStreamChunkInv, ScrClosure *, + ScrStreamPlainInv); void *(*retain)(void *); void (*release)(void *); ScrTraceFn trace; @@ -292,5 +299,19 @@ bool scr_children_ready(void); bool scr_children_failed_pending(void); void scr_children_poll(void); bool scr_children_wait(double max_wait_ms); +/* The loop reaches the functions above through this table. The child + * unit installs it with the first spawned child or IPC channel, so + * programs that never create either do not link the unit. */ +typedef struct ScrLoopChildren { + bool (*pending)(void); + bool (*ready)(void); + bool (*reffed_pending)(void); + bool (*failed_pending)(void); + void (*poll)(void); + bool (*wait)(double max_wait_ms); + int (*wake_fd)(void); + void (*teardown)(void); +} ScrLoopChildren; +void scr_loop_set_children(const ScrLoopChildren *hooks); #endif /* SCR_CHILD_H */ diff --git a/packages/runtime/src/scr_crypto.h b/packages/runtime/src/scr_crypto.h index 4b6b6a6a6..d71f46b13 100644 --- a/packages/runtime/src/scr_crypto.h +++ b/packages/runtime/src/scr_crypto.h @@ -71,6 +71,7 @@ void scr_crypto_hash_stream_set(ScrCryptoHash *hash, void *stream, void (*releas void scr_crypto_hash_stream_detach(void *hash); void scr_crypto_hash_stream_close(ScrCryptoHash *hash); bool scr_crypto_hash_stream_usable(const ScrCryptoHash *hash); +bool scr_crypto_hash_is_hmac(const ScrCryptoHash *hash); void scr_crypto_hash_stream_hold(ScrCryptoHash *hash); void scr_crypto_hash_stream_unhold(ScrCryptoHash *hash); ScrStream *scr_crypto_hash_stream_piped(ScrCryptoHash *hash); diff --git a/packages/runtime/src/scr_cycle.c b/packages/runtime/src/scr_cycle.c index d700e6ba8..fd5d64c81 100644 --- a/packages/runtime/src/scr_cycle.c +++ b/packages/runtime/src/scr_cycle.c @@ -121,7 +121,9 @@ * mature the same two rules one level down: a growth fraction * measured from the last mature-or-full pass and scheduled * by everything mature passes free, and a backlog rule over - * the mature buffer. + * the mature buffer. Until a mature pass has had + * candidates, the backlog threshold widens with the growth + * fraction (scr_cyc_backlog_threshold). * scheduled age a mature or old candidate has waited through a nursery's * worth of event-loop checkpoints. Candidate COUNT cannot * bound the garbage behind one root, and an idle heap does @@ -217,6 +219,7 @@ typedef struct { unsigned char growth_shift[SCR_CYC_NGENS]; unsigned char growth_streak[SCR_CYC_NGENS]; unsigned backlog_shift[SCR_CYC_NGENS]; + bool backlog_judged[SCR_CYC_NGENS]; size_t fallen; size_t live_seen; size_t full_after; @@ -256,6 +259,7 @@ static _Thread_local ScrCycThread scr_cyc_thread = {.gen_limit = SCR_CYC_OLD}; #define scr_cyc_growth_shift (scr_cyc_thread.growth_shift) #define scr_cyc_growth_streak (scr_cyc_thread.growth_streak) #define scr_cyc_backlog_shift (scr_cyc_thread.backlog_shift) +#define scr_cyc_backlog_judged (scr_cyc_thread.backlog_judged) #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) @@ -535,11 +539,13 @@ static unsigned scr_cyc_growth_max_shift(void) { } /* Doublings of each generation's backlog threshold earned by consecutive - * passes that found almost nothing to free (see scr_cyc_backlog_threshold). + * passes that found almost nothing to free (see scr_cyc_backlog_threshold), + * and whether a pass at that level has had candidates to judge them by. * Only the mature and old entries are used. */ #define SCR_CYC_BACKLOG_MAX_SHIFT 8 #ifndef SCR_WORKERS static SCR_TL unsigned scr_cyc_backlog_shift[SCR_CYC_NGENS]; +static SCR_TL bool scr_cyc_backlog_judged[SCR_CYC_NGENS]; #endif /* Mature objects in the last full pass's dead set (scr_cyc_adapt_growth). */ @@ -684,11 +690,21 @@ static SCR_CYC_SCHEDULING bool scr_cyc_grown(unsigned gen) { * this trigger rests, leaving heap growth, the scheduled age and exit to * collect. That only delays garbage made from existing data: replacing it * requires allocation, which grows the heap and trips a growth trigger. The - * first productive pass at that level restores the original fraction. */ + * first productive pass at that level restores the original fraction. + * + * Only passes that had candidates adapt the threshold. Until one has, the + * level uses its growth fraction's doublings instead (scr_cyc_adapt_growth), + * which every growth-triggered pass at the level adapts, candidates or not. + * A program can keep a level's buffer empty while its heap grows, so that + * growth passes there find nothing and widen, and then fill the buffer with + * live objects (a structure it builds and reads as it builds it); at the + * base threshold, the first backlog pass would walk a quarter of the live + * heap to find nothing. */ static size_t scr_cyc_backlog_threshold(unsigned gen) { size_t t = scr_cyc_live / SCR_CYC_GROWTH_DIV; if (t < SCR_CYC_NURSERY_CANDIDATES) t = SCR_CYC_NURSERY_CANDIDATES; - unsigned shift = scr_cyc_backlog_shift[gen]; + unsigned shift = + scr_cyc_backlog_judged[gen] ? scr_cyc_backlog_shift[gen] : scr_cyc_growth_shift[gen]; return t > (SIZE_MAX >> shift) ? SIZE_MAX : t << shift; } @@ -788,6 +804,7 @@ static SCR_CYC_SCHEDULING void scr_cyc_scheduled_pass(unsigned gen, bool by_grow size_t old_reclaimed = scr_cyc_old_freed - old_freed_before + scr_cyc_mature_dead; if (gen == SCR_CYC_NURSERY) return; if (scr_cands.n != 0) { + scr_cyc_backlog_judged[gen] = true; if (freed * SCR_CYC_GROWTH_DIV >= scr_cands.n) scr_cyc_backlog_shift[gen] = 0; else if (scr_cyc_backlog_shift[gen] < SCR_CYC_BACKLOG_MAX_SHIFT) @@ -2088,6 +2105,13 @@ size_t scr_cyc_pass_at(unsigned gen) { return scr_cyc_pass(gen); } /* Tests only: route appended candidates of bypassing types, as the next * scheduling decision or pass would. */ void scr_cyc_route_bypassed_at(void) { scr_cyc_route_bypassed(); } +/* Tests only: the backlog threshold at `gen`, and forgetting what passes + * there have taught it, as on a thread that has not collected yet. */ +size_t scr_cyc_backlog_threshold_at(unsigned gen) { return scr_cyc_backlog_threshold(gen); } +void scr_cyc_forget_backlog_at(unsigned gen) { + scr_cyc_backlog_shift[gen] = 0; + scr_cyc_backlog_judged[gen] = false; +} #endif size_t scr_cyc_collect_set(const ScrCycSet *set, unsigned gen) { diff --git a/packages/runtime/src/scr_dgram.h b/packages/runtime/src/scr_dgram.h index 6c8782da0..dac772c93 100644 --- a/packages/runtime/src/scr_dgram.h +++ b/packages/runtime/src/scr_dgram.h @@ -88,6 +88,12 @@ void scr_dns_lookup_service(const ScrDyn *address, const ScrDyn *port, const Scr typedef struct ScrPromise ScrPromise; ScrPromise *scr_dns_lookup_promise(const ScrDyn *hostname, const ScrDyn *options); ScrPromise *scr_dns_lookup_service_promise(const ScrDyn *address, const ScrDyn *port); +/* node:dns/promises (scr_dns_promises.c, linked only for the module's + * promise forms): lib/internal/dns/promises.js's stricter argument checks + * throw synchronously; the promise (+1), or NULL with the argument error + * pending. Arguments borrowed. */ +ScrPromise *scr_dns_promises_lookup(const ScrDyn *hostname, const ScrDyn *options); +ScrPromise *scr_dns_promises_lookup_service(const ScrDyn *address, const ScrDyn *port); ScrDyn *scr_dns_get_servers(void); /* +1 */ ScrStr *scr_dns_get_default_result_order(void); /* +1 */ void scr_dns_set_default_result_order(const ScrDyn *order); diff --git a/packages/runtime/src/scr_dns_promises.c b/packages/runtime/src/scr_dns_promises.c new file mode 100644 index 000000000..e197271f4 --- /dev/null +++ b/packages/runtime/src/scr_dns_promises.c @@ -0,0 +1,312 @@ +/* node:dns/promises — lookup(hostname[, options]) and lookupService(address, + * port) as promises over scr_dgram.c's callback forms. LINK-GATED: compiled + * into the binary only when the IR uses the promise forms (the dnsPromises + * runtime feature), so callback-only and dns-free programs keep their exact + * executables. This unit uses only scr_dgram.c's public surface. + * + * lookup follows lib/internal/dns/promises.js: its argument checks are + * stricter than the callback form's (family is a number only) and throw + * synchronously, before any promise exists (a NUL byte in the hostname + * throws first, as in the callback form). An empty hostname (with the + * shared DEP0118 warning) and an IP literal settle at once, as Node resolves + * them inside the promise executor; other names resolve through the + * callback form's getaddrinfo and deliveries, with normalized options. + * lookup fulfills with { address, family } (an array of them for all: + * true), lookupService with { hostname, service }; failures reject with the + * callback forms' errors. */ +#include "scr_platform.h" +#include "scr_runtime.h" + +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif + +/* caps[0]: the destination promise. */ +static ScrPromise *scr_dnsp_promise_of(ScrClosure *clo) { + return (ScrPromise *)scr_box_get_ref(clo->caps[0]); +} + +static void scr_dnsp_fulfill(ScrPromise *p, ScrDyn *value /* moves */) { + scr_promise_fulfill_ref(p, value, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); +} + +/* A callback whose first argument is an error rejects the promise with it. */ +static bool scr_dnsp_rejects(ScrPromise *p, ScrDyn *const *args, size_t argc) { + if (argc == 0 || args[0]->kind == SCR_DYN_NULL || args[0]->kind == SCR_DYN_UNDEF) return false; + scr_dyn_throw(scr_dyn_retain(args[0])); + scr_promise_reject_pending(p); + return true; +} + +static ScrDyn *scr_dnsp_arg(ScrDyn *const *args, size_t argc, size_t i) { + return i < argc ? scr_dyn_retain(args[i]) : scr_dyn_undefined(); +} + +static ScrDyn *scr_dnsp_pair(const char *a, size_t alen, ScrDyn *av, const char *b, size_t blen, + ScrDyn *bv) { + ScrDyn *entry = scr_dyn_new_obj(); + scr_dyn_obj_set(entry, a, alen, av); + scr_dyn_obj_set(entry, b, blen, bv); + return entry; +} + +/* (err, address, family) -> { address, family } */ +static ScrDyn *scr_dnsp_one_settle(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrPromise *p = scr_dnsp_promise_of(clo); + if (!scr_dnsp_rejects(p, args, argc)) { + scr_dnsp_fulfill(p, scr_dnsp_pair("address", 7, scr_dnsp_arg(args, argc, 1), "family", 6, + scr_dnsp_arg(args, argc, 2))); + } + scr_promise_release(p); + return scr_dyn_undefined(); +} + +/* (err, addresses) -> addresses */ +static ScrDyn *scr_dnsp_all_settle(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrPromise *p = scr_dnsp_promise_of(clo); + if (!scr_dnsp_rejects(p, args, argc)) scr_dnsp_fulfill(p, scr_dnsp_arg(args, argc, 1)); + scr_promise_release(p); + return scr_dyn_undefined(); +} + +/* (err, hostname, service) -> { hostname, service } */ +static ScrDyn *scr_dnsp_service_settle(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrPromise *p = scr_dnsp_promise_of(clo); + if (!scr_dnsp_rejects(p, args, argc)) { + scr_dnsp_fulfill(p, scr_dnsp_pair("hostname", 8, scr_dnsp_arg(args, argc, 1), "service", 7, + scr_dnsp_arg(args, argc, 2))); + } + scr_promise_release(p); + return scr_dyn_undefined(); +} + +/* The empty-hostname path only needs the callback form's DEP0118 warning. */ +static ScrDyn *scr_dnsp_ignore(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + (void)clo; + (void)args; + (void)argc; + return scr_dyn_undefined(); +} + +static ScrDyn *scr_dnsp_callback(ScrPromise *p, ScrDynThunk thunk, uint32_t arity) { + ScrClosure *clo = scr_closure_new((void *)thunk, 1); + ScrBox *box = scr_box_new_obj(&scr_promise_retain_v, &scr_promise_release_v, NULL); + scr_box_set_ref(box, scr_promise_retain(p)); /* the box owns this reference */ + clo->caps[0] = box; + return scr_dyn_new_func(clo, thunk, arity, "", ""); +} + +static bool scr_dnsp_given(const ScrDyn *v) { + return v != NULL && v->kind != SCR_DYN_UNDEF && v->kind != SCR_DYN_NULL; +} + +static const char *const scr_dnsp_orders[] = { "verbatim", "ipv4first", "ipv6first" }; + +static int scr_dnsp_order_of(const ScrDyn *v) { + if (v->kind != SCR_DYN_STR) return -1; + for (int i = 0; i < 3; i++) { + size_t n = strlen(scr_dnsp_orders[i]); + if (v->v.str->len == n && memcmp(v->v.str->data, scr_dnsp_orders[i], n) == 0) return i; + } + return -1; +} + +static int scr_dnsp_default_order(void) { + ScrStr *name = scr_dns_get_default_result_order(); + ScrDyn *d = scr_dyn_new_str(name); + scr_str_release(name); + int order = scr_dnsp_order_of(d); + scr_dyn_release(d); + return order < 0 ? 0 : order; +} + +/* 4 or 6 for an IP literal (net.isIP), 0 otherwise. */ +static double scr_dnsp_literal_family(const ScrStr *h) { + unsigned char buf[16]; + char tmp[64]; + if (h->len == 0 || h->len >= sizeof tmp) return 0; + memcpy(tmp, h->data, h->len); + tmp[h->len] = '\0'; + if (inet_pton(AF_INET, tmp, buf) == 1) return 4; + if (strchr(tmp, ':') != NULL) { + char *pct = strchr(tmp, '%'); + if (pct) *pct = '\0'; + if (inet_pton(AF_INET6, tmp, buf) == 1) return 6; + } + return 0; +} + +/* validateStringWithoutNullBytes(hostname, 'hostname'): the callback + * form's error. False with it pending. */ +static bool scr_dnsp_host_without_nul(const ScrStr *host) { + if (memchr(host->data, '\0', host->len) == NULL) return true; + ScrStr *shown = scr_str_inspect_quoted((ScrStr *)host, true); + ScrJsonBuf msg; + scr_jb_init(&msg); + scr_jb_puts(&msg, "The argument 'hostname' must be a string without null bytes. Received "); + scr_jb_put_str(&msg, shown); + scr_str_release(shown); + ScrStr *text = scr_jb_finish(&msg); + scr_throw_error_msg_code(SCR_ERR_TYPE, text->data, text->len, "ERR_INVALID_ARG_VALUE"); + scr_str_release(text); + return false; +} + +/* lookup's options, validated as lib/internal/dns/promises.js validates + * them. False with the argument error pending. */ +static bool scr_dnsp_options(const ScrDyn *opts, double *family, bool *all, int *order) { + if (opts->kind == SCR_DYN_NUM) { + double f = opts->v.num; + if (f != 0 && f != 4 && f != 6) { + scr_dyn_arg_value_fail("family", "must be one of: 0, 4, 6", opts); + return false; + } + *family = f + 0.0; /* Node coerces -0 to +0 */ + return true; + } + if (opts->kind != SCR_DYN_UNDEF && opts->kind != SCR_DYN_OBJ && opts->kind != SCR_DYN_NULL) { + scr_dyn_arg_type_fail("options", "of type object or integer", opts); + return false; + } + if (opts->kind != SCR_DYN_OBJ) return true; + const ScrDyn *v = scr_dyn_obj_get(opts, "hints", 5); + if (scr_dnsp_given(v)) { + if (v->kind != SCR_DYN_NUM) { + scr_dyn_prop_type_fail("options.hints", "of type number", v); + return false; + } + if (v->v.num != 0) { + scr_throw_error_msg_code(SCR_ERR_ERROR, "dns.lookup hints are not supported yet", 38, "SC2020"); + return false; + } + } + v = scr_dyn_obj_get(opts, "family", 6); + if (scr_dnsp_given(v)) { + if (v->kind != SCR_DYN_NUM || (v->v.num != 0 && v->v.num != 4 && v->v.num != 6)) { + scr_dyn_arg_value_fail("options.family", "must be one of: 0, 4, 6", v); + return false; + } + *family = v->v.num + 0.0; + } + v = scr_dyn_obj_get(opts, "all", 3); + if (scr_dnsp_given(v)) { + if (v->kind != SCR_DYN_BOOL) { + scr_dyn_prop_type_fail("options.all", "of type boolean", v); + return false; + } + *all = v->v.b; + } + v = scr_dyn_obj_get(opts, "verbatim", 8); + if (scr_dnsp_given(v)) { + if (v->kind != SCR_DYN_BOOL) { + scr_dyn_prop_type_fail("options.verbatim", "of type boolean", v); + return false; + } + *order = v->v.b ? 0 : 1; + } + v = scr_dyn_obj_get(opts, "order", 5); + if (scr_dnsp_given(v)) { + int o = scr_dnsp_order_of(v); + if (o < 0) { + scr_dyn_arg_value_fail("options.order", "must be one of: 'verbatim', 'ipv4first', 'ipv6first'", + v); + return false; + } + *order = o; + } + return true; +} + +/* The callback form's options for a validated promise lookup. +1 dyn. */ +static ScrDyn *scr_dnsp_normalized(double family, bool all, int order) { + ScrDyn *o = scr_dyn_new_obj(); + scr_dyn_obj_set(o, "family", 6, scr_dyn_new_num(family)); + scr_dyn_obj_set(o, "all", 3, scr_dyn_new_bool(all)); + const char *name = scr_dnsp_orders[order]; + ScrStr *s = scr_str_new(name, strlen(name)); + scr_dyn_obj_set(o, "order", 5, scr_dyn_new_str(s)); + scr_str_release(s); + return o; +} + +static ScrPromise *scr_dnsp_lookup(const ScrDyn *hostname, const ScrDyn *opts) { + bool host_given = scr_dyn_truthy(hostname); + if (host_given && hostname->kind != SCR_DYN_STR) { + scr_dyn_arg_type_fail("hostname", "of type string", hostname); + return NULL; + } + if (host_given && !scr_dnsp_host_without_nul(hostname->v.str)) return NULL; + double family = 0; + bool all = false; + int order = scr_dnsp_default_order(); + if (!scr_dnsp_options(opts, &family, &all, &order)) return NULL; + ScrPromise *p = scr_promise_new(); + if (!host_given) { + /* The callback form emits the shared DEP0118 warning; its own answer + * goes to a callback that ignores it. */ + ScrDyn *normalized = scr_dnsp_normalized(family, all, order); + ScrDyn *ignore = scr_dyn_new_func(scr_closure_new((void *)scr_dnsp_ignore, 0), scr_dnsp_ignore, + 0, "", ""); + scr_dns_lookup_dyn(hostname, normalized, ignore, false); + scr_dyn_release(ignore); + scr_dyn_release(normalized); + if (all) { + scr_dnsp_fulfill(p, scr_dyn_new_arr()); + } else { + scr_dnsp_fulfill(p, scr_dnsp_pair("address", 7, scr_dyn_new_null(), "family", 6, + scr_dyn_new_num(family == 6 ? 6 : 4))); + } + return p; + } + const ScrStr *host = hostname->v.str; + double literal = scr_dnsp_literal_family(host); + if (literal != 0) { + ScrDyn *entry = scr_dnsp_pair("address", 7, scr_dyn_new_str((ScrStr *)host), "family", 6, + scr_dyn_new_num(literal)); + if (all) { + ScrDyn *list = scr_dyn_new_arr(); + scr_dyn_arr_push(list, entry); + entry = list; + } + scr_dnsp_fulfill(p, entry); + return p; + } + ScrDyn *cb = scr_dnsp_callback(p, all ? scr_dnsp_all_settle : scr_dnsp_one_settle, all ? 2 : 3); + { + ScrDyn *normalized = scr_dnsp_normalized(family, all, order); + scr_dns_lookup_dyn(hostname, normalized, cb, false); + scr_dyn_release(normalized); + } + scr_dyn_release(cb); + if (scr_exc_pending()) { + scr_promise_release(p); + return NULL; + } + return p; +} + +ScrPromise *scr_dns_promises_lookup(const ScrDyn *hostname, const ScrDyn *options) { + if (options->kind != SCR_DYN_TYPED_REF) return scr_dnsp_lookup(hostname, options); + /* a typed options record arrives by reference */ + ScrDyn *view = scr_dyn_typed_ref_materialize(options); + ScrPromise *p = scr_exc_pending() ? NULL : scr_dnsp_lookup(hostname, view); + scr_dyn_release(view); + return p; +} + +ScrPromise *scr_dns_promises_lookup_service(const ScrDyn *address, const ScrDyn *port) { + ScrPromise *p = scr_promise_new(); + ScrDyn *cb = scr_dnsp_callback(p, scr_dnsp_service_settle, 3); + scr_dns_lookup_service(address, port, cb, false); + scr_dyn_release(cb); + if (scr_exc_pending()) { + scr_promise_release(p); + return NULL; + } + return p; +} diff --git a/packages/runtime/src/scr_dyn.h b/packages/runtime/src/scr_dyn.h index a3d506a10..401bf2b39 100644 --- a/packages/runtime/src/scr_dyn.h +++ b/packages/runtime/src/scr_dyn.h @@ -191,6 +191,11 @@ typedef enum { X(SHARED_ARRAY_BUFFER, 37) \ X(STDIO, 27) /* stable process stdin/stdout/stderr values */ \ X(URL, 28) /* native WHATWG URL */ \ + X(WASM_GLOBAL, 61) /* WebAssembly.Global */ \ + X(WASM_INSTANCE, 58) /* WebAssembly.Instance */ \ + X(WASM_MEMORY, 59) /* WebAssembly.Memory */ \ + X(WASM_MODULE, 57) /* WebAssembly.Module (scr_wasm.c) */ \ + X(WASM_TABLE, 60) /* WebAssembly.Table */ \ X(WEAK_MAP, 23) /* native weak-key metadata */ \ X(WEAK_SET, 24) /* native weak membership */ \ X(WEB_CONTROLLER, 11) /* native ReadableStreamDefaultController */ \ @@ -646,6 +651,9 @@ ScrDyn *scr_dyn_native_search_params(ScrSearchParams *value); bool scr_dyn_native_search_params_is(const ScrDyn *value); bool scr_dyn_native_date_is(const ScrDyn *value); ScrDyn *scr_dyn_native_date_new(const ScrDyn *arguments); +ScrDyn *scr_dyn_native_date_new_fields(const ScrDyn *arguments); +ScrDyn *scr_dyn_native_date_new_value(const ScrDyn *arguments); +ScrStr *scr_dyn_object_tag_of(const ScrDyn *d); ScrDyn *scr_dyn_native_date_from_ms(double ms); double scr_dyn_native_date_value(const ScrDyn *value); ScrDyn *scr_dyn_native_regex(ScrRegex *value); @@ -697,6 +705,19 @@ ScrDyn *scr_dyn_symbol_ref(ScrSym *value, void (*release)(ScrSym *), ScrStr *(*r ScrDyn *scr_dyn_new_str(ScrStr *s); ScrDyn *scr_dyn_new_arr(void); ScrDyn *scr_dyn_array_constructor(void); +/* The interned builtin constructor `name` (Object, Date, RegExp, Map, Set, + * Promise, WeakMap, WeakSet) as a dynamic function value: +1. */ +ScrDyn *scr_dyn_global_constructor(const ScrStr *name); +/* A builtin global function (setTimeout, setImmediate, process.nextTick, ...) + * as an interned dynamic function value: +1. */ +ScrDyn *scr_dyn_builtin_function(const ScrStr *name); +/* A constant-key read of an Object.prototype member name: takes the + * ordinary read's +1 result and answers the inherited member in place of + * an undefined one (scr_dyn_invoke.c). */ +ScrDyn *scr_dyn_inherited_read(const ScrDyn *d, const ScrStr *key, ScrDyn *read); +/* The member `key` read on Object.prototype itself, or NULL for any other + * receiver (scr_dyn_invoke.c). */ +ScrDyn *scr_dyn_object_prototype_member(const ScrDyn *d, const ScrStr *key); bool scr_dyn_arr_has_index(const ScrDyn *arr, size_t index); void scr_dyn_arr_push_hole(ScrDyn *arr); ScrDyn *scr_dyn_new_obj(void); @@ -937,7 +958,17 @@ double scr_dyn_arr_len(const ScrDyn *d); ScrDyn *scr_dyn_arr_at(const ScrDyn *d, double i); ScrDyn *scr_dyn_arr_named_get(const ScrDyn *d, const ScrStr *key); void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value); +void scr_dyn_obj_set_field(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value); ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor); +void scr_dyn_class_field_attributes(ScrDyn *view, ScrStr *name, double bits); +void scr_dyn_class_view_attributes(ScrDyn *view, double bits); +ScrDyn *scr_class_define_field(ScrDyn *current, double field_bits, double object_bits, ScrStr *name, + ScrDyn *descriptor); +double scr_class_defined_bits(ScrDyn *holder, ScrStr *name); +ScrDyn *scr_class_defined_value(ScrDyn *holder, ScrStr *name); +bool scr_class_descriptor_has_value(ScrDyn *descriptor); +void scr_class_throw_read_only(ScrStr *field, ScrStr *owner); +void scr_class_throw_not_extensible(ScrStr *field); ScrDyn *scr_dyn_get_own_property_descriptor(ScrDyn *target, ScrDyn *key); ScrDyn *scr_dyn_array_proto_call(ScrDyn *target, ScrStr *method, ScrDyn *args); ScrDyn *scr_dyn_array_prototype(void); /* +1, installs native method adapters */ @@ -1066,6 +1097,12 @@ extern SCR_TL size_t scr_uncloneable_count; ScrDyn *scr_dyn_from_error(const ScrError *e); void scr_error_register_dyn(const ScrVt *vt, ScrDyn *(*box)(void *)); ScrDyn *scr_error_dyn_fields(const ScrError *e); +void scr_error_dyn_place_name(ScrDyn *view, const char *after, size_t after_len); +/* An accessor property of a fresh object: enumerable and configurable, + * with the given getter and setter (either may be NULL). Moves both. */ +void scr_dyn_obj_set_accessor(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *getter, ScrDyn *setter); +/* The getter or setter of an own accessor property, +1 (undefined when none). */ +ScrDyn *scr_dyn_obj_accessor_fn(const ScrDyn *obj, const char *key, size_t key_len, bool setter); void scr_error_commit_dyn(ScrError *e, const ScrDyn *view); /* The reverse extraction, riding the same identity cache: a dyn error * that came from a runtime ScrError answers THAT instance (+1; @@ -1116,6 +1153,49 @@ ScrDyn *scr_dyn_define_prototype_props(ScrDyn *target, ScrDyn *descs, ScrDyn *pr * must be static literals (the box never frees them; name may be NULL). */ ScrDyn *scr_dyn_new_func(ScrClosure *clo, ScrDynThunk thunk, uint32_t arity, const char *sig, const char *name); ScrDyn *scr_dyn_new_class(ScrClassObj *cls, const char *type_key); +/* EventEmitter and the stream constructors as untyped constructor values, + * and instanceof over untyped constructor functions + * (scr_dyn_builtin_class.c, linked with the dynBuiltinClass feature). */ +ScrDyn *scr_dyn_builtin_class(double kind); +bool scr_dyn_builtin_instanceof(ScrDyn *value, double kind); +bool scr_dyn_function_instanceof(ScrDyn *value, ScrDyn *callee); +/* The stream half of those constructors (scr_dyn_builtin_stream.c, linked + * with the dynBuiltinStream feature), registered with + * scr_dyn_builtin_class_streams before the first stream class value is + * read: construct runs `Readable.call(this, opts)` and the other stream + * kinds (+1 or NULL with an exception pending), prototype installs a stream + * kind's methods, and event answers EventEmitter.prototype's methods for a + * receiver with stream state (true when it handled the call; *result +1 or + * NULL with an exception pending). */ +enum { + SCR_EE_M_ON, SCR_EE_M_PREPEND, SCR_EE_M_ONCE, SCR_EE_M_PREPEND_ONCE, SCR_EE_M_OFF, + SCR_EE_M_REMOVE_ALL, SCR_EE_M_EMIT, SCR_EE_M_LISTENERS, SCR_EE_M_RAW_LISTENERS, + SCR_EE_M_LISTENER_COUNT, SCR_EE_M_EVENT_NAMES, SCR_EE_M_SET_MAX, SCR_EE_M_GET_MAX, +}; +typedef struct ScrDynStreamOps { + ScrDyn *(*construct)(int kind, ScrDyn *self, ScrDyn *const *args, size_t argc); + void (*prototype)(int kind, ScrDyn *prototype); + bool (*event)(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result); +} ScrDynStreamOps; +void scr_dyn_builtin_class_streams(const ScrDynStreamOps *ops); +/* A stream class value with its stream half (scr_dyn_builtin_stream.c), + * +1; the stream a JavaScript value stands for (a constructor-function + * stream or a boxed runtime stream), +1 or NULL; and the constructor- + * function object of a runtime stream, +1 or NULL. */ +ScrDyn *scr_dyn_builtin_stream_class(double kind); +struct ScrStream *scr_dyn_builtin_stream_of(const ScrDyn *value); +ScrDyn *scr_dyn_builtin_stream_object(struct ScrStream *s); +/* The untyped view of a runtime stream in programs that link that unit + * (+1), a checked cast to a runtime stream class kind (+1 or NULL), and + * the stream.unboxDyn libCall in those programs (scr_stream_unbox_dyn's + * contract, also taking constructor-function streams). */ +ScrDyn *scr_dyn_builtin_stream_view(struct ScrStream *s); +struct ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind); +struct ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *message); +/* stream.dynNative in those programs: scr_stream_dyn_native's contract, + * also taking constructor-function streams that keep the runtime's write + * and end (+1 or NULL). */ +struct ScrStream *scr_dyn_builtin_stream_dyn_native(const ScrDyn *value, double side); bool scr_dyn_class_is_key(const ScrDyn *value, const ScrStr *type_key); bool scr_dyn_class_is(const ScrDyn *value, const char *type_key); /* Calls a dyn value: a non-function kind throws the catchable TypeError @@ -1249,7 +1329,8 @@ typedef struct ScrDynHandleOps { * inspector returns `this`) instead of the bare class name. */ bool inspect_bracketed_past_depth; /* The @@toStringTag when it differs from cls: String() answers - * "[object ]" and util.inspect prints ` [] `. */ + * "[object ]" and util.inspect prints ` [] `, or + * ` ` when the tag ends with "." and cls, as Node does. */ const char *to_string_tag; /* The view has no properties, so util.inspect prints it in full at any * depth, as Node prints `{}`. */ @@ -1309,6 +1390,10 @@ extern SCR_TL ScrDyn *(*scr_dyn_promise_member_fn)(const char *key, size_t len); ScrPromise *scr_dyn_promise_all(const ScrDyn *entries, const ScrDyn *reader); ScrPromise *scr_dyn_promise_all_settled(const ScrDyn *entries, const ScrDyn *reader); ScrPromise *scr_dyn_promise_any(const ScrDyn *entries, const ScrDyn *reader); +/* PromiseResolve over one dyn value (borrowed): a native promise is + * itself, a thenable adopts through its resolve job, a throwing `then` + * read rejects, and any other value fulfills. +1. */ +ScrPromise *scr_dyn_promise_resolve(const ScrDyn *value, const ScrDyn *reader); /* Native predicates for typed reference capsules and boxed errors. */ bool scr_dyn_ref_typeof_is(const ScrDyn *d, const char *name); diff --git a/packages/runtime/src/scr_dyn_builtin_class.c b/packages/runtime/src/scr_dyn_builtin_class.c new file mode 100644 index 000000000..2f947048a --- /dev/null +++ b/packages/runtime/src/scr_dyn_builtin_class.c @@ -0,0 +1,1596 @@ +/* EventEmitter and the node:stream constructors as checked-dynamic + * constructor values. JavaScript constructor functions inherit from them + * the pre-class way: + * + * function Bus() { EventEmitter.call(this); } + * util.inherits(Bus, EventEmitter); + * + * A compiled EventEmitter instance keeps its listeners in native state, but + * the receiver here is an ordinary dyn object built by `new Bus()`. So the + * EventEmitter value is a native function whose prototype carries Node's + * lib/events.js methods over that object's own `_events`, `_eventsCount` + * and `_maxListeners` properties, the state Node's own implementation + * keeps there. Calling it with a receiver runs EventEmitter.init. + * + * Stream is Node's legacy constructor: EventEmitter.call(this, opts) and a + * prototype that inherits EventEmitter's. Readable, Writable, Duplex, + * Transform and PassThrough have prototypes chained as in Node, so + * util.inherits onto them and instanceof work. Their stream half (the + * prototypes' methods and `Readable.call(this, opts)`) lives in + * scr_dyn_builtin_stream.c, which registers itself through + * scr_dyn_builtin_class_streams in programs that link it; without it, + * calling one with a receiver throws the "not supported yet" Error where + * the construction runs. EventEmitter's methods hand a receiver with + * stream state to that unit, whose listeners live in the runtime stream. + * + * One constructor per kind and thread, so every boxing of the same class + * value is the same function. All arguments are borrowed. */ +#include "scr_runtime.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#define BC_PID() ((long)_getpid()) +#else +#include +#define BC_PID() ((long)getpid()) +#endif + +enum { + BC_EMITTER, + BC_STREAM, + BC_READABLE, + BC_WRITABLE, + BC_DUPLEX, + BC_TRANSFORM, + BC_PASSTHROUGH, + BC_COUNT +}; + +static const char *const bc_names[BC_COUNT] = { + "EventEmitter", "Stream", "Readable", "Writable", "Duplex", "Transform", "PassThrough", +}; + +static SCR_TL ScrDyn *bc_ctors[BC_COUNT]; +static SCR_TL bool bc_teardown_registered; +/* Node's private kShapeMode and kCapture symbols, which util.inspect shows + * on every emitter as Symbol(shapeMode) and Symbol(kCapture). */ +enum { BC_SHAPE_MODE, BC_CAPTURE, BC_SYMBOLS }; +static SCR_TL ScrDyn *bc_symbols[BC_SYMBOLS]; + +static ScrDyn *bc_class(int kind); + +/* The stream half, once registered (scr_dyn_builtin_stream.c). */ +static SCR_TL const ScrDynStreamOps *bc_stream_ops; + +/* EventEmitter.prototype's method on a receiver with stream state. */ +static bool bc_stream_event(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result) { + return bc_stream_ops != NULL && bc_stream_ops->event(method, self, args, argc, result); +} + +/* ── property helpers ───────────────────────────────────────────────── */ + +static ScrDyn *bc_key(const char *name) { + ScrStr *text = scr_str_new(name, strlen(name)); + ScrDyn *key = scr_dyn_new_str(text); + scr_str_release(text); + return key; +} + +/* object[key] with object as the receiver: +1, or NULL with an exception + * pending. */ +static ScrDyn *bc_get_key(ScrDyn *object, ScrDyn *key) { + ScrDyn *value = scr_dyn_reflect_get(object, key, object); + if (!value && !scr_exc_pending()) value = scr_dyn_retain(scr_dyn_undefined()); + return value; +} + +static ScrDyn *bc_get(ScrDyn *object, const char *name) { + ScrDyn *key = bc_key(name); + ScrDyn *value = bc_get_key(object, key); + scr_dyn_release(key); + return value; +} + +/* object[key] = value; the value moves in. False with an exception + * pending. */ +static bool bc_set_key(ScrDyn *object, ScrDyn *key, ScrDyn *value) { + scr_dyn_key_set_computed(object, key, value); + scr_dyn_release(value); + return !scr_exc_pending(); +} + +static bool bc_set(ScrDyn *object, const char *name, ScrDyn *value) { + ScrDyn *key = bc_key(name); + bool ok = bc_set_key(object, key, value); + scr_dyn_release(key); + return ok; +} + +static bool bc_undefined(const ScrDyn *value) { + return value->kind == SCR_DYN_UNDEF; +} + +static double bc_number(const ScrDyn *value) { + return value->kind == SCR_DYN_NUM ? value->v.num : 0; +} + +/* receiver.method(...args), dispatched through the receiver's own lookup + * like Node's `this.emit(...)`. +1 or NULL. */ +static ScrDyn *bc_invoke(ScrDyn *receiver, const char *method, ScrDyn *const *args, size_t argc) { + ScrDyn *fn = bc_get(receiver, method); + if (!fn) return NULL; + scr_dyn_this_push_dyn(receiver); + ScrDyn *result = scr_dyn_call(fn, args, argc, method); + scr_dyn_this_pop(); + scr_dyn_release(fn); + return result; +} + +static ScrDyn *bc_apply(ScrDyn *fn, ScrDyn *receiver, ScrDyn *const *args, size_t argc) { + scr_dyn_this_push_dyn(receiver); + ScrDyn *result = scr_dyn_call(fn, args, argc, "listener"); + scr_dyn_this_pop(); + return result; +} + +/* Listener lists are replaced rather than mutated: a fresh array with item + * first, or without the item at index. Node mutates its array in place, + * which nothing outside the emitter observes (emit iterates a copy and + * listeners() answers one). A leak warning already given stays given. */ +static ScrDyn *bc_list_copy(ScrDyn *list, ScrDyn *first, double skip) { + ScrDyn *copy = scr_dyn_new_arr(); + if (first) scr_dyn_arr_push(copy, scr_dyn_retain(first)); + double length = scr_dyn_arr_len(list); + for (double i = 0; i < length; i++) { + if (i == skip) continue; + ScrDyn *item = scr_dyn_index_get(list, i); + if (!item) { + scr_dyn_release(copy); + return NULL; + } + scr_dyn_arr_push(copy, item); + } + ScrDyn *warned = bc_get(list, "warned"); + if (!warned || (scr_dyn_truthy(warned) && !bc_set(copy, "warned", scr_dyn_new_bool(true)))) { + if (warned) scr_dyn_release(warned); + scr_dyn_release(copy); + return NULL; + } + scr_dyn_release(warned); + return copy; +} + +static ScrDyn *bc_unused_args[1]; + +static ScrDyn *bc_arg(ScrDyn *const *args, size_t argc, size_t index) { + return index < argc ? args[index] : scr_dyn_undefined(); +} + +/* The receiver of a prototype method call, +1. Methods called without one + * (an extracted `emitter.on`) read `this` as undefined, as in strict code. */ +static ScrDyn *bc_this(void) { + return scr_dyn_this_get(); +} + +static bool bc_require_object(ScrDyn *self, const char *reading) { + if (self->kind != SCR_DYN_UNDEF && self->kind != SCR_DYN_NULL) return true; + char message[96]; + snprintf(message, sizeof message, "Cannot read properties of %s (reading '%s')", + self->kind == SCR_DYN_NULL ? "null" : "undefined", reading); + scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); + return false; +} + +static bool bc_check_listener(const ScrDyn *listener) { + if (scr_dyn_is_callable(listener)) return true; + scr_dyn_arg_type_fail("listener", "of type function", listener); + return false; +} + +static void bc_teardown(void); + +static ScrDyn *bc_symbol(int which) { + if (!bc_symbols[which]) { + if (!bc_teardown_registered) { + bc_teardown_registered = true; + scr_atexit(bc_teardown); + } + const char *name = which == BC_SHAPE_MODE ? "shapeMode" : "kCapture"; + ScrStr *description = scr_str_new(name, strlen(name)); + ScrSym *symbol = scr_sym_new(description); + scr_str_release(description); + bc_symbols[which] = scr_dyn_new_symbol(symbol); + scr_sym_release(symbol); + } + return bc_symbols[which]; +} + +/* ── EventEmitter.init ──────────────────────────────────────────────── */ + +/* `_events` is fresh unless the receiver already has its own (Node's + * shapeMode, a subclass that pre-populates it); `_maxListeners` keeps a + * value set before the call. */ +static bool bc_emitter_init(ScrDyn *self, ScrDyn *opts) { + if (!bc_require_object(self, "_events")) return false; + ScrDyn *events = bc_get(self, "_events"); + if (!events) return false; + bool fresh = bc_undefined(events); + if (!fresh) { + ScrDyn *proto = scr_dyn_get_prototype(self); + if (!proto) { + scr_dyn_release(events); + return false; + } + if (proto->kind != SCR_DYN_NULL) { + ScrDyn *inherited = bc_get(proto, "_events"); + if (!inherited) { + scr_dyn_release(proto); + scr_dyn_release(events); + return false; + } + fresh = scr_dyn_strict_eq(events, inherited); + scr_dyn_release(inherited); + } + scr_dyn_release(proto); + } + scr_dyn_release(events); + if (fresh) { + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto())) return false; + if (!bc_set(self, "_eventsCount", scr_dyn_new_num(0))) return false; + } + if (!bc_set_key(self, bc_symbol(BC_SHAPE_MODE), scr_dyn_new_bool(!fresh))) return false; + ScrDyn *max = bc_get(self, "_maxListeners"); + if (!max) return false; + bool keep = scr_dyn_truthy(max); + if (!bc_set(self, "_maxListeners", keep ? max : (scr_dyn_release(max), scr_dyn_retain(scr_dyn_undefined())))) + return false; + if (opts->kind != SCR_DYN_UNDEF && opts->kind != SCR_DYN_NULL) { + ScrDyn *capture = bc_get(opts, "captureRejections"); + if (!capture) return false; + bool requested = !bc_undefined(capture); + if (requested && capture->kind != SCR_DYN_BOOL) { + scr_dyn_arg_type_fail("options.captureRejections", "of type boolean", capture); + scr_dyn_release(capture); + return false; + } + bool enabled = requested && capture->v.b; + scr_dyn_release(capture); + if (enabled) { + static const char message[] = "EventEmitter captureRejections on a constructor function is not supported yet"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC2020"); + return false; + } + } + return bc_set_key(self, bc_symbol(BC_CAPTURE), scr_dyn_new_bool(false)); +} + +/* ── listener bookkeeping (lib/events.js) ───────────────────────────── */ + +static double bc_max_listeners(ScrDyn *self) { + ScrDyn *max = bc_get(self, "_maxListeners"); + if (!max) return -1; + double value; + if (bc_undefined(max)) { + value = scr_emitter_get_default_max(); + } else { + value = bc_number(max); + } + scr_dyn_release(max); + return value; +} + +static void bc_warn_tick(ScrClosure *self) { + ScrStr *message = (ScrStr *)scr_box_get_ref(self->caps[0]); + if (scr_warning_emit_hook) { + scr_warning_emit_hook("MaxListenersExceededWarning", NULL, message); + } else { + fflush(stdout); + fprintf(stderr, "(node:%ld) MaxListenersExceededWarning: %.*s\n", BC_PID(), + (int)message->len, message->data); + if (!scr_warning_trace_hint_shown) { + scr_warning_trace_hint_shown = true; + fputs("(Use `node --trace-warnings ...` to show where the warning was created)\n", stderr); + } + } + scr_str_release(message); +} + +/* The constructor name util.inspect prints at depth -1: `[Bus]`. */ +static ScrStr *bc_constructor_name(ScrDyn *self) { + ScrDyn *ctor = bc_get(self, "constructor"); + ScrStr *name = NULL; + if (ctor && scr_dyn_is_callable(ctor)) { + ScrDyn *text = bc_get(ctor, "name"); + if (text && text->kind == SCR_DYN_STR && text->v.str->len) name = scr_str_retain(text->v.str); + if (text) scr_dyn_release(text); + } + if (ctor) scr_dyn_release(ctor); + if (scr_exc_pending()) scr_exc_clear(); + return name ? name : scr_str_new("Object", 6); +} + +static void bc_warn_leak(ScrDyn *self, ScrDyn *type, double count, double max) { + char counted[32], limit[32]; + snprintf(counted, sizeof counted, "%g", count); + snprintf(limit, sizeof limit, "%g", max); + ScrStr *event = scr_dyn_string_coerce_js(type); + if (!event) { + scr_exc_clear(); + event = scr_str_new("", 0); + } + ScrStr *cls = bc_constructor_name(self); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Possible EventEmitter memory leak detected. "); + scr_jb_puts(&text, counted); + scr_jb_putc(&text, ' '); + scr_jb_put_str(&text, event); + scr_jb_puts(&text, " listeners added to ["); + scr_jb_put_str(&text, cls); + scr_jb_puts(&text, "]. MaxListeners is "); + scr_jb_puts(&text, limit); + scr_jb_puts(&text, ". Use emitter.setMaxListeners() to increase limit"); + scr_str_release(event); + scr_str_release(cls); + ScrClosure *tick = scr_closure_new((void *)&bc_warn_tick, 1); + ScrBox *box = scr_box_new(SCR_BOX_STR); + scr_box_set_ref(box, scr_jb_finish(&text)); + tick->caps[0] = box; + scr_next_tick(tick); +} + +/* _addListener(target, type, listener, prepend): +1 target or NULL. */ +static ScrDyn *bc_add_listener(ScrDyn *self, ScrDyn *type, ScrDyn *listener, bool prepend) { + if (!bc_check_listener(listener)) return NULL; + ScrDyn *events = bc_get(self, "_events"); + if (!events) return NULL; + ScrDyn *existing = NULL; + if (bc_undefined(events)) { + scr_dyn_release(events); + events = scr_dyn_new_obj_null_proto(); + if (!bc_set(self, "_events", scr_dyn_retain(events)) || !bc_set(self, "_eventsCount", scr_dyn_new_num(0))) + goto fail; + } else { + ScrDyn *watcher = bc_get(events, "newListener"); + if (!watcher) goto fail; + bool watched = !bc_undefined(watcher); + scr_dyn_release(watcher); + if (watched) { + ScrDyn *inner = bc_get(listener, "listener"); + if (!inner) goto fail; + ScrDyn *name = bc_key("newListener"); + ScrDyn *args[3] = { name, type, bc_undefined(inner) || inner->kind == SCR_DYN_NULL ? listener : inner }; + ScrDyn *emitted = bc_invoke(self, "emit", args, 3); + scr_dyn_release(name); + scr_dyn_release(inner); + if (!emitted) goto fail; + scr_dyn_release(emitted); + /* the listener may have replaced _events */ + scr_dyn_release(events); + events = bc_get(self, "_events"); + if (!events) return NULL; + } + existing = bc_get_key(events, type); + if (!existing) goto fail; + } + if (!existing || bc_undefined(existing)) { + if (!bc_set_key(events, type, scr_dyn_retain(listener))) goto fail; + ScrDyn *count = bc_get(self, "_eventsCount"); + if (!count) goto fail; + double next = bc_number(count) + 1; + scr_dyn_release(count); + if (!bc_set(self, "_eventsCount", scr_dyn_new_num(next))) goto fail; + } else { + if (scr_dyn_is_callable(existing)) { + ScrDyn *list = scr_dyn_new_arr(); + scr_dyn_arr_push(list, scr_dyn_retain(prepend ? listener : existing)); + scr_dyn_arr_push(list, scr_dyn_retain(prepend ? existing : listener)); + if (!bc_set_key(events, type, scr_dyn_retain(list))) { + scr_dyn_release(list); + goto fail; + } + scr_dyn_release(existing); + existing = list; + } else if (prepend) { + ScrDyn *list = bc_list_copy(existing, listener, -1); + if (!list) goto fail; + if (!bc_set_key(events, type, scr_dyn_retain(list))) { + scr_dyn_release(list); + goto fail; + } + scr_dyn_release(existing); + existing = list; + } else if (existing->kind == SCR_DYN_ARR) { + scr_dyn_arr_push(existing, scr_dyn_retain(listener)); + } + double max = bc_max_listeners(self); + if (scr_exc_pending()) goto fail; + double length = existing->kind == SCR_DYN_ARR ? scr_dyn_arr_len(existing) : 0; + if (max > 0 && length > max) { + ScrDyn *warned = bc_get(existing, "warned"); + if (!warned) goto fail; + bool already = scr_dyn_truthy(warned); + scr_dyn_release(warned); + if (!already) { + if (!bc_set(existing, "warned", scr_dyn_new_bool(true))) goto fail; + bc_warn_leak(self, type, length, max); + } + } + } + if (existing) scr_dyn_release(existing); + scr_dyn_release(events); + return scr_dyn_retain(self); +fail: + if (existing) scr_dyn_release(existing); + scr_dyn_release(events); + return NULL; +} + +/* removeListener(type, listener): +1 self or NULL. */ +static ScrDyn *bc_remove_listener(ScrDyn *self, ScrDyn *type, ScrDyn *listener) { + if (!bc_check_listener(listener)) return NULL; + ScrDyn *events = bc_get(self, "_events"); + if (!events) return NULL; + if (bc_undefined(events)) { + scr_dyn_release(events); + return scr_dyn_retain(self); + } + ScrDyn *list = bc_get_key(events, type); + if (!list) goto fail_events; + if (bc_undefined(list)) { + scr_dyn_release(list); + scr_dyn_release(events); + return scr_dyn_retain(self); + } + ScrDyn *wrapped = scr_dyn_is_callable(list) ? bc_get(list, "listener") : NULL; + if (scr_exc_pending()) goto fail_list; + bool match = scr_dyn_is_callable(list) && + (scr_dyn_strict_eq(list, listener) || (wrapped && scr_dyn_strict_eq(wrapped, listener))); + if (match) { + ScrDyn *count = bc_get(self, "_eventsCount"); + if (!count) goto fail_wrapped; + double left = bc_number(count) - 1; + scr_dyn_release(count); + if (!bc_set(self, "_eventsCount", scr_dyn_new_num(left))) goto fail_wrapped; + ScrDyn *shaped = bc_get_key(self, bc_symbol(BC_SHAPE_MODE)); + if (!shaped) goto fail_wrapped; + bool shape_mode = scr_dyn_truthy(shaped); + scr_dyn_release(shaped); + if (shape_mode) { + if (!bc_set_key(events, type, scr_dyn_retain(scr_dyn_undefined()))) goto fail_wrapped; + } else if (left == 0) { + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto())) goto fail_wrapped; + } else { + scr_dyn_key_delete_computed(events, type, false); + if (scr_exc_pending()) goto fail_wrapped; + ScrDyn *watcher = bc_get(events, "removeListener"); + if (!watcher) goto fail_wrapped; + bool watched = scr_dyn_truthy(watcher); + scr_dyn_release(watcher); + if (watched) { + ScrDyn *name = bc_key("removeListener"); + ScrDyn *args[3] = { name, type, wrapped && scr_dyn_truthy(wrapped) ? wrapped : listener }; + ScrDyn *emitted = bc_invoke(self, "emit", args, 3); + scr_dyn_release(name); + if (!emitted) goto fail_wrapped; + scr_dyn_release(emitted); + } + } + } else if (list->kind == SCR_DYN_ARR) { + double length = scr_dyn_arr_len(list); + double position = -1; + for (double i = length - 1; i >= 0; i--) { + ScrDyn *item = scr_dyn_index_get(list, i); + if (!item) goto fail_wrapped; + bool same = scr_dyn_strict_eq(item, listener); + if (!same && scr_dyn_is_callable(item)) { + ScrDyn *inner = bc_get(item, "listener"); + if (!inner) { + scr_dyn_release(item); + goto fail_wrapped; + } + same = scr_dyn_strict_eq(inner, listener); + scr_dyn_release(inner); + } + scr_dyn_release(item); + if (same) { + position = i; + break; + } + } + if (position >= 0) { + ScrDyn *rest = bc_list_copy(list, NULL, position); + if (!rest) goto fail_wrapped; + ScrDyn *stored = scr_dyn_arr_len(rest) == 1 ? scr_dyn_index_get(rest, 0) : scr_dyn_retain(rest); + scr_dyn_release(rest); + if (!stored || !bc_set_key(events, type, stored)) goto fail_wrapped; + ScrDyn *watcher = bc_get(events, "removeListener"); + if (!watcher) goto fail_wrapped; + bool watched = !bc_undefined(watcher); + scr_dyn_release(watcher); + if (watched) { + ScrDyn *name = bc_key("removeListener"); + ScrDyn *args[3] = { name, type, listener }; + ScrDyn *emitted = bc_invoke(self, "emit", args, 3); + scr_dyn_release(name); + if (!emitted) goto fail_wrapped; + scr_dyn_release(emitted); + } + } + } + if (wrapped) scr_dyn_release(wrapped); + scr_dyn_release(list); + scr_dyn_release(events); + return scr_dyn_retain(self); +fail_wrapped: + if (wrapped) scr_dyn_release(wrapped); +fail_list: + scr_dyn_release(list); +fail_events: + scr_dyn_release(events); + return NULL; +} + +/* ── once wrappers ──────────────────────────────────────────────────── */ + +enum { BC_ONCE_FIRED, BC_ONCE_TARGET, BC_ONCE_TYPE, BC_ONCE_LISTENER, BC_ONCE_SELF, BC_ONCE_CAPS }; + +static ScrBox *bc_box(ScrDyn *value /* moves in */) { + ScrBox *box = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(box, value); + return box; +} + +static ScrDyn *bc_cap(ScrClosure *closure, size_t index) { + return scr_box_get_ref(closure->caps[index]); /* +1 */ +} + +/* onceWrapper: the first call removes the wrapper, then calls the listener + * with the target as its receiver. The wrapper's reference to itself is + * dropped when it fires, so a fired wrapper holds no cycle. */ +static ScrDyn *bc_once_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + if (scr_box_get_f64(closure->caps[BC_ONCE_FIRED]) != 0) return scr_dyn_retain(scr_dyn_undefined()); + ScrDyn *target = bc_cap(closure, BC_ONCE_TARGET); + ScrDyn *type = bc_cap(closure, BC_ONCE_TYPE); + ScrDyn *listener = bc_cap(closure, BC_ONCE_LISTENER); + ScrDyn *self = bc_cap(closure, BC_ONCE_SELF); + ScrDyn *result = NULL; + ScrDyn *removal[2] = { type, self }; + ScrDyn *removed = bc_invoke(target, "removeListener", removal, 2); + if (removed) { + scr_dyn_release(removed); + scr_box_set_f64(closure->caps[BC_ONCE_FIRED], 1); + scr_box_set_ref(closure->caps[BC_ONCE_SELF], scr_dyn_retain(scr_dyn_undefined())); + result = bc_apply(listener, target, args, argc); + } + scr_dyn_release(self); + scr_dyn_release(listener); + scr_dyn_release(type); + scr_dyn_release(target); + return result; +} + +static ScrDyn *bc_once_wrap(ScrDyn *target, ScrDyn *type, ScrDyn *listener) { + ScrClosure *closure = scr_closure_new(NULL, BC_ONCE_CAPS); + closure->caps[BC_ONCE_FIRED] = scr_box_new(SCR_BOX_F64); + closure->caps[BC_ONCE_TARGET] = bc_box(scr_dyn_retain(target)); + closure->caps[BC_ONCE_TYPE] = bc_box(scr_dyn_retain(type)); + closure->caps[BC_ONCE_LISTENER] = bc_box(scr_dyn_retain(listener)); + closure->caps[BC_ONCE_SELF] = bc_box(scr_dyn_retain(scr_dyn_undefined())); + scr_closure_retain(closure); + ScrDyn *wrapper = scr_dyn_new_func(closure, bc_once_call, 0, "native:events", "bound onceWrapper"); + scr_box_set_ref(closure->caps[BC_ONCE_SELF], scr_dyn_retain(wrapper)); + scr_closure_release(closure); + if (!bc_set(wrapper, "listener", scr_dyn_retain(listener))) { + scr_dyn_release(wrapper); + return NULL; + } + return wrapper; +} + +/* ── EventEmitter.prototype ─────────────────────────────────────────── */ + +static ScrDyn *bc_on(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_ON, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") + ? bc_add_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1), false) : NULL; + scr_dyn_release(self); + return result; +} + +static ScrDyn *bc_prepend(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_PREPEND, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") + ? bc_add_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1), true) : NULL; + scr_dyn_release(self); + return result; +} + +static ScrDyn *bc_once_common(ScrDyn *const *args, size_t argc, bool prepend) { + ScrDyn *self = bc_this(); + ScrDyn *type = bc_arg(args, argc, 0); + ScrDyn *listener = bc_arg(args, argc, 1); + ScrDyn *result = NULL; + if (bc_stream_event(prepend ? SCR_EE_M_PREPEND_ONCE : SCR_EE_M_ONCE, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + if (bc_check_listener(listener) && bc_require_object(self, "_events")) { + ScrDyn *wrapper = bc_once_wrap(self, type, listener); + if (wrapper) { + result = bc_add_listener(self, type, wrapper, prepend); + scr_dyn_release(wrapper); + } + } + scr_dyn_release(self); + return result; +} + +static ScrDyn *bc_once(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + return bc_once_common(args, argc, false); +} + +static ScrDyn *bc_prepend_once(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + return bc_once_common(args, argc, true); +} + +static ScrDyn *bc_off(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_OFF, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") + ? bc_remove_listener(self, bc_arg(args, argc, 0), bc_arg(args, argc, 1)) : NULL; + scr_dyn_release(self); + return result; +} + +/* removeAllListeners(type?): with no 'removeListener' watcher the table is + * reset; otherwise every listener is removed through removeListener, last + * first, so the watcher sees each one (and itself last). */ +static ScrDyn *bc_remove_all(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_REMOVE_ALL, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } + if (!bc_require_object(self, "_events")) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *type = bc_arg(args, argc, 0); + bool all = argc == 0; + ScrDyn *events = bc_get(self, "_events"); + ScrDyn *watcher = NULL; + if (!events) goto fail; + if (bc_undefined(events)) goto done; + watcher = bc_get(events, "removeListener"); + if (!watcher) goto fail; + if (bc_undefined(watcher)) { + if (all) { + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto()) || !bc_set(self, "_eventsCount", scr_dyn_new_num(0))) + goto fail; + } else { + ScrDyn *present = bc_get_key(events, type); + if (!present) goto fail; + bool has = !bc_undefined(present); + scr_dyn_release(present); + if (has) { + ScrDyn *count = bc_get(self, "_eventsCount"); + if (!count) goto fail; + double left = bc_number(count) - 1; + scr_dyn_release(count); + if (left == 0) { + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto()) || !bc_set(self, "_eventsCount", scr_dyn_new_num(0))) + goto fail; + } else { + if (!bc_set(self, "_eventsCount", scr_dyn_new_num(left))) goto fail; + scr_dyn_key_delete_computed(events, type, false); + if (scr_exc_pending()) goto fail; + } + } + } + goto done; + } + if (all) { + ScrDyn *keys = scr_dyn_own_keys(events); + if (!keys) goto fail; + double length = scr_dyn_arr_len(keys); + for (double i = 0; i < length; i++) { + ScrDyn *key = scr_dyn_index_get(keys, i); + if (!key) { + scr_dyn_release(keys); + goto fail; + } + bool skip = key->kind == SCR_DYN_STR && key->v.str->len == 14 && memcmp(key->v.str->data, "removeListener", 14) == 0; + ScrDyn *done_key = NULL; + if (!skip) { + ScrDyn *one[1] = { key }; + done_key = bc_invoke(self, "removeAllListeners", one, 1); + } + scr_dyn_release(key); + if (!skip && !done_key) { + scr_dyn_release(keys); + goto fail; + } + if (done_key) scr_dyn_release(done_key); + } + scr_dyn_release(keys); + ScrDyn *name = bc_key("removeListener"); + ScrDyn *one[1] = { name }; + ScrDyn *cleared = bc_invoke(self, "removeAllListeners", one, 1); + scr_dyn_release(name); + if (!cleared) goto fail; + scr_dyn_release(cleared); + if (!bc_set(self, "_events", scr_dyn_new_obj_null_proto()) || !bc_set(self, "_eventsCount", scr_dyn_new_num(0))) + goto fail; + goto done; + } + { + ScrDyn *listeners = bc_get_key(events, type); + if (!listeners) goto fail; + if (scr_dyn_is_callable(listeners)) { + ScrDyn *pair[2] = { type, listeners }; + ScrDyn *removed = bc_invoke(self, "removeListener", pair, 2); + scr_dyn_release(listeners); + if (!removed) goto fail; + scr_dyn_release(removed); + } else if (listeners->kind == SCR_DYN_ARR) { + for (double i = scr_dyn_arr_len(listeners) - 1; i >= 0; i--) { + ScrDyn *item = scr_dyn_index_get(listeners, i); + if (!item) { + scr_dyn_release(listeners); + goto fail; + } + ScrDyn *pair[2] = { type, item }; + ScrDyn *removed = bc_invoke(self, "removeListener", pair, 2); + scr_dyn_release(item); + if (!removed) { + scr_dyn_release(listeners); + goto fail; + } + scr_dyn_release(removed); + } + scr_dyn_release(listeners); + } else { + scr_dyn_release(listeners); + } + } +done: + if (watcher) scr_dyn_release(watcher); + if (events) scr_dyn_release(events); + return self; +fail: + if (watcher) scr_dyn_release(watcher); + if (events) scr_dyn_release(events); + scr_dyn_release(self); + return NULL; +} + +/* emit(type, ...args): Node's unhandled 'error' rule, then each listener + * with the emitter as its receiver over a copy of the list. */ +static ScrDyn *bc_emit(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_EMIT, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } + if (!bc_require_object(self, "_events")) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *type = bc_arg(args, argc, 0); + ScrDyn *const *rest = argc > 1 ? args + 1 : bc_unused_args; + size_t restc = argc > 1 ? argc - 1 : 0; + bool error = type->kind == SCR_DYN_STR && type->v.str->len == 5 && memcmp(type->v.str->data, "error", 5) == 0; + ScrDyn *events = bc_get(self, "_events"); + if (!events) goto fail_self; + if (!bc_undefined(events)) { + if (error) { + ScrDyn *handler = bc_get(events, "error"); + if (!handler) goto fail_events; + error = bc_undefined(handler); + scr_dyn_release(handler); + } + } else if (!error) { + scr_dyn_release(events); + scr_dyn_release(self); + return scr_dyn_new_bool(false); + } + if (error) { + ScrDyn *payload = restc ? rest[0] : scr_dyn_undefined(); + if (scr_dyn_err_instanceof(payload, SCR_ERR_ERROR)) { + scr_dyn_throw(scr_dyn_retain(payload)); + } else { + ScrStr *shown = scr_insp_dyn(payload, 0, 2); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Unhandled error. ("); + scr_jb_put_str(&text, shown); + scr_jb_putc(&text, ')'); + scr_str_release(shown); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "ERR_UNHANDLED_ERROR"); + scr_str_release(message); + } + goto fail_events; + } + ScrDyn *handler = bc_get_key(events, type); + if (!handler) goto fail_events; + bool fired = !bc_undefined(handler); + if (scr_dyn_is_callable(handler)) { + ScrDyn *result = bc_apply(handler, self, rest, restc); + scr_dyn_release(handler); + if (!result) goto fail_events; + scr_dyn_release(result); + } else if (handler->kind == SCR_DYN_ARR) { + /* arrayClone: listeners added or removed during the emit run next time */ + size_t length = (size_t)scr_dyn_arr_len(handler); + ScrDyn **listeners = malloc((length ? length : 1) * sizeof *listeners); + if (!listeners) scr_trap("out of memory"); + for (size_t i = 0; i < length; i++) listeners[i] = scr_dyn_index_get(handler, (double)i); + scr_dyn_release(handler); + bool ok = true; + for (size_t i = 0; i < length && ok; i++) { + if (!listeners[i]) { + ok = false; + break; + } + ScrDyn *result = bc_apply(listeners[i], self, rest, restc); + if (!result) ok = false; + else scr_dyn_release(result); + } + for (size_t i = 0; i < length; i++) + if (listeners[i]) scr_dyn_release(listeners[i]); + free(listeners); + if (!ok) goto fail_events; + } else { + scr_dyn_release(handler); + } + scr_dyn_release(events); + scr_dyn_release(self); + return scr_dyn_new_bool(fired); +fail_events: + scr_dyn_release(events); +fail_self: + scr_dyn_release(self); + return NULL; +} + +/* _listeners(target, type, unwrap): a fresh array. */ +static ScrDyn *bc_listeners_of(bool unwrap, ScrDyn *const *args, size_t argc) { + ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(unwrap ? SCR_EE_M_LISTENERS : SCR_EE_M_RAW_LISTENERS, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } + if (!bc_require_object(self, "_events")) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *result = scr_dyn_new_arr(); + ScrDyn *events = bc_get(self, "_events"); + ScrDyn *list = NULL; + if (!events) goto fail; + if (bc_undefined(events)) goto done; + list = bc_get_key(events, bc_arg(args, argc, 0)); + if (!list) goto fail; + size_t length = scr_dyn_is_callable(list) ? 1 : list->kind == SCR_DYN_ARR ? (size_t)scr_dyn_arr_len(list) : 0; + for (size_t i = 0; i < length; i++) { + ScrDyn *item = scr_dyn_is_callable(list) ? scr_dyn_retain(list) : scr_dyn_index_get(list, (double)i); + if (!item) goto fail; + if (unwrap && scr_dyn_is_callable(item)) { + ScrDyn *inner = bc_get(item, "listener"); + if (!inner) { + scr_dyn_release(item); + goto fail; + } + if (!bc_undefined(inner)) { + scr_dyn_release(item); + item = inner; + } else { + scr_dyn_release(inner); + } + } + scr_dyn_arr_push(result, item); + } +done: + if (list) scr_dyn_release(list); + if (events) scr_dyn_release(events); + scr_dyn_release(self); + return result; +fail: + if (list) scr_dyn_release(list); + if (events) scr_dyn_release(events); + scr_dyn_release(result); + scr_dyn_release(self); + return NULL; +} + +static ScrDyn *bc_listeners(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + return bc_listeners_of(true, args, argc); +} + +static ScrDyn *bc_raw_listeners(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + return bc_listeners_of(false, args, argc); +} + +/* listenerCount(type, listener?) over an emitter's _events. */ +static ScrDyn *bc_count_listeners(ScrDyn *self, ScrDyn *type, ScrDyn *only) { + ScrDyn *events = bc_get(self, "_events"); + if (!events) return NULL; + double count = 0; + if (!bc_undefined(events)) { + ScrDyn *list = bc_get_key(events, type); + if (!list) { + scr_dyn_release(events); + return NULL; + } + size_t length = scr_dyn_is_callable(list) ? 1 : list->kind == SCR_DYN_ARR ? (size_t)scr_dyn_arr_len(list) : 0; + for (size_t i = 0; i < length; i++) { + if (!only) { + count++; + continue; + } + ScrDyn *item = scr_dyn_is_callable(list) ? scr_dyn_retain(list) : scr_dyn_index_get(list, (double)i); + if (!item) break; + bool same = scr_dyn_strict_eq(item, only); + if (!same && scr_dyn_is_callable(item)) { + ScrDyn *inner = bc_get(item, "listener"); + if (inner) { + same = scr_dyn_strict_eq(inner, only); + scr_dyn_release(inner); + } + } + scr_dyn_release(item); + if (same) count++; + } + scr_dyn_release(list); + } + scr_dyn_release(events); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_num(count); +} + +static ScrDyn *bc_listener_count(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *result; + if (bc_stream_event(SCR_EE_M_LISTENER_COUNT, self, args, argc, &result)) { + scr_dyn_release(self); + return result; + } + result = bc_require_object(self, "_events") + ? bc_count_listeners(self, bc_arg(args, argc, 0), + argc > 1 && !bc_undefined(args[1]) && args[1]->kind != SCR_DYN_NULL ? args[1] : NULL) + : NULL; + scr_dyn_release(self); + return result; +} + +/* EventEmitter.listenerCount(emitter, type): the deprecated static. */ +static ScrDyn *bc_static_listener_count(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *emitter = bc_arg(args, argc, 0); + ScrDyn *type = bc_arg(args, argc, 1); + ScrDyn *method = bc_require_object(emitter, "listenerCount") ? bc_get(emitter, "listenerCount") : NULL; + if (!method) return NULL; + ScrDyn *result; + if (scr_dyn_is_callable(method)) result = bc_apply(method, emitter, &type, 1); + else result = bc_count_listeners(emitter, type, NULL); + scr_dyn_release(method); + return result; +} + +static ScrDyn *bc_event_names(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + (void)args; + (void)argc; + ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_EVENT_NAMES, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } + if (!bc_require_object(self, "_eventsCount")) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *count = bc_get(self, "_eventsCount"); + ScrDyn *result = NULL; + if (count) { + if (bc_number(count) > 0) { + ScrDyn *events = bc_get(self, "_events"); + if (events) { + result = scr_dyn_own_keys(events); + scr_dyn_release(events); + } + } else { + result = scr_dyn_new_arr(); + } + scr_dyn_release(count); + } + scr_dyn_release(self); + return result; +} + +static ScrDyn *bc_set_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = bc_this(); + ScrDyn *n = bc_arg(args, argc, 0); + if (n->kind != SCR_DYN_NUM) { + scr_dyn_arg_type_fail("n", "of type number", n); + scr_dyn_release(self); + return NULL; + } + if (n->v.num < 0 || n->v.num != n->v.num) { + ScrStr *shown = scr_dyn_string_coerce_js(n); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "The value of \"n\" is out of range. It must be a non-negative number. Received "); + if (shown) scr_jb_put_str(&text, shown); + if (shown) scr_str_release(shown); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_RANGE, message->data, message->len, "ERR_OUT_OF_RANGE"); + scr_str_release(message); + scr_dyn_release(self); + return NULL; + } + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_SET_MAX, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } + if (!bc_require_object(self, "_maxListeners") || !bc_set(self, "_maxListeners", scr_dyn_retain(n))) { + scr_dyn_release(self); + return NULL; + } + return self; +} + +/* EventEmitter.defaultMaxListeners, Node's accessor over the one default + * the typed EventEmitter reads too (scr_events_emitter.c). The setter + * validates as Node's validateNumber(arg, 'defaultMaxListeners', 0). */ +static ScrDyn *bc_default_max_get(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + (void)args; + (void)argc; + return scr_dyn_new_num(scr_emitter_get_default_max()); +} + +static ScrDyn *bc_default_max_set(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + static const char name[] = "defaultMaxListeners"; + ScrStr *key = scr_str_new(name, sizeof name - 1); + scr_emitter_set_default_max_chk(argc ? args[0] : scr_dyn_undefined(), key); + scr_str_release(key); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(scr_dyn_undefined()); +} + +static void bc_define_default_max(ScrDyn *ctor) { + ScrDyn *descriptor = scr_dyn_new_obj(); + scr_dyn_obj_set(descriptor, "get", 3, + scr_dyn_new_func(scr_closure_new(NULL, 0), bc_default_max_get, 0, "native:events", "get")); + scr_dyn_obj_set(descriptor, "set", 3, + scr_dyn_new_func(scr_closure_new(NULL, 0), bc_default_max_set, 1, "native:events", "set")); + scr_dyn_obj_set(descriptor, "enumerable", 10, scr_dyn_new_bool(true)); + ScrStr *name = scr_str_new("defaultMaxListeners", 19); + ScrDyn *key = scr_dyn_new_str(name); + scr_str_release(name); + ScrDyn *defined = scr_dyn_define_property(ctor, key, descriptor); + if (defined) scr_dyn_release(defined); + scr_dyn_release(key); + scr_dyn_release(descriptor); +} + +static ScrDyn *bc_get_max(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + (void)args; + (void)argc; + ScrDyn *self = bc_this(); + ScrDyn *handled; + if (bc_stream_event(SCR_EE_M_GET_MAX, self, args, argc, &handled)) { + scr_dyn_release(self); + return handled; + } + double max = bc_require_object(self, "_maxListeners") ? bc_max_listeners(self) : -1; + scr_dyn_release(self); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_num(max); +} + +/* ── Stream.prototype.pipe (lib/internal/streams/legacy.js) ─────────── */ + +enum { BC_PIPE_SOURCE, BC_PIPE_DEST, BC_PIPE_ENDED, BC_PIPE_HANDLERS, BC_PIPE_CAPS }; +enum { BC_ONDATA, BC_ONDRAIN, BC_ONEND, BC_ONCLOSE, BC_ONERROR, BC_CLEANUP, BC_HANDLER_COUNT }; + +static ScrDyn *bc_pipe_handler(ScrClosure *state, int which); + +static ScrDyn *bc_pipe_get_handler(ScrClosure *state, int which) { + ScrDyn *handlers = bc_cap(state, BC_PIPE_HANDLERS); + ScrDyn *handler = scr_dyn_index_get(handlers, which); + scr_dyn_release(handlers); + return handler; +} + +/* Calls source/dest.removeListener for one pipe handler. */ +static bool bc_pipe_remove(ScrDyn *emitter, const char *event, ScrClosure *state, int which) { + ScrDyn *handler = bc_pipe_get_handler(state, which); + if (!handler) return false; + ScrDyn *name = bc_key(event); + ScrDyn *pair[2] = { name, handler }; + ScrDyn *removed = bc_invoke(emitter, "removeListener", pair, 2); + scr_dyn_release(name); + scr_dyn_release(handler); + if (!removed) return false; + scr_dyn_release(removed); + return true; +} + +static bool bc_pipe_on(ScrDyn *emitter, const char *event, ScrClosure *state, int which) { + ScrDyn *handler = bc_pipe_get_handler(state, which); + if (!handler) return false; + ScrDyn *name = bc_key(event); + ScrDyn *pair[2] = { name, handler }; + ScrDyn *added = bc_invoke(emitter, "on", pair, 2); + scr_dyn_release(name); + scr_dyn_release(handler); + if (!added) return false; + scr_dyn_release(added); + return true; +} + +static bool bc_pipe_cleanup(ScrClosure *state) { + /* A second run (dest emitting 'close' from end(), with cleanup still + * queued on the source's 'end') finds the listeners removed, as Node's + * repeated removeListener calls do. */ + ScrDyn *handlers = bc_cap(state, BC_PIPE_HANDLERS); + bool cleaned = handlers->kind == SCR_DYN_ARR && scr_dyn_arr_len(handlers) == 0; + scr_dyn_release(handlers); + if (cleaned) return true; + ScrDyn *source = bc_cap(state, BC_PIPE_SOURCE); + ScrDyn *dest = bc_cap(state, BC_PIPE_DEST); + bool ok = bc_pipe_remove(source, "data", state, BC_ONDATA) && + bc_pipe_remove(dest, "drain", state, BC_ONDRAIN) && + bc_pipe_remove(source, "end", state, BC_ONEND) && + bc_pipe_remove(source, "close", state, BC_ONCLOSE) && + bc_pipe_remove(source, "error", state, BC_ONERROR) && + bc_pipe_remove(dest, "error", state, BC_ONERROR) && + bc_pipe_remove(source, "end", state, BC_CLEANUP) && + bc_pipe_remove(source, "close", state, BC_CLEANUP) && + bc_pipe_remove(dest, "close", state, BC_CLEANUP); + scr_dyn_release(dest); + scr_dyn_release(source); + /* the handlers array held the closures that hold this state */ + if (ok) scr_box_set_ref(state->caps[BC_PIPE_HANDLERS], scr_dyn_new_arr()); + return ok; +} + +static ScrDyn *bc_pipe_event(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrClosure *state = (ScrClosure *)scr_box_get_ref(closure->caps[0]); + int which = (int)scr_box_get_f64(closure->caps[1]); + ScrDyn *source = bc_cap(state, BC_PIPE_SOURCE); + ScrDyn *dest = bc_cap(state, BC_PIPE_DEST); + bool ok = true; + switch (which) { + case BC_ONDATA: { + ScrDyn *writable = bc_get(dest, "writable"); + if (!writable) { + ok = false; + break; + } + bool open = scr_dyn_truthy(writable); + scr_dyn_release(writable); + if (!open) break; + ScrDyn *chunk = bc_arg(args, argc, 0); + ScrDyn *written = bc_invoke(dest, "write", &chunk, 1); + if (!written) { + ok = false; + break; + } + bool full = written->kind == SCR_DYN_BOOL && !written->v.b; + scr_dyn_release(written); + if (full) { + ScrDyn *pause = bc_get(source, "pause"); + if (!pause) { + ok = false; + break; + } + if (scr_dyn_is_callable(pause)) { + ScrDyn *paused = bc_apply(pause, source, bc_unused_args, 0); + if (paused) scr_dyn_release(paused); + else ok = false; + } + scr_dyn_release(pause); + } + break; + } + case BC_ONDRAIN: { + ScrDyn *readable = bc_get(source, "readable"); + ScrDyn *resume = readable ? bc_get(source, "resume") : NULL; + if (!readable || !resume) { + ok = false; + } else if (scr_dyn_truthy(readable) && scr_dyn_is_callable(resume)) { + ScrDyn *resumed = bc_apply(resume, source, bc_unused_args, 0); + if (resumed) scr_dyn_release(resumed); + else ok = false; + } + if (readable) scr_dyn_release(readable); + if (resume) scr_dyn_release(resume); + break; + } + case BC_ONEND: + case BC_ONCLOSE: { + if (scr_box_get_f64(state->caps[BC_PIPE_ENDED]) != 0) break; + scr_box_set_f64(state->caps[BC_PIPE_ENDED], 1); + ScrDyn *done = which == BC_ONEND ? bc_invoke(dest, "end", bc_unused_args, 0) : NULL; + if (which == BC_ONCLOSE) { + ScrDyn *destroy = bc_get(dest, "destroy"); + if (!destroy) { + ok = false; + break; + } + if (scr_dyn_is_callable(destroy)) done = bc_apply(destroy, dest, bc_unused_args, 0); + else done = scr_dyn_retain(scr_dyn_undefined()); + scr_dyn_release(destroy); + } + if (done) scr_dyn_release(done); + else ok = false; + break; + } + case BC_ONERROR: { + ok = bc_pipe_cleanup(state); + if (!ok) break; + ScrDyn *rethrow = scr_dyn_this_get(); + ScrDyn *name = bc_key("error"); + ScrDyn *count = rethrow->kind == SCR_DYN_UNDEF ? NULL : bc_invoke(rethrow, "listenerCount", &name, 1); + scr_dyn_release(name); + if (count && bc_number(count) == 0) { + scr_dyn_throw(scr_dyn_retain(bc_arg(args, argc, 0))); + ok = false; + } else if (!count && scr_exc_pending()) { + ok = false; + } + if (count) scr_dyn_release(count); + scr_dyn_release(rethrow); + break; + } + case BC_CLEANUP: + ok = bc_pipe_cleanup(state); + break; + default: + break; + } + scr_dyn_release(dest); + scr_dyn_release(source); + scr_closure_release(state); + return ok ? scr_dyn_retain(scr_dyn_undefined()) : NULL; +} + +static ScrDyn *bc_pipe_handler(ScrClosure *state, int which) { + ScrClosure *closure = scr_closure_new(NULL, 2); + ScrBox *owner = scr_box_new_obj(scr_closure_retain_v, scr_closure_release_v, scr_closure_trace_v); + scr_box_set_ref(owner, scr_closure_retain(state)); + closure->caps[0] = owner; + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], which); + return scr_dyn_new_func(closure, bc_pipe_event, 0, "native:stream", which == BC_ONDATA ? "ondata" + : which == BC_ONDRAIN ? "ondrain" : which == BC_ONEND ? "onend" : which == BC_ONCLOSE ? "onclose" + : which == BC_ONERROR ? "onerror" : "cleanup"); +} + +/* Stream.prototype.pipe(dest, options): Node's legacy pipe for streams + * that are only EventEmitters. 'end' and 'close' end or destroy the + * destination unless options.end is false or the destination is stdio. */ +static ScrDyn *bc_pipe(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *source = bc_this(); + ScrDyn *dest = bc_arg(args, argc, 0); + ScrDyn *options = bc_arg(args, argc, 1); + if (!bc_require_object(source, "on") || !bc_require_object(dest, "on")) { + scr_dyn_release(source); + return NULL; + } + ScrClosure *state = scr_closure_new(NULL, BC_PIPE_CAPS); + state->caps[BC_PIPE_SOURCE] = bc_box(scr_dyn_retain(source)); + state->caps[BC_PIPE_DEST] = bc_box(scr_dyn_retain(dest)); + state->caps[BC_PIPE_ENDED] = scr_box_new(SCR_BOX_F64); + ScrDyn *handlers = scr_dyn_new_arr(); + for (int i = 0; i < BC_HANDLER_COUNT; i++) scr_dyn_arr_push(handlers, bc_pipe_handler(state, i)); + state->caps[BC_PIPE_HANDLERS] = bc_box(handlers); + bool ok = bc_pipe_on(source, "data", state, BC_ONDATA) && bc_pipe_on(dest, "drain", state, BC_ONDRAIN); + bool end_dest = true; + if (ok && options->kind != SCR_DYN_UNDEF && options->kind != SCR_DYN_NULL) { + ScrDyn *end = bc_get(options, "end"); + if (!end) ok = false; + else { + end_dest = !(end->kind == SCR_DYN_BOOL && !end->v.b); + scr_dyn_release(end); + } + } + if (ok && end_dest) { + ScrDyn *stdio = bc_get(dest, "_isStdio"); + if (!stdio) ok = false; + else { + end_dest = !scr_dyn_truthy(stdio); + scr_dyn_release(stdio); + } + } + if (ok && end_dest) + ok = bc_pipe_on(source, "end", state, BC_ONEND) && bc_pipe_on(source, "close", state, BC_ONCLOSE); + if (ok) { + /* prependListener(source, 'error', onerror) and the same on dest */ + ScrDyn *onerror = bc_pipe_get_handler(state, BC_ONERROR); + ScrDyn *name = bc_key("error"); + ScrDyn *pair[2] = { name, onerror }; + ScrDyn *a = onerror ? bc_invoke(source, "prependListener", pair, 2) : NULL; + ScrDyn *b = a ? bc_invoke(dest, "prependListener", pair, 2) : NULL; + ok = a && b; + if (a) scr_dyn_release(a); + if (b) scr_dyn_release(b); + scr_dyn_release(name); + if (onerror) scr_dyn_release(onerror); + } + if (ok) + ok = bc_pipe_on(source, "end", state, BC_CLEANUP) && bc_pipe_on(source, "close", state, BC_CLEANUP) && + bc_pipe_on(dest, "close", state, BC_CLEANUP); + ScrDyn *result = NULL; + if (ok) { + ScrDyn *name = bc_key("pipe"); + ScrDyn *pair[2] = { name, source }; + ScrDyn *emitted = bc_invoke(dest, "emit", pair, 2); + scr_dyn_release(name); + if (emitted) { + scr_dyn_release(emitted); + result = scr_dyn_retain(dest); + } + } + scr_closure_release(state); + scr_dyn_release(source); + return result; +} + +/* ── the constructors ───────────────────────────────────────────────── */ + +static ScrDyn *bc_construct(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + int kind = (int)scr_box_get_f64(closure->caps[0]); + ScrDyn *self = bc_this(); + ScrDyn *result = NULL; + if (kind == BC_EMITTER || kind == BC_STREAM) { + if (bc_emitter_init(self, bc_arg(args, argc, 0))) result = scr_dyn_retain(scr_dyn_undefined()); + } else if (bc_stream_ops) { + result = bc_stream_ops->construct(kind, self, args, argc); + } else { + char message[160]; + snprintf(message, sizeof message, + "'%s' as the base of a constructor function (util.inherits or %s.call(this)) is not supported yet", + bc_names[kind], bc_names[kind]); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020"); + } + scr_dyn_release(self); + return result; +} + +static void bc_method(ScrDyn *prototype, const char *name, ScrDynThunk thunk, uint32_t arity) { + ScrDyn *fn = scr_dyn_new_func(scr_closure_new(NULL, 0), thunk, arity, "native:events", name); + bc_set(prototype, name, fn); +} + +static void bc_alias(ScrDyn *prototype, const char *alias, const char *name) { + ScrDyn *fn = bc_get(prototype, name); + if (fn) bc_set(prototype, alias, fn); +} + +static void bc_teardown(void) { + for (int i = 0; i < BC_COUNT; i++) { + if (!bc_ctors[i]) continue; + ScrDyn *ctor = bc_ctors[i]; + /* prototype.constructor, EventEmitter.EventEmitter and Stream.Stream + * point back at the constructor, and Stream.Readable … at constructors + * whose own prototype is Stream: break those cycles first. */ + ScrDyn *prototype = bc_get(ctor, "prototype"); + if (prototype && prototype->kind == SCR_DYN_OBJ) bc_set(prototype, "constructor", scr_dyn_retain(scr_dyn_undefined())); + if (prototype) scr_dyn_release(prototype); + bc_set(ctor, bc_names[i], scr_dyn_retain(scr_dyn_undefined())); + if (i == BC_STREAM) + for (int other = BC_READABLE; other < BC_COUNT; other++) + bc_set(ctor, bc_names[other], scr_dyn_retain(scr_dyn_undefined())); + if (scr_exc_pending()) scr_exc_clear(); + } + for (int i = 0; i < BC_COUNT; i++) { + if (!bc_ctors[i]) continue; + ScrDyn *ctor = bc_ctors[i]; + bc_ctors[i] = NULL; + scr_dyn_release(ctor); + } + for (int i = 0; i < BC_SYMBOLS; i++) { + if (bc_symbols[i]) scr_dyn_release(bc_symbols[i]); + bc_symbols[i] = NULL; + } + if (scr_exc_pending()) scr_exc_clear(); +} + +static ScrDyn *bc_new_class(int kind, ScrDyn *parent) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->function_kind = 4; /* an ordinary function with its own prototype */ + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], kind); + ScrDyn *ctor = scr_dyn_new_func(closure, bc_construct, 1, "native:events", bc_names[kind]); + ScrDyn *prototype = parent ? NULL : scr_dyn_new_obj(); + if (parent) { + ScrDyn *base = bc_get(parent, "prototype"); + prototype = scr_dyn_obj_create(base); + scr_dyn_release(base); + ScrDyn *linked = scr_dyn_set_prototype(ctor, parent); + if (linked) scr_dyn_release(linked); + } + bc_set(prototype, "constructor", scr_dyn_retain(ctor)); + bc_set(ctor, "prototype", scr_dyn_retain(prototype)); + if (kind == BC_EMITTER) { + bc_set(prototype, "_events", scr_dyn_retain(scr_dyn_undefined())); + bc_set(prototype, "_eventsCount", scr_dyn_new_num(0)); + bc_set(prototype, "_maxListeners", scr_dyn_retain(scr_dyn_undefined())); + bc_set_key(prototype, bc_symbol(BC_CAPTURE), scr_dyn_new_bool(false)); + bc_method(prototype, "setMaxListeners", bc_set_max, 1); + bc_method(prototype, "getMaxListeners", bc_get_max, 0); + bc_method(prototype, "emit", bc_emit, 1); + bc_method(prototype, "addListener", bc_on, 2); + bc_alias(prototype, "on", "addListener"); + bc_method(prototype, "prependListener", bc_prepend, 2); + bc_method(prototype, "once", bc_once, 2); + bc_method(prototype, "prependOnceListener", bc_prepend_once, 2); + bc_method(prototype, "removeListener", bc_off, 2); + bc_alias(prototype, "off", "removeListener"); + bc_method(prototype, "removeAllListeners", bc_remove_all, 1); + bc_method(prototype, "listeners", bc_listeners, 1); + bc_method(prototype, "rawListeners", bc_raw_listeners, 1); + bc_method(prototype, "listenerCount", bc_listener_count, 2); + bc_method(prototype, "eventNames", bc_event_names, 0); + bc_set(ctor, "EventEmitter", scr_dyn_retain(ctor)); + bc_define_default_max(ctor); + ScrDyn *count = scr_dyn_new_func(scr_closure_new(NULL, 0), bc_static_listener_count, 2, "native:events", "listenerCount"); + bc_set(ctor, "listenerCount", count); + } else if (kind == BC_STREAM) { + ScrDyn *pipe = scr_dyn_new_func(scr_closure_new(NULL, 0), bc_pipe, 2, "native:stream", "pipe"); + bc_set(prototype, "pipe", pipe); + } else if (bc_stream_ops) { + bc_stream_ops->prototype(kind, prototype); + } + scr_dyn_release(prototype); + if (scr_exc_pending()) scr_exc_clear(); + return ctor; +} + +/* All seven constructors are created together, each after its parent, so + * Stream's static members name the same constructors every other read + * answers. */ +static ScrDyn *bc_class(int kind) { + if (!bc_ctors[kind]) { + static const int parents[BC_COUNT] = { + -1, BC_EMITTER, BC_STREAM, BC_STREAM, BC_READABLE, BC_DUPLEX, BC_TRANSFORM, + }; + for (int each = 0; each < BC_COUNT; each++) { + if (bc_ctors[each]) continue; + bc_ctors[each] = bc_new_class(each, parents[each] >= 0 ? bc_ctors[parents[each]] : NULL); + } + /* Registered after the constructors exist, so the teardown runs before + * the runtime state their properties were stored in (scr_atexit is + * last in, first out). */ + if (!bc_teardown_registered) { + bc_teardown_registered = true; + scr_atexit(bc_teardown); + } + /* require("stream") is Stream itself, carrying its classes */ + ScrDyn *stream = bc_ctors[BC_STREAM]; + bc_set(stream, "Stream", scr_dyn_retain(stream)); + for (int other = BC_READABLE; other < BC_COUNT; other++) + bc_set(stream, bc_names[other], scr_dyn_retain(bc_ctors[other])); + if (scr_exc_pending()) scr_exc_clear(); + } + return bc_ctors[kind]; +} + +/* Registers the stream half; constructors that already exist get their + * stream prototypes' methods now. */ +void scr_dyn_builtin_class_streams(const ScrDynStreamOps *ops) { + if (bc_stream_ops) return; + bc_stream_ops = ops; + for (int kind = BC_READABLE; kind < BC_COUNT; kind++) { + if (!bc_ctors[kind]) continue; + ScrDyn *prototype = bc_get(bc_ctors[kind], "prototype"); + if (prototype) { + ops->prototype(kind, prototype); + scr_dyn_release(prototype); + } + if (scr_exc_pending()) scr_exc_clear(); + } +} + +/* The constructor value for kind (the BC_* order), +1. */ +ScrDyn *scr_dyn_builtin_class(double kind) { + int index = (int)kind; + if (index < 0 || index >= BC_COUNT) scr_trap("scriptc: unknown builtin class value\n"); + return scr_dyn_retain(bc_class(index)); +} + +/* OrdinaryHasInstance over the value's prototype chain. */ +static bool bc_chain_has(ScrDyn *value, ScrDyn *prototype) { + if (value->kind != SCR_DYN_OBJ && value->kind != SCR_DYN_ARR && value->kind != SCR_DYN_FUNC && + value->kind != SCR_DYN_PROXY) + return false; + ScrDyn *current = scr_dyn_get_prototype(value); + while (current && current->kind != SCR_DYN_NULL) { + if (scr_dyn_strict_eq(current, prototype)) { + scr_dyn_release(current); + return true; + } + ScrDyn *next = scr_dyn_get_prototype(current); + scr_dyn_release(current); + current = next; + } + if (current) scr_dyn_release(current); + return false; +} + +/* `value instanceof `. Node's Writable also answers true for an + * object whose _writableState is a WritableState, which no untyped object + * has, so the prototype chain answers for every kind. */ +bool scr_dyn_builtin_instanceof(ScrDyn *value, double kind) { + int index = (int)kind; + if (index < 0 || index >= BC_COUNT) scr_trap("scriptc: unknown builtin class value\n"); + ScrDyn *prototype = bc_get(bc_class(index), "prototype"); + if (!prototype) return false; + bool found = bc_chain_has(value, prototype); + scr_dyn_release(prototype); + return found; +} + +/* `value instanceof callee` for an untyped constructor function. */ +bool scr_dyn_function_instanceof(ScrDyn *value, ScrDyn *callee) { + if (!scr_dyn_is_callable(callee)) { + static const char message[] = "Right-hand side of 'instanceof' is not callable"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return false; + } + ScrDyn *prototype = bc_get(callee, "prototype"); + if (!prototype) return false; + bool found = false; + if (prototype->kind == SCR_DYN_OBJ || prototype->kind == SCR_DYN_ARR || prototype->kind == SCR_DYN_FUNC || + prototype->kind == SCR_DYN_PROXY) { + found = bc_chain_has(value, prototype); + } else if (value->kind == SCR_DYN_OBJ || value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_FUNC) { + static const char message[] = "Function has non-object prototype 'undefined' in instanceof check"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } + scr_dyn_release(prototype); + return found; +} diff --git a/packages/runtime/src/scr_dyn_builtin_stream.c b/packages/runtime/src/scr_dyn_builtin_stream.c new file mode 100644 index 000000000..b7295f59a --- /dev/null +++ b/packages/runtime/src/scr_dyn_builtin_stream.c @@ -0,0 +1,1717 @@ +/* Native stream state for JavaScript constructor functions that inherit + * from the node:stream classes the pre-class way: + * + * function Source(opts) { Readable.call(this, opts); } + * util.inherits(Source, Readable); + * Source.prototype._read = function (n) { this.push(...); }; + * + * scr_dyn_builtin_class.c provides Readable, Writable, Duplex, Transform + * and PassThrough as untyped constructors with Node's prototype chain. + * This unit (the dynBuiltinStream feature) gives them their stream half: + * + * - `Readable.call(this, opts)` (and the other four) creates a runtime + * stream (scr_stream.c) over the receiver. The receiver keeps it in a + * hidden slot (an entry of its symbol table that no symbol can name), + * so JavaScript sees only the properties Node's constructor adds: + * `_readableState` / `_writableState` (live read-only views of the + * runtime state), `allowHalfOpen` on the duplex kinds, and the option + * callbacks copied to `_read`, `_write`, `_final`, `_destroy`, + * `_transform` and `_flush`. + * - The runtime stream calls the receiver's `_read`, `_write`, + * `_transform`, `_flush`, `_final` and `_destroy` through its property + * lookup, with the receiver as `this`, as Node's machinery does. + * - The stream prototypes carry Node's methods and accessors (push, read, + * write, end, pipe, destroy, readable, destroyed, ...) over the hidden + * stream, and EventEmitter's methods register listeners with the runtime + * stream's own emitter (scr_dyn_builtin_class.c forwards to them), so the + * runtime's events and pipes reach JavaScript listeners with the + * receiver as `this`. + * + * Lifetime: the receiver holds its stream through a traced capsule, and + * the stream's hook closures hold the receiver through traced boxes, so + * the pair lives while either is reachable and the cycle collector + * reclaims it afterwards. The stream's native record points back at the + * receiver without a reference; the hook closures outlive the record. + * + * Not supported here (each throws the "not supported yet" Error where it + * runs): objectMode and the other options scr_stream.c's dyn option walk + * refuses, `_writev` and `_construct`, symbol event names, listeners() / + * rawListeners(), `wrap()`, and pipes into values that are neither runtime + * streams nor constructor-function streams. */ +#include "scr_runtime.h" + +#include +#include +#include + +enum { BCS_READABLE = 2, BCS_WRITABLE, BCS_DUPLEX, BCS_TRANSFORM, BCS_PASSTHROUGH }; + +static const char bcs_hidden_key[] = "scriptc:stream"; +#define BCS_HIDDEN_LEN (sizeof bcs_hidden_key - 1) + +/* The runtime stream's native record. */ +struct BcsRec; +typedef struct BcsRec { + ScrDyn *object; /* borrowed: the hook closures keep it alive */ + ScrDyn **destroy_cbs; /* destroy(err, cb) callbacks waiting for _destroy */ + size_t destroy_n, destroy_cap; + char cls[64]; /* the constructor's name, for the leak warning */ +} BcsRec; + +static void bcs_rec_release(void *p) { + BcsRec *rec = p; + for (size_t i = 0; i < rec->destroy_n; i++) scr_dyn_release(rec->destroy_cbs[i]); + free(rec->destroy_cbs); + free(rec); +} + +static BcsRec *bcs_rec(ScrStream *s) { + return scr_stream_native(s, bcs_rec_release); +} + +/* ── small helpers ──────────────────────────────────────────────────── */ + +static ScrDyn *bcs_key(const char *name) { + ScrStr *text = scr_str_new(name, strlen(name)); + ScrDyn *key = scr_dyn_new_str(text); + scr_str_release(text); + return key; +} + +/* object[name] with object as the receiver: +1, or NULL with an exception + * pending. */ +static ScrDyn *bcs_get(ScrDyn *object, const char *name) { + ScrDyn *key = bcs_key(name); + ScrDyn *value = scr_dyn_reflect_get(object, key, object); + scr_dyn_release(key); + if (!value && !scr_exc_pending()) value = scr_dyn_retain(scr_dyn_undefined()); + return value; +} + +/* object[name] = value; the value moves in. */ +static bool bcs_set(ScrDyn *object, const char *name, ScrDyn *value) { + ScrDyn *key = bcs_key(name); + scr_dyn_key_set_computed(object, key, value); + scr_dyn_release(key); + scr_dyn_release(value); + return !scr_exc_pending(); +} + +static ScrDyn *bcs_arg(ScrDyn *const *args, size_t argc, size_t index) { + return index < argc ? args[index] : scr_dyn_undefined(); +} + +static bool bcs_nullish(const ScrDyn *value) { + return value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL; +} + +static ScrDyn *bcs_undefined(void) { + return scr_dyn_retain(scr_dyn_undefined()); +} + +static ScrDyn *bcs_str(const char *text) { + return bcs_key(text); +} + +static void bcs_unsupported(const char *message) { + scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "SC2020"); +} + +/* fn.call(self, ...args): +1 or NULL. */ +static ScrDyn *bcs_apply(ScrDyn *fn, ScrDyn *self, ScrDyn *const *args, size_t argc, const char *what) { + scr_dyn_this_push_dyn(self); + ScrDyn *result = scr_dyn_call(fn, args, argc, what); + scr_dyn_this_pop(); + return result; +} + +/* An error argument of a completion callback or destroy(): NULL for a + * falsy value (Node tests `if (err)`), else the runtime error that boxes + * back to the same JavaScript value (+1). */ +static ScrError *bcs_error_of(const ScrDyn *value) { + if (!scr_dyn_truthy(value)) return NULL; + if (value->kind == SCR_DYN_STR) return scr_error_new(SCR_ERR_ERROR, value->v.str); + return scr_error_from_dyn(value); +} + +/* ── the hidden slot ────────────────────────────────────────────────── */ + +static void *bcs_capsule_retain(void *p) { return scr_stream_retain(p); } +/* Also the capsule's tag. */ +static void bcs_capsule_release(void *p) { scr_stream_release(p); } + +static bool bcs_stream_vt(const ScrVt *vt) { + return (vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) || + (vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post); +} + +static void bcs_rec_release(void *p); + +/* The stream of a constructor-function stream or one of its state views, + * or of a typed capsule of such a stream (a typed stream value that + * crossed back into JavaScript), borrowed; NULL for other values. */ +static ScrStream *bcs_of(const ScrDyn *value) { + if (value != NULL && value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 && + memcmp(value->v.typed_ref.type_key, "object:", 7) == 0) { + ScrStream *stream = value->v.typed_ref.ptr; + return bcs_stream_vt(stream->vt) && scr_stream_native(stream, bcs_rec_release) ? stream : NULL; + } + if (value == NULL || value->kind != SCR_DYN_OBJ || value->symbol_properties == NULL) return NULL; + ScrDyn *capsule = scr_dyn_obj_get(value->symbol_properties, bcs_hidden_key, BCS_HIDDEN_LEN); + if (capsule == NULL || capsule->kind != SCR_DYN_TYPED_REF || + capsule->v.typed_ref.release != bcs_capsule_release) + return NULL; + return capsule->v.typed_ref.ptr; +} + +/* Stores the stream in the object's symbol table under a storage name no + * symbol produces (symbols store under their address), outside the key + * list, so no reflection reaches it. */ +static void bcs_hide(ScrDyn *object, ScrStream *s) { + scr_dyn_region_store(object, NULL); + if (!object->symbol_properties) object->symbol_properties = scr_dyn_new_obj_null_proto(); + if (!object->symbol_keys) object->symbol_keys = scr_dyn_new_arr(); + ScrDyn *capsule = scr_dyn_new_typed_ref_traced(s, bcs_capsule_retain, bcs_capsule_release, + bcs_hidden_key, BCS_HIDDEN_LEN, + scr_dyn_class_view_unavailable, NULL); + scr_dyn_obj_set(object->symbol_properties, bcs_hidden_key, BCS_HIDDEN_LEN, capsule); +} + +/* A runtime stream boxed as a typed capsule (a typed stream value that + * crossed into JavaScript): +1, or NULL. */ +static ScrStream *bcs_native_of(const ScrDyn *value) { + if (value->kind != SCR_DYN_TYPED_REF || value->v.typed_ref.type_key_len <= 7 || + memcmp(value->v.typed_ref.type_key, "object:", 7) != 0) + return NULL; + ScrStream *stream = scr_dyn_typed_ref_unbox(value); + if (bcs_stream_vt(stream->vt)) return stream; + value->v.typed_ref.release(stream); + return NULL; +} + +/* Any stream a JavaScript value stands for (+1), or NULL. */ +ScrStream *scr_dyn_builtin_stream_of(const ScrDyn *value) { + ScrStream *own = bcs_of(value); + if (own) return scr_stream_retain(own); + return bcs_native_of(value); +} + +/* The JavaScript value of a runtime stream: the constructor-function + * object it belongs to (+1), or NULL for other streams. */ +ScrDyn *scr_dyn_builtin_stream_object(ScrStream *s) { + BcsRec *rec = bcs_rec(s); + return rec ? scr_dyn_retain(rec->object) : NULL; +} + +/* A stream as a listener argument: its constructor-function object, or a + * live view of another runtime stream whose pipe and unpipe take those + * objects (scr_dyn_builtin_stream_view). +1. */ +static ScrDyn *bcs_stream_value(ScrStream *s) { + return scr_dyn_builtin_stream_view(s); +} + +/* The receiver's stream for a prototype method, borrowed; throws Node's + * TypeError (reading the state of a non-stream) and answers NULL. */ +static ScrStream *bcs_need(ScrDyn *self, const char *reading) { + ScrStream *s = bcs_of(self); + if (s) return s; + char message[128]; + if (bcs_nullish(self)) { + snprintf(message, sizeof message, "Cannot read properties of %s (reading '%s')", + self->kind == SCR_DYN_NULL ? "null" : "undefined", reading); + scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); + } else { + snprintf(message, sizeof message, + "stream method '%s' on an object without stream state is not supported yet", reading); + bcs_unsupported(message); + } + return NULL; +} + +/* ── completion callbacks ───────────────────────────────────────────── */ + +enum { BCS_DONE_WRITE, BCS_DONE_FINAL, BCS_DONE_DESTROY, BCS_DONE_TRANSFORM, BCS_DONE_FLUSH }; + +static void bcs_run_destroy_cbs(ScrStream *s, ScrError *err) { + BcsRec *rec = bcs_rec(s); + if (!rec || rec->destroy_n == 0) return; + ScrDyn **cbs = rec->destroy_cbs; + size_t n = rec->destroy_n; + rec->destroy_cbs = NULL; + rec->destroy_n = rec->destroy_cap = 0; + ScrDyn *arg = err ? scr_dyn_from_error(err) : scr_dyn_new_null(); + ScrDyn *object = rec->object; + scr_dyn_retain(object); + for (size_t i = 0; i < n; i++) { + if (!scr_exc_pending()) { + ScrDyn *result = bcs_apply(cbs[i], object, &arg, 1, "callback"); + if (result) scr_dyn_release(result); + } + scr_dyn_release(cbs[i]); + } + free(cbs); + scr_dyn_release(object); + scr_dyn_release(arg); +} + +static ScrDyn *bcs_done_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrStream *s = scr_box_get_ref(closure->caps[0]); + int which = (int)scr_box_get_f64(closure->caps[1]); + ScrError *err = bcs_error_of(bcs_arg(args, argc, 0)); + switch (which) { + case BCS_DONE_WRITE: + scr_stream_write_done(s, err); + break; + case BCS_DONE_FINAL: + scr_stream_final_done(s, err); + break; + case BCS_DONE_DESTROY: { + ScrError *kept = err ? scr_error_retain(err) : NULL; + scr_stream_destroy_done(s, err); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + if (kept) scr_error_release(kept); + break; + } + default: { + ScrDyn *data = bcs_arg(args, argc, 1); + ScrBytes *bytes = data->kind == SCR_DYN_BYTES ? scr_bytes_raw_view(data->v.bytes) : NULL; + ScrStr *text = data->kind == SCR_DYN_STR ? scr_str_retain(data->v.str) : NULL; + if (which == BCS_DONE_TRANSFORM) scr_stream_transform_done(s, err, bytes, text); + else scr_stream_flush_done(s, err, bytes, text); + break; + } + } + scr_stream_release(s); + if (scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +static ScrDyn *bcs_done(ScrStream *s, int which) { + ScrClosure *closure = scr_closure_new((void *)bcs_done_call, 2); + closure->caps[0] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[0], scr_stream_retain(s)); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], which); + uint32_t arity = which == BCS_DONE_TRANSFORM || which == BCS_DONE_FLUSH ? 2 : 1; + return scr_dyn_new_func(closure, bcs_done_call, arity, "native:stream", ""); +} + +/* ── the hooks: the runtime calls the receiver's underscore methods ─── */ + +static ScrBox *bcs_box(ScrDyn *value /* moves in */) { + ScrBox *box = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(box, value); + return box; +} + +static ScrClosure *bcs_hook(ScrDyn *object, void *inv) { + ScrClosure *closure = scr_closure_new(inv, 1); + closure->caps[0] = bcs_box(scr_dyn_retain(object)); + return closure; +} + +/* receiver[method](...args) with the receiver as `this`: +1 or NULL. */ +static ScrDyn *bcs_invoke(ScrDyn *object, const char *method, ScrDyn *const *args, size_t argc) { + ScrDyn *fn = bcs_get(object, method); + if (!fn) return NULL; + ScrDyn *result = NULL; + if (scr_dyn_is_callable(fn)) { + result = bcs_apply(fn, object, args, argc, method); + } else { + char message[96]; + snprintf(message, sizeof message, "this.%s is not a function", method); + scr_throw_error_msg(SCR_ERR_TYPE, message, strlen(message)); + } + scr_dyn_release(fn); + return result; +} + +/* The thrown value of a hook Node calls inside try/catch, as the runtime + * error its catch hands on (+1); clears the exception. */ +static ScrError *bcs_take_error(void) { + ScrCaught *caught = scr_exc_take(); + ScrDyn *value = scr_caught_to_dyn(caught); + scr_caught_release(caught); + ScrError *err = NULL; + if (value) { + err = scr_dyn_truthy(value) ? bcs_error_of(value) : NULL; + scr_dyn_release(value); + } + if (!err) { + ScrStr *empty = scr_str_new("", 0); + err = scr_error_new(SCR_ERR_ERROR, empty); + scr_str_release(empty); + } + return err; +} + +/* Readable.read: `try { this._read(hwm) } catch (err) { errorOrDestroy }` */ +static void bcs_read_inv(ScrClosure *cb, ScrStream *s, double size) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *n = scr_dyn_new_num(size); + ScrDyn *result = bcs_invoke(object, "_read", &n, 1); + scr_dyn_release(n); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *err = bcs_take_error(); + scr_stream_fail(s, err); + scr_error_release(err); + } + scr_dyn_release(object); +} + +/* _write(chunk, 'buffer', cb) and _transform(chunk, 'buffer', cb): a + * throw propagates to the write() caller, as in Node. */ +static void bcs_chunk_call(ScrClosure *cb, ScrStream *s, ScrBytes *chunk, const char *method, int done) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *args[3]; + args[0] = scr_dyn_new_buffer(chunk); + args[1] = bcs_str("buffer"); + args[2] = bcs_done(s, done); + ScrDyn *result = bcs_invoke(object, method, args, 3); + for (int i = 0; i < 3; i++) scr_dyn_release(args[i]); + if (result) scr_dyn_release(result); + scr_dyn_release(object); +} + +static void bcs_write_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { + bcs_chunk_call(cb, s, chunk, "_write", BCS_DONE_WRITE); +} + +static void bcs_transform_inv(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { + bcs_chunk_call(cb, s, chunk, "_transform", BCS_DONE_TRANSFORM); +} + +/* callFinal: `try { stream._final(onFinish) } catch (err) { onFinish(err) }`; + * a Transform's _flush runs inside the same try. */ +static void bcs_plain_call(ScrClosure *cb, ScrStream *s, const char *method, int done) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *callback = bcs_done(s, done); + ScrDyn *result = bcs_invoke(object, method, &callback, 1); + scr_dyn_release(callback); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *err = bcs_take_error(); + if (done == BCS_DONE_FINAL) scr_stream_final_done(s, err); + else scr_stream_flush_done(s, err, NULL, NULL); + } + scr_dyn_release(object); +} + +static bool bcs_is_function(ScrDyn *object, const char *name, bool *failed); + +/* prefinish reads `stream._final` when the writable side finishes, so a + * method assigned after the constructor runs; without one the finish goes + * on as Node's does when `typeof stream._final !== 'function'`. */ +static void bcs_final_inv(ScrClosure *cb, ScrStream *s) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + bool failed = false; + bool present = bcs_is_function(object, "_final", &failed); + scr_dyn_release(object); + if (failed) return; + if (present) bcs_plain_call(cb, s, "_final", BCS_DONE_FINAL); + else scr_stream_final_done(s, NULL); +} + +/* Transform's final reads `this._flush` the same way: without one it ends + * the readable side. */ +static void bcs_flush_inv(ScrClosure *cb, ScrStream *s) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + bool failed = false; + bool present = bcs_is_function(object, "_flush", &failed); + if (!failed && bcs_is_function(object, "_final", &failed)) { + bcs_unsupported("_final on a constructor-function Transform is not supported yet"); + failed = true; + } + scr_dyn_release(object); + if (failed) return; + if (present) bcs_plain_call(cb, s, "_flush", BCS_DONE_FLUSH); + else scr_stream_flush_done(s, NULL, NULL, NULL); +} + +static ScrDyn *bcs_default_method(int kind, const char *name); + +/* _destroy(err || null, onDestroy) inside try/catch. The prototype's + * default (cb(err)) completes without a call. */ +static void bcs_destroy_inv(ScrClosure *cb, ScrStream *s, ScrError *err) { + ScrDyn *object = scr_box_get_ref(cb->caps[0]); + ScrDyn *fn = bcs_get(object, "_destroy"); + ScrDyn *fallback = bcs_default_method(BCS_READABLE, "_destroy"); + if (fn && fallback && scr_dyn_strict_eq(fn, fallback)) { + ScrError *kept = err ? scr_error_retain(err) : NULL; + scr_stream_destroy_done(s, err ? scr_error_retain(err) : NULL); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + if (kept) scr_error_release(kept); + } else if (fn) { + ScrDyn *args[2]; + args[0] = err ? scr_dyn_from_error(err) : scr_dyn_new_null(); + args[1] = bcs_done(s, BCS_DONE_DESTROY); + ScrDyn *result = NULL; + if (scr_dyn_is_callable(fn)) { + result = bcs_apply(fn, object, args, 2, "_destroy"); + } else { + static const char message[] = "this._destroy is not a function"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + } + scr_dyn_release(args[0]); + scr_dyn_release(args[1]); + if (result) scr_dyn_release(result); + else if (scr_exc_pending()) { + ScrError *thrown = bcs_take_error(); + ScrError *kept = scr_error_retain(thrown); + scr_stream_destroy_done(s, thrown); + if (!scr_exc_pending()) bcs_run_destroy_cbs(s, kept); + scr_error_release(kept); + } + } + if (fallback) scr_dyn_release(fallback); + if (fn) scr_dyn_release(fn); + scr_dyn_release(object); +} + +/* ── listeners: the runtime emitter, the receiver as `this` ─────────── */ + +enum { BCS_L_FN, BCS_L_SELF, BCS_L_CAPS }; + +static void bcs_listener_call(ScrClosure *adapter, ScrDyn *const *args, size_t argc) { + ScrDyn *fn = scr_box_get_ref(adapter->caps[BCS_L_FN]); + ScrDyn *self = scr_box_get_ref(adapter->caps[BCS_L_SELF]); + ScrDyn *result = bcs_apply(fn, self, args, argc, "listener"); + if (result) scr_dyn_release(result); + scr_dyn_release(self); + scr_dyn_release(fn); +} + +static void bcs_listener0(ScrClosure *adapter) { + bcs_listener_call(adapter, NULL, 0); +} + +static void bcs_listener_data(ScrClosure *adapter, ScrBytes *bytes, ScrStr *text) { + ScrDyn *arg = text ? scr_dyn_new_str(text) : scr_dyn_new_buffer(bytes); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_error(ScrClosure *adapter, ScrError *error) { + ScrDyn *arg = scr_dyn_from_error(error); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_stream(ScrClosure *adapter, ScrStream *stream) { + ScrDyn *arg = bcs_stream_value(stream); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +static void bcs_listener_name(ScrClosure *adapter, ScrStr *name) { + ScrDyn *arg = scr_dyn_new_str(name); + bcs_listener_call(adapter, &arg, 1); + scr_dyn_release(arg); +} + +/* Computed events: the listener's checked-dynamic call, with every + * argument emit passed. */ +static ScrDyn *bcs_listener_flex(ScrClosure *adapter, ScrDyn *const *args, size_t argc) { + bcs_listener_call(adapter, args, argc); + if (scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +static ScrClosure *bcs_adapter(ScrDyn *fn, ScrDyn *self, void *call) { + ScrClosure *adapter = scr_closure_new(call, BCS_L_CAPS); + adapter->caps[BCS_L_FN] = bcs_box(scr_dyn_retain(fn)); + adapter->caps[BCS_L_SELF] = bcs_box(scr_dyn_retain(self)); + scr_dyn_adopt_identity(adapter, fn); + return adapter; +} + +static bool bcs_is(const ScrStr *name, const char *text) { + size_t length = strlen(text); + return name->len == length && memcmp(name->data, text, length) == 0; +} + +/* The runtime's fixed event tuples: 0 none, 1 data, 2 error, 3 a stream + * (pipe/unpipe), 4 an event name (the meta events); -1 computed. */ +static int bcs_event_shape(const ScrStr *name) { + if (bcs_is(name, "data")) return 1; + if (bcs_is(name, "error")) return 2; + if (bcs_is(name, "pipe") || bcs_is(name, "unpipe")) return 3; + if (bcs_is(name, "newListener") || bcs_is(name, "removeListener")) return 4; + static const char *const empty[] = { + "end", "close", "finish", "drain", "readable", "resume", "pause", "prefinish", + }; + for (size_t i = 0; i < sizeof empty / sizeof *empty; i++) + if (bcs_is(name, empty[i])) return 0; + return -1; +} + +/* The event name argument as a string, or NULL with an exception + * pending. */ +static ScrStr *bcs_event_name(const ScrDyn *type) { + if (type->kind == SCR_DYN_STR) return scr_str_retain(type->v.str); + if (type->kind == SCR_DYN_SYMBOL) { + bcs_unsupported("symbol event names on a constructor-function stream are not supported yet"); + return NULL; + } + return scr_dyn_string_coerce_js(type); +} + +static bool bcs_check_listener(const ScrDyn *listener) { + if (listener->kind == SCR_DYN_FUNC) return true; + if (scr_dyn_is_callable(listener)) { + bcs_unsupported("listeners other than functions on a constructor-function stream are not supported yet"); + return false; + } + scr_dyn_arg_type_fail("listener", "of type function", listener); + return false; +} + +static bool bcs_add_listener(ScrStream *s, ScrDyn *self, ScrDyn *type, ScrDyn *listener, bool once, + bool prepend) { + if (!bcs_check_listener(listener)) return false; + ScrStr *name = bcs_event_name(type); + if (!name) return false; + ScrEmitter *em = (ScrEmitter *)s; + ScrEmitter *chained = NULL; + switch (bcs_event_shape(name)) { + case 0: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener0), + scr_ee_inv_fixed0, once, prepend); + break; + case 1: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_data), + scr_ee_inv_fixed2, once, prepend); + break; + case 2: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_error), + scr_ee_inv_fixed1, once, prepend); + break; + case 3: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_stream), + scr_ee_inv_fixed1, once, prepend); + break; + case 4: + chained = scr_emitter_on_dyn(em, name, listener, bcs_adapter(listener, self, (void *)bcs_listener_name), + scr_ee_inv_fixed1, once, prepend); + break; + default: { + ScrClosure *adapter = bcs_adapter(listener, self, (void *)bcs_listener_flex); + ScrDyn *wrapper = scr_dyn_new_func(adapter, bcs_listener_flex, 0, "native:events", "listener"); + chained = scr_emitter_on_flex(em, name, wrapper, once, prepend); + scr_dyn_release(wrapper); + break; + } + } + if (chained) scr_emitter_release(chained); + scr_str_release(name); + return !scr_exc_pending(); +} + +/* this.emit(type, ...args) on the runtime emitter: the fixed tuples the + * runtime's own listeners read, computed events with every argument. */ +static ScrDyn *bcs_emit(ScrStream *s, ScrDyn *const *args, size_t argc) { + ScrStr *name = bcs_event_name(bcs_arg(args, argc, 0)); + if (!name) return NULL; + ScrEmitter *em = (ScrEmitter *)s; + ScrDyn *const *rest = argc > 1 ? args + 1 : NULL; + size_t restc = argc > 1 ? argc - 1 : 0; + ScrDyn *first = restc ? rest[0] : scr_dyn_undefined(); + bool had = false; + switch (bcs_event_shape(name)) { + case 0: + had = scr_emitter_emit(em, name); + break; + case 1: + if (first->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(first->v.bytes); + had = scr_emitter_emit(em, name, bytes, (ScrStr *)NULL); + scr_bytes_release(bytes); + } else if (first->kind == SCR_DYN_STR) { + had = scr_emitter_emit(em, name, (ScrBytes *)NULL, first->v.str); + } else { + bcs_unsupported("emit('data') with a chunk other than a Buffer or string on a constructor-function stream is not supported yet"); + } + break; + case 2: + if (!scr_emitter_has(em, "error")) { + /* Node's unhandled 'error': an Error throws itself, anything else + * ERR_UNHANDLED_ERROR. */ + if (scr_dyn_err_instanceof(first, SCR_ERR_ERROR)) { + scr_dyn_throw(scr_dyn_retain(first)); + } else { + ScrStr *shown = scr_insp_dyn(first, 0, 2); + ScrJsonBuf text; + scr_jb_init(&text); + scr_jb_puts(&text, "Unhandled error. ("); + scr_jb_put_str(&text, shown); + scr_jb_putc(&text, ')'); + scr_str_release(shown); + ScrStr *message = scr_jb_finish(&text); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "ERR_UNHANDLED_ERROR"); + scr_str_release(message); + } + } else { + ScrError *err = bcs_error_of(first); + if (!err) { + ScrStr *empty = scr_str_new("", 0); + err = scr_error_new(SCR_ERR_ERROR, empty); + scr_str_release(empty); + } + had = scr_emitter_emit_error(em, name, err); + scr_error_release(err); + } + break; + case 3: { + ScrStream *stream = scr_dyn_builtin_stream_of(first); + if (stream) { + had = scr_emitter_emit(em, name, stream); + scr_stream_release(stream); + } else { + bcs_unsupported("emit('pipe') or emit('unpipe') with a value other than a stream on a constructor-function stream is not supported yet"); + } + break; + } + case 4: + if (first->kind == SCR_DYN_STR) had = scr_emitter_emit(em, name, first->v.str); + else bcs_unsupported("emit('newListener') or emit('removeListener') with a name other than a string on a constructor-function stream is not supported yet"); + break; + default: + had = scr_emitter_emit_flex(em, name, rest, restc); + break; + } + scr_str_release(name); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_bool(had); +} + +static ScrDyn *bcs_event_names(ScrStream *s) { + ScrArr *names = scr_emitter_event_names((ScrEmitter *)s); + ScrDyn *result = scr_dyn_new_arr(); + double length = scr_arr_len(names); + for (double i = 0; i < length; i++) { + ScrStr *name = scr_arr_get_ref(names, i); + scr_dyn_arr_push(result, scr_dyn_new_str(name)); + scr_str_release(name); + } + scr_arr_release(names); + return result; +} + +/* EventEmitter.prototype's methods on a constructor-function stream + * (called by scr_dyn_builtin_class.c): true when self has a stream. */ +static bool bcs_event(int method, ScrDyn *self, ScrDyn *const *args, size_t argc, ScrDyn **result) { + ScrStream *s = bcs_of(self); + if (!s) return false; + ScrDyn *type = bcs_arg(args, argc, 0); + ScrEmitter *em = (ScrEmitter *)s; + ScrDyn *out = NULL; + switch (method) { + case SCR_EE_M_ON: + case SCR_EE_M_PREPEND: + case SCR_EE_M_ONCE: + case SCR_EE_M_PREPEND_ONCE: + if (bcs_add_listener(s, self, type, bcs_arg(args, argc, 1), + method == SCR_EE_M_ONCE || method == SCR_EE_M_PREPEND_ONCE, + method == SCR_EE_M_PREPEND || method == SCR_EE_M_PREPEND_ONCE)) + out = scr_dyn_retain(self); + break; + case SCR_EE_M_OFF: { + ScrDyn *listener = bcs_arg(args, argc, 1); + if (!bcs_check_listener(listener)) break; + ScrStr *name = bcs_event_name(type); + if (!name) break; + scr_emitter_release(scr_emitter_off_dyn(em, name, listener)); + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_retain(self); + break; + } + case SCR_EE_M_REMOVE_ALL: { + ScrStr *name = argc == 0 || type->kind == SCR_DYN_UNDEF ? NULL : bcs_event_name(type); + if (argc > 0 && type->kind != SCR_DYN_UNDEF && !name) break; + if (!name) name = scr_str_new("", 0); + scr_emitter_release(scr_emitter_remove_all(em, name, argc == 0 || type->kind == SCR_DYN_UNDEF)); + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_retain(self); + break; + } + case SCR_EE_M_EMIT: + out = bcs_emit(s, args, argc); + break; + case SCR_EE_M_LISTENER_COUNT: { + ScrStr *name = bcs_event_name(type); + if (!name) break; + ScrDyn *only = bcs_arg(args, argc, 1); + double count = bcs_nullish(only) ? scr_emitter_listener_count(em, name) + : only->kind == SCR_DYN_FUNC ? scr_emitter_listener_count_dyn(em, name, only) : 0; + scr_str_release(name); + if (!scr_exc_pending()) out = scr_dyn_new_num(count); + break; + } + case SCR_EE_M_EVENT_NAMES: + out = bcs_event_names(s); + break; + case SCR_EE_M_SET_MAX: + scr_emitter_release(scr_emitter_set_max(em, bcs_arg(args, argc, 0)->v.num)); + out = scr_dyn_retain(self); + break; + case SCR_EE_M_GET_MAX: + out = scr_dyn_new_num(scr_emitter_get_max(em)); + break; + default: + bcs_unsupported("listeners() and rawListeners() on a constructor-function stream are not supported yet"); + break; + } + *result = scr_exc_pending() ? (out ? (scr_dyn_release(out), NULL) : NULL) : out; + return true; +} + +/* ── construction ───────────────────────────────────────────────────── */ + +static bool bcs_is_function(ScrDyn *object, const char *name, bool *failed) { + ScrDyn *value = bcs_get(object, name); + if (!value) { + *failed = true; + return false; + } + bool callable = scr_dyn_is_callable(value); + scr_dyn_release(value); + return callable; +} + +/* Node's constructors copy these options onto the instance. */ +static bool bcs_copy_option(ScrDyn *self, ScrDyn *opts, const char *option, const char *property) { + ScrDyn *value = bcs_get(opts, option); + if (!value) return false; + if (scr_dyn_is_callable(value)) return bcs_set(self, property, value); + scr_dyn_release(value); + return true; +} + +static void bcs_class_name(ScrDyn *self, char *out, size_t size) { + snprintf(out, size, "%s", "Object"); + ScrDyn *ctor = bcs_get(self, "constructor"); + if (!ctor) { + scr_exc_clear(); + return; + } + if (scr_dyn_is_callable(ctor)) { + ScrDyn *name = bcs_get(ctor, "name"); + if (name && name->kind == SCR_DYN_STR && name->v.str->len > 0) + snprintf(out, size, "%.*s", (int)name->v.str->len, name->v.str->data); + if (name) scr_dyn_release(name); + else scr_exc_clear(); + } + scr_dyn_release(ctor); +} + +static ScrDyn *bcs_state_proto(bool readable); + +/* Listeners added before the constructor ran (bl registers its 'pipe' + * listener first) sit in the instance's own _events, where Node's + * constructor keeps them: they move to the runtime emitter, in order. */ +static bool bcs_adopt_listeners(ScrStream *s, ScrDyn *self) { + ScrDyn *events = bcs_get(self, "_events"); + if (!events) return false; + if (events->kind != SCR_DYN_OBJ) { + scr_dyn_release(events); + return true; + } + ScrDyn *keys = scr_dyn_own_keys(events); + bool ok = keys != NULL; + size_t count = ok ? (size_t)scr_dyn_arr_len(keys) : 0; + for (size_t i = 0; ok && i < count; i++) { + ScrDyn *key = scr_dyn_index_get(keys, (double)i); + ScrDyn *list = key ? scr_dyn_reflect_get(events, key, events) : NULL; + if (!list) { + if (key) scr_dyn_release(key); + ok = false; + break; + } + if (list->kind == SCR_DYN_ARR) { + size_t length = (size_t)scr_dyn_arr_len(list); + for (size_t j = 0; ok && j < length; j++) { + ScrDyn *listener = scr_dyn_index_get(list, (double)j); + ok = listener && bcs_add_listener(s, self, key, listener, false, false); + if (listener) scr_dyn_release(listener); + } + } else if (scr_dyn_is_callable(list)) { + ok = bcs_add_listener(s, self, key, list, false, false); + } + scr_dyn_release(list); + scr_dyn_release(key); + } + if (keys) scr_dyn_release(keys); + scr_dyn_release(events); + if (ok && count > 0) + ok = bcs_set(self, "_events", scr_dyn_new_obj_null_proto()) && bcs_set(self, "_eventsCount", scr_dyn_new_num(0)); + return ok; +} + +static bool bcs_state_view(ScrDyn *self, ScrStream *s, bool readable) { + ScrDyn *view = scr_dyn_obj_create(bcs_state_proto(readable)); + bcs_hide(view, s); + return bcs_set(self, readable ? "_readableState" : "_writableState", view); +} + +static ScrDyn *bcs_construct(int kind, ScrDyn *self, ScrDyn *const *args, size_t argc) { + static const char *const names[] = { "", "", "Readable", "Writable", "Duplex", "Transform", "PassThrough" }; + char message[192]; + if (self->kind != SCR_DYN_OBJ || !scr_dyn_builtin_instanceof(self, kind)) { + if (scr_exc_pending()) return NULL; + snprintf(message, sizeof message, + "%s.call() on a receiver that does not inherit %s.prototype is not supported yet", + names[kind], names[kind]); + bcs_unsupported(message); + return NULL; + } + if (bcs_of(self)) { + snprintf(message, sizeof message, + "%s.call() on a receiver that already has stream state is not supported yet", names[kind]); + bcs_unsupported(message); + return NULL; + } + bool failed = false; + bool writes = kind != BCS_READABLE; + if (writes && bcs_is_function(self, "_writev", &failed)) { + bcs_unsupported("_writev on a constructor-function stream is not supported yet"); + return NULL; + } + if (!failed && bcs_is_function(self, "_construct", &failed)) { + bcs_unsupported("_construct on a constructor-function stream is not supported yet"); + return NULL; + } + if (failed) return NULL; + ScrDyn *opts = bcs_arg(args, argc, 0); + ScrStream *s = kind == BCS_READABLE ? scr_stream_new_readable_dyn(opts) + : kind == BCS_WRITABLE ? scr_stream_new_writable_dyn(opts) + : kind == BCS_DUPLEX ? scr_stream_new_duplex_dyn(opts) + : kind == BCS_TRANSFORM ? scr_stream_new_transform_dyn(opts) + : scr_stream_new_passthrough_dyn(opts); + if (!s) return NULL; + bool ok = true; + if (opts->kind == SCR_DYN_OBJ) { + bool transformish = kind == BCS_TRANSFORM || kind == BCS_PASSTHROUGH; + if (!transformish && kind != BCS_WRITABLE) ok = ok && bcs_copy_option(self, opts, "read", "_read"); + if (!transformish && kind != BCS_READABLE) { + ok = ok && bcs_copy_option(self, opts, "write", "_write"); + ok = ok && bcs_copy_option(self, opts, "final", "_final"); + } + ok = ok && bcs_copy_option(self, opts, "destroy", "_destroy"); + if (transformish) { + ok = ok && bcs_copy_option(self, opts, "transform", "_transform"); + ok = ok && bcs_copy_option(self, opts, "flush", "_flush"); + } + } + if (ok && kind >= BCS_DUPLEX) + ok = bcs_set(self, "allowHalfOpen", scr_dyn_new_bool(scr_stream_prop(s, "allowHalfOpen") != 0)); + if (!ok) { + scr_stream_release(s); + return NULL; + } + BcsRec *rec = calloc(1, sizeof *rec); + if (!rec) scr_trap("out of memory"); + rec->object = self; + bcs_class_name(self, rec->cls, sizeof rec->cls); + scr_stream_set_native(s, rec, bcs_rec_release); + s->cls = rec->cls; + /* The runtime calls the instance's underscore methods. */ + if (kind == BCS_READABLE || kind == BCS_DUPLEX) + scr_stream_set_read(s, bcs_hook(self, (void *)bcs_read_inv), bcs_read_inv); + if (kind == BCS_WRITABLE || kind == BCS_DUPLEX) { + scr_stream_set_write(s, bcs_hook(self, (void *)bcs_write_inv), bcs_write_inv); + /* _final is read when the writable side finishes (bcs_final_inv). */ + scr_stream_set_final(s, bcs_hook(self, (void *)bcs_final_inv), bcs_final_inv); + } + if (kind == BCS_TRANSFORM || kind == BCS_PASSTHROUGH) { + /* _transform is read per chunk (a PassThrough's default passes the + * chunk on) and _flush when the writable side finishes. */ + scr_stream_set_transform(s, bcs_hook(self, (void *)bcs_transform_inv), bcs_transform_inv); + scr_stream_set_flush(s, bcs_hook(self, (void *)bcs_flush_inv), bcs_flush_inv); + if (!failed && bcs_is_function(self, "_final", &failed)) { + bcs_unsupported("_final on a constructor-function Transform is not supported yet"); + failed = true; + } + } + scr_stream_set_destroy(s, bcs_hook(self, (void *)bcs_destroy_inv), bcs_destroy_inv); + bcs_hide(self, s); + if (!failed) failed = !bcs_adopt_listeners(s, self); + if (!failed && kind != BCS_WRITABLE) failed = !bcs_state_view(self, s, true); + if (!failed && kind != BCS_READABLE) failed = !bcs_state_view(self, s, false); + scr_stream_release(s); /* the capsule holds it */ + if (failed || scr_exc_pending()) return NULL; + return bcs_undefined(); +} + +/* ── prototype methods ──────────────────────────────────────────────── */ + +/* Node's encoding names, canonical; NULL with ERR_UNKNOWN_ENCODING. */ +static ScrStr *bcs_encoding(const ScrDyn *value) { + static const struct { const char *from, *to; } map[] = { + {"utf8", "utf8"}, {"utf-8", "utf8"}, {"hex", "hex"}, {"base64", "base64"}, + {"base64url", "base64url"}, {"latin1", "latin1"}, {"binary", "latin1"}, + {"ascii", "ascii"}, {"utf16le", "utf16le"}, {"utf-16le", "utf16le"}, + {"ucs2", "utf16le"}, {"ucs-2", "utf16le"}, + }; + ScrStr *raw = value->kind == SCR_DYN_STR ? scr_str_retain(value->v.str) : scr_dyn_string_coerce_js(value); + if (!raw) return NULL; + for (size_t i = 0; i < sizeof map / sizeof *map; i++) { + if (bcs_is(raw, map[i].from)) { + scr_str_release(raw); + return scr_str_new(map[i].to, strlen(map[i].to)); + } + } + char message[128]; + snprintf(message, sizeof message, "Unknown encoding: %.*s", (int)(raw->len < 64 ? raw->len : 64), raw->data); + scr_str_release(raw); + scr_throw_error_msg_code(SCR_ERR_TYPE, message, strlen(message), "ERR_UNKNOWN_ENCODING"); + return NULL; +} + +enum { + BCS_M_PUSH, BCS_M_UNSHIFT, BCS_M_READ, BCS_M_SET_ENCODING, BCS_M_PAUSE, BCS_M_RESUME, + BCS_M_IS_PAUSED, BCS_M_PIPE, BCS_M_UNPIPE, BCS_M_DESTROY, BCS_M_WRITE, BCS_M_END, + BCS_M_CORK, BCS_M_UNCORK, BCS_M_SET_DEFAULT_ENCODING, BCS_M_WRITABLE_PIPE, BCS_M_WRAP, + BCS_M_READ_DEFAULT, BCS_M_WRITE_DEFAULT, BCS_M_TRANSFORM_DEFAULT, BCS_M_DESTROY_DEFAULT, + BCS_M_PASSTHROUGH_TRANSFORM, BCS_M_ON, BCS_M_OFF, BCS_M_REMOVE_ALL, +}; + +enum { BCS_CB_STREAM = BCS_L_CAPS, BCS_CB_CAPS }; + +/* A write or end callback: null after a successful write and at 'finish'; + * once the stream failed, its error, or Node's ERR_STREAM_DESTROYED for a + * write a destroy cut off. */ +static void bcs_callback_fire(ScrClosure *closure) { + ScrDyn *fn = scr_box_get_ref(closure->caps[BCS_L_FN]); + ScrDyn *self = scr_box_get_ref(closure->caps[BCS_L_SELF]); + ScrStream *s = scr_box_get_ref(closure->caps[BCS_CB_STREAM]); + ScrError *err = scr_stream_errored(s); + ScrDyn *arg; + if (err) { + arg = scr_dyn_from_error(err); + scr_error_release(err); + } else if (scr_stream_prop(s, "destroyed") != 0) { + ScrStr *message = scr_str_new("Cannot call write after a stream was destroyed", 46); + ScrError *destroyed = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_error_set_code(destroyed, "ERR_STREAM_DESTROYED"); + arg = scr_dyn_from_error(destroyed); + scr_error_release(destroyed); + } else { + arg = scr_dyn_new_null(); + } + scr_stream_release(s); + ScrDyn *result = bcs_apply(fn, self, &arg, 1, "callback"); + if (result) scr_dyn_release(result); + scr_dyn_release(arg); + scr_dyn_release(self); + scr_dyn_release(fn); +} + +static ScrClosure *bcs_callback(ScrDyn *fn, ScrDyn *self, ScrStream *s) { + if (!scr_dyn_is_callable(fn)) return NULL; + ScrClosure *closure = scr_closure_new((void *)bcs_callback_fire, BCS_CB_CAPS); + closure->caps[BCS_L_FN] = bcs_box(scr_dyn_retain(fn)); + closure->caps[BCS_L_SELF] = bcs_box(scr_dyn_retain(self)); + closure->caps[BCS_CB_STREAM] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[BCS_CB_STREAM], scr_stream_retain(s)); + return closure; +} + +/* Strings in another encoding than utf8 have no write path here. */ +static bool bcs_utf8_only(const ScrDyn *encoding) { + if (encoding->kind != SCR_DYN_STR) return true; + ScrStr *canonical = bcs_encoding(encoding); + if (!canonical) return false; + bool utf8 = bcs_is(canonical, "utf8"); + scr_str_release(canonical); + if (!utf8) bcs_unsupported("writing strings in an encoding other than utf8 to a constructor-function stream is not supported yet"); + return utf8; +} + +static ScrDyn *bcs_write(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *chunk = bcs_arg(args, argc, 0); + ScrDyn *encoding = bcs_arg(args, argc, 1); + ScrDyn *cb = bcs_arg(args, argc, 2); + if (scr_dyn_is_callable(encoding)) { + cb = encoding; + encoding = scr_dyn_undefined(); + } + if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL; + bool below = scr_stream_write_dyn(s, chunk, bcs_callback(cb, self, s)); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_bool(below); +} + +static ScrDyn *bcs_end(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *chunk = bcs_arg(args, argc, 0); + ScrDyn *encoding = bcs_arg(args, argc, 1); + ScrDyn *cb = bcs_arg(args, argc, 2); + if (scr_dyn_is_callable(chunk)) { + cb = chunk; + chunk = scr_dyn_undefined(); + encoding = scr_dyn_undefined(); + } else if (scr_dyn_is_callable(encoding)) { + cb = encoding; + encoding = scr_dyn_undefined(); + } + if (chunk->kind == SCR_DYN_STR && !bcs_utf8_only(encoding)) return NULL; + ScrStream *chained = NULL; + if (bcs_nullish(chunk)) { + chained = scr_stream_end(s, NULL, NULL, bcs_callback(cb, self, s)); + } else if (chunk->kind == SCR_DYN_STR) { + chained = scr_stream_end(s, NULL, chunk->v.str, bcs_callback(cb, self, s)); + } else if (chunk->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(chunk->v.bytes); + chained = scr_stream_end(s, bytes, NULL, bcs_callback(cb, self, s)); + scr_bytes_release(bytes); + } else { + scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer, TypedArray, or DataView", chunk); + } + if (chained) scr_stream_release(chained); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(self); +} + +static ScrDyn *bcs_destroy(ScrStream *s, ScrDyn *self, ScrDyn *const *args, size_t argc) { + ScrDyn *errv = bcs_arg(args, argc, 0); + ScrDyn *cb = bcs_arg(args, argc, 1); + if (scr_stream_prop(s, "destroyed") != 0) { + if (scr_dyn_is_callable(cb)) { + ScrDyn *result = bcs_apply(cb, self, NULL, 0, "callback"); + if (!result) return NULL; + scr_dyn_release(result); + } + return scr_dyn_retain(self); + } + if (scr_dyn_is_callable(cb)) { + BcsRec *rec = bcs_rec(s); + if (rec->destroy_n == rec->destroy_cap) { + rec->destroy_cap = rec->destroy_cap ? rec->destroy_cap * 2 : 2; + rec->destroy_cbs = realloc(rec->destroy_cbs, rec->destroy_cap * sizeof *rec->destroy_cbs); + if (!rec->destroy_cbs) scr_trap("out of memory"); + } + rec->destroy_cbs[rec->destroy_n++] = scr_dyn_retain(cb); + } + ScrError *err = bcs_error_of(errv); + ScrStream *chained = scr_stream_destroy(s, err); + if (err) scr_error_release(err); + scr_stream_release(chained); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(self); +} + +/* pipe(dest, { end }): runtime streams and constructor-function streams + * pipe through the runtime's pipe. */ +static bool bcs_overrides(ScrDyn *dest, const char *name); + +static ScrDyn *bcs_pipe(ScrStream *s, ScrDyn *const *args, size_t argc) { + ScrDyn *dest = bcs_arg(args, argc, 0); + ScrDyn *options = bcs_arg(args, argc, 1); + ScrStream *d = scr_dyn_builtin_stream_of(dest); + if (!d) { + if (!scr_exc_pending()) bcs_unsupported("pipe destinations other than streams are not supported yet"); + return NULL; + } + /* The runtime's pipe writes and ends the destination's stream itself; + * Node calls the destination's write() and end(). */ + if (bcs_of(dest) && (bcs_overrides(dest, "write") || bcs_overrides(dest, "end"))) { + scr_stream_release(d); + if (!scr_exc_pending()) + bcs_unsupported("pipe into a constructor-function stream with its own write() or end() is not supported yet"); + return NULL; + } + bool end = true; + if (options->kind == SCR_DYN_OBJ) { + ScrDyn *option = bcs_get(options, "end"); + if (!option) { + scr_stream_release(d); + return NULL; + } + end = !(option->kind == SCR_DYN_BOOL && !option->v.b); + scr_dyn_release(option); + } + ScrStream *chained = scr_stream_pipe(s, d, end); + if (chained) scr_stream_release(chained); + scr_stream_release(d); + if (scr_exc_pending()) return NULL; + return scr_dyn_retain(dest); +} + +static ScrDyn *bcs_not_implemented(const char *method) { + char message[64]; + snprintf(message, sizeof message, "The %s method is not implemented", method); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, strlen(message), "ERR_METHOD_NOT_IMPLEMENTED"); + return NULL; +} + +static ScrDyn *bcs_method_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + int method = (int)scr_box_get_f64(closure->caps[0]); + switch (method) { + case BCS_M_READ_DEFAULT: + return bcs_not_implemented("_read()"); + case BCS_M_WRITE_DEFAULT: + return bcs_not_implemented("_write()"); + case BCS_M_TRANSFORM_DEFAULT: + return bcs_not_implemented("_transform()"); + case BCS_M_DESTROY_DEFAULT: + case BCS_M_PASSTHROUGH_TRANSFORM: { + /* _destroy(err, cb) => cb(err); _transform(chunk, enc, cb) => cb(null, chunk) */ + ScrDyn *cb = bcs_arg(args, argc, method == BCS_M_DESTROY_DEFAULT ? 1 : 2); + ScrDyn *pass[2]; + size_t passc = 1; + if (method == BCS_M_DESTROY_DEFAULT) { + pass[0] = bcs_arg(args, argc, 0); + } else { + pass[0] = scr_dyn_new_null(); + pass[1] = bcs_arg(args, argc, 0); + passc = 2; + } + ScrDyn *answer = scr_dyn_call(cb, pass, passc, "callback"); + if (method != BCS_M_DESTROY_DEFAULT) scr_dyn_release(pass[0]); + return answer; + } + default: + break; + } + ScrDyn *self = scr_dyn_this_get(); + static const char *const reading[] = { + "push", "unshift", "read", "setEncoding", "pause", "resume", "isPaused", "pipe", "unpipe", + "destroy", "write", "end", "cork", "uncork", "setDefaultEncoding", "pipe", "wrap", + "", "", "", "", "", "on", "removeListener", "removeAllListeners", + }; + ScrDyn *result = NULL; + if (method == BCS_M_WRITABLE_PIPE) { + ScrStream *s = bcs_need(self, "pipe"); + if (s) { + ScrStr *message = scr_str_new("Cannot pipe, not readable", 25); + ScrError *err = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_error_set_code(err, "ERR_STREAM_CANNOT_PIPE"); + scr_stream_fail(s, err); + scr_error_release(err); + if (!scr_exc_pending()) result = scr_dyn_retain(bcs_arg(args, argc, 0)); + } + scr_dyn_release(self); + return result; + } + if (method == BCS_M_ON || method == BCS_M_OFF || method == BCS_M_REMOVE_ALL) { + int ee = method == BCS_M_ON ? SCR_EE_M_ON : method == BCS_M_OFF ? SCR_EE_M_OFF : SCR_EE_M_REMOVE_ALL; + if (!bcs_event(ee, self, args, argc, &result)) { + /* Before the constructor ran (bl adds listeners first), Node's + * Readable.prototype.on is Stream.prototype.on over _events. */ + ScrDyn *emitter = scr_dyn_builtin_class(0); + ScrDyn *prototype = bcs_get(emitter, "prototype"); + scr_dyn_release(emitter); + ScrDyn *fn = prototype ? bcs_get(prototype, reading[method]) : NULL; + result = fn ? bcs_apply(fn, self, args, argc, reading[method]) : NULL; + if (fn) scr_dyn_release(fn); + if (prototype) scr_dyn_release(prototype); + } + scr_dyn_release(self); + return result; + } + ScrStream *s = bcs_need(self, reading[method]); + if (!s) { + scr_dyn_release(self); + return NULL; + } + ScrDyn *first = bcs_arg(args, argc, 0); + switch (method) { + case BCS_M_PUSH: + case BCS_M_UNSHIFT: { + ScrDyn *encoding = bcs_arg(args, argc, 1); + if (method == BCS_M_PUSH && first->kind == SCR_DYN_STR && encoding->kind == SCR_DYN_STR) { + ScrStr *canonical = bcs_encoding(encoding); + if (canonical) { + result = scr_dyn_new_bool(scr_stream_push_str_enc(s, first->v.str, canonical)); + scr_str_release(canonical); + } + } else if (method == BCS_M_PUSH) { + if (first->kind == SCR_DYN_UNDEF) { + ScrStr *empty = scr_str_new("", 0); + result = scr_dyn_new_bool(scr_stream_push_str(s, empty)); + scr_str_release(empty); + } else { + result = scr_dyn_new_bool(scr_stream_push_dyn(s, first)); + } + } else if (first->kind == SCR_DYN_STR) { + scr_stream_unshift_str(s, first->v.str); + result = bcs_undefined(); + } else if (first->kind == SCR_DYN_BYTES) { + ScrBytes *bytes = scr_bytes_raw_view(first->v.bytes); + scr_stream_unshift(s, bytes); + scr_bytes_release(bytes); + result = bcs_undefined(); + } else if (first->kind == SCR_DYN_NULL) { + scr_stream_push_null(s); + result = bcs_undefined(); + } else { + scr_dyn_arg_type_fail("chunk", "of type string or an instance of Buffer or Uint8Array", first); + } + break; + } + case BCS_M_READ: + result = scr_stream_read_dyn(s, first); + break; + case BCS_M_SET_ENCODING: { + ScrStr *canonical = bcs_encoding(first->kind == SCR_DYN_UNDEF ? scr_dyn_undefined() : first); + if (canonical) { + scr_stream_release(scr_stream_set_encoding(s, canonical)); + scr_str_release(canonical); + result = scr_dyn_retain(self); + } + break; + } + case BCS_M_PAUSE: + scr_stream_release(scr_stream_pause(s)); + result = scr_dyn_retain(self); + break; + case BCS_M_RESUME: + scr_stream_release(scr_stream_resume(s)); + result = scr_dyn_retain(self); + break; + case BCS_M_IS_PAUSED: + result = scr_dyn_new_bool(scr_stream_is_paused(s)); + break; + case BCS_M_PIPE: + result = bcs_pipe(s, args, argc); + break; + case BCS_M_UNPIPE: { + ScrStream *d = first->kind == SCR_DYN_UNDEF ? NULL : scr_dyn_builtin_stream_of(first); + if (first->kind == SCR_DYN_UNDEF || d) { + scr_stream_release(scr_stream_unpipe(s, d)); + if (d) scr_stream_release(d); + } + if (!scr_exc_pending()) result = scr_dyn_retain(self); + break; + } + case BCS_M_DESTROY: + result = bcs_destroy(s, self, args, argc); + break; + case BCS_M_WRITE: + result = bcs_write(s, self, args, argc); + break; + case BCS_M_END: + result = bcs_end(s, self, args, argc); + break; + case BCS_M_CORK: + scr_stream_cork(s); + result = bcs_undefined(); + break; + case BCS_M_UNCORK: + scr_stream_uncork(s); + result = bcs_undefined(); + break; + case BCS_M_SET_DEFAULT_ENCODING: + if (bcs_utf8_only(first)) result = scr_dyn_retain(self); + break; + default: + bcs_unsupported("Readable.prototype.wrap on a constructor-function stream is not supported yet"); + break; + } + scr_dyn_release(self); + if (scr_exc_pending()) { + if (result) scr_dyn_release(result); + return NULL; + } + return result; +} + +static ScrDyn *bcs_function(int method, const char *name, uint32_t arity) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], method); + return scr_dyn_new_func(closure, bcs_method_call, arity, "native:stream", name); +} + +static void bcs_method(ScrDyn *prototype, const char *name, int method, uint32_t arity) { + bcs_set(prototype, name, bcs_function(method, name, arity)); +} + +/* ── accessors ──────────────────────────────────────────────────────── */ + +enum { + BCS_P_READABLE, BCS_P_READABLE_ENDED, BCS_P_READABLE_LENGTH, BCS_P_READABLE_HWM, + BCS_P_READABLE_OBJECT_MODE, BCS_P_READABLE_FLOWING, BCS_P_WRITABLE, BCS_P_WRITABLE_ENDED, + BCS_P_WRITABLE_FINISHED, BCS_P_WRITABLE_LENGTH, BCS_P_WRITABLE_HWM, BCS_P_WRITABLE_NEED_DRAIN, + BCS_P_WRITABLE_CORKED, BCS_P_WRITABLE_OBJECT_MODE, BCS_P_DESTROYED, BCS_P_CLOSED, BCS_P_ERRORED, + BCS_P_COUNT +}; + +static const char *const bcs_property_names[BCS_P_COUNT] = { + "readable", "readableEnded", "readableLength", "readableHighWaterMark", "readableObjectMode", + "readableFlowing", "writable", "writableEnded", "writableFinished", "writableLength", + "writableHighWaterMark", "writableNeedDrain", "writableCorked", "writableObjectMode", + "destroyed", "closed", "errored", +}; + +static ScrDyn *bcs_flowing(ScrStream *s) { + double flowing = scr_stream_flowing(s); + return flowing < 0 ? scr_dyn_new_null() : scr_dyn_new_bool(flowing != 0); +} + +static ScrDyn *bcs_errored(ScrStream *s) { + ScrError *err = scr_stream_errored(s); + if (!err) return scr_dyn_new_null(); + ScrDyn *value = scr_dyn_from_error(err); + scr_error_release(err); + return value; +} + +static ScrDyn *bcs_getter_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + int property = (int)scr_box_get_f64(closure->caps[0]); + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + /* Node's getters answer undefined (or false) before the constructor ran. */ + if (!s) return property == BCS_P_DESTROYED ? scr_dyn_new_bool(false) : bcs_undefined(); + const char *name = bcs_property_names[property]; + switch (property) { + case BCS_P_READABLE_FLOWING: + return bcs_flowing(s); + case BCS_P_ERRORED: + return bcs_errored(s); + case BCS_P_WRITABLE_OBJECT_MODE: + return scr_dyn_new_bool(false); + case BCS_P_READABLE_LENGTH: + case BCS_P_READABLE_HWM: + case BCS_P_WRITABLE_LENGTH: + case BCS_P_WRITABLE_HWM: + case BCS_P_WRITABLE_CORKED: + return scr_dyn_new_num(scr_stream_prop(s, name)); + default: + return scr_dyn_new_bool(scr_stream_prop(s, name) != 0); + } +} + +/* The destroyed setter: Node sets the states' flag without destroying. + * The readable and writable setters store a flag the runtime state does + * not keep (legacy code assigns `this.readable = true`), so they are + * accepted and have no effect. */ +static ScrDyn *bcs_setter_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + int property = (int)scr_box_get_f64(closure->caps[0]); + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + if (s && property == BCS_P_DESTROYED) scr_stream_set_destroyed(s, scr_dyn_truthy(bcs_arg(args, argc, 0))); + return bcs_undefined(); +} + +static void bcs_define(ScrDyn *target, const char *name, ScrDyn *getter, ScrDyn *setter) { + ScrDyn *descriptor = scr_dyn_new_obj(); + bcs_set(descriptor, "get", getter); + if (setter) bcs_set(descriptor, "set", setter); + bcs_set(descriptor, "enumerable", scr_dyn_new_bool(false)); + bcs_set(descriptor, "configurable", scr_dyn_new_bool(true)); + ScrDyn *key = bcs_key(name); + ScrDyn *defined = scr_dyn_define_property(target, key, descriptor); + if (defined) scr_dyn_release(defined); + scr_dyn_release(key); + scr_dyn_release(descriptor); +} + +static void bcs_accessor(ScrDyn *prototype, int property) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], property); + const char *name = bcs_property_names[property]; + ScrDyn *getter = scr_dyn_new_func(closure, bcs_getter_call, 0, "native:stream", name); + ScrDyn *setter = NULL; + if (property == BCS_P_DESTROYED || property == BCS_P_READABLE || property == BCS_P_WRITABLE) { + ScrClosure *set = scr_closure_new(NULL, 1); + set->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(set->caps[0], property); + setter = scr_dyn_new_func(set, bcs_setter_call, 1, "native:stream", name); + } + bcs_define(prototype, name, getter, setter); +} + +/* ── _readableState / _writableState views ──────────────────────────── */ + +static const char *const bcs_rs_names[] = { + "highWaterMark", "length", "ended", "endEmitted", "reading", "destroyed", "objectMode", + "needReadable", "emittedReadable", "readableListening", "resumeScheduled", "errorEmitted", + "closed", "constructed", "flowing", "errored", +}; +static const char *const bcs_ws_names[] = { + "highWaterMark", "length", "ended", "ending", "finished", "prefinished", "destroyed", + "objectMode", "needDrain", "corked", "closed", "constructed", "errorEmitted", + "bufferedRequestCount", "errored", +}; +#define BCS_RS_COUNT (sizeof bcs_rs_names / sizeof *bcs_rs_names) +#define BCS_WS_COUNT (sizeof bcs_ws_names / sizeof *bcs_ws_names) + +static SCR_TL ScrDyn *bcs_state_protos[2]; +static SCR_TL bool bcs_teardown_registered; + +static ScrDyn *bcs_state_get(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + bool readable = scr_box_get_f64(closure->caps[0]) != 0; + size_t index = (size_t)scr_box_get_f64(closure->caps[1]); + const char *name = readable ? bcs_rs_names[index] : bcs_ws_names[index]; + ScrDyn *self = scr_dyn_this_get(); + ScrStream *s = bcs_of(self); + scr_dyn_release(self); + if (!s) return bcs_undefined(); + if (!strcmp(name, "flowing")) return bcs_flowing(s); + if (!strcmp(name, "errored")) return bcs_errored(s); + char key[48]; + snprintf(key, sizeof key, "%s:%s", readable ? "rs" : "ws", name); + double value = scr_stream_prop(s, key); + bool number = !strcmp(name, "highWaterMark") || !strcmp(name, "length") || !strcmp(name, "corked") || + !strcmp(name, "bufferedRequestCount"); + return number ? scr_dyn_new_num(value) : scr_dyn_new_bool(value != 0); +} + +static void bcs_teardown(void) { + for (int i = 0; i < 2; i++) { + if (bcs_state_protos[i]) scr_dyn_release(bcs_state_protos[i]); + bcs_state_protos[i] = NULL; + } + if (scr_exc_pending()) scr_exc_clear(); +} + +static ScrDyn *bcs_state_proto(bool readable) { + ScrDyn **slot = &bcs_state_protos[readable ? 0 : 1]; + if (!*slot) { + if (!bcs_teardown_registered) { + bcs_teardown_registered = true; + scr_atexit(bcs_teardown); + } + ScrDyn *proto = scr_dyn_new_obj(); + size_t count = readable ? BCS_RS_COUNT : BCS_WS_COUNT; + for (size_t i = 0; i < count; i++) { + ScrClosure *closure = scr_closure_new(NULL, 2); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], readable ? 1 : 0); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], (double)i); + const char *name = readable ? bcs_rs_names[i] : bcs_ws_names[i]; + bcs_define(proto, name, scr_dyn_new_func(closure, bcs_state_get, 0, "native:stream", name), NULL); + } + *slot = proto; + } + return *slot; +} + +/* ── the prototypes ─────────────────────────────────────────────────── */ + +static ScrDyn *bcs_default_method(int kind, const char *name) { + ScrDyn *ctor = scr_dyn_builtin_class(kind); + ScrDyn *prototype = bcs_get(ctor, "prototype"); + scr_dyn_release(ctor); + if (!prototype) return NULL; + ScrDyn *method = bcs_get(prototype, name); + scr_dyn_release(prototype); + return method; +} + +static void bcs_writable_members(ScrDyn *prototype) { + bcs_method(prototype, "write", BCS_M_WRITE, 3); + bcs_method(prototype, "cork", BCS_M_CORK, 0); + bcs_method(prototype, "uncork", BCS_M_UNCORK, 0); + bcs_method(prototype, "setDefaultEncoding", BCS_M_SET_DEFAULT_ENCODING, 1); + bcs_method(prototype, "_write", BCS_M_WRITE_DEFAULT, 3); + bcs_set(prototype, "_writev", scr_dyn_new_null()); + bcs_method(prototype, "end", BCS_M_END, 3); + for (int p = BCS_P_WRITABLE; p <= BCS_P_WRITABLE_OBJECT_MODE; p++) bcs_accessor(prototype, p); +} + +/* Node's stream prototypes: Readable and Writable carry their methods, + * Duplex adds Writable's, Transform and PassThrough their _transform. */ +static void bcs_prototype(int kind, ScrDyn *prototype) { + switch (kind) { + case BCS_READABLE: + bcs_method(prototype, "destroy", BCS_M_DESTROY, 2); + bcs_method(prototype, "_destroy", BCS_M_DESTROY_DEFAULT, 2); + bcs_method(prototype, "push", BCS_M_PUSH, 2); + bcs_method(prototype, "unshift", BCS_M_UNSHIFT, 2); + bcs_method(prototype, "isPaused", BCS_M_IS_PAUSED, 0); + bcs_method(prototype, "setEncoding", BCS_M_SET_ENCODING, 1); + bcs_method(prototype, "read", BCS_M_READ, 1); + bcs_method(prototype, "_read", BCS_M_READ_DEFAULT, 1); + bcs_method(prototype, "pipe", BCS_M_PIPE, 2); + bcs_method(prototype, "on", BCS_M_ON, 2); + { + ScrDyn *on = bcs_get(prototype, "on"); + if (on) bcs_set(prototype, "addListener", on); + } + bcs_method(prototype, "removeListener", BCS_M_OFF, 2); + { + ScrDyn *off = bcs_get(prototype, "removeListener"); + if (off) bcs_set(prototype, "off", off); + } + bcs_method(prototype, "removeAllListeners", BCS_M_REMOVE_ALL, 1); + bcs_method(prototype, "resume", BCS_M_RESUME, 0); + bcs_method(prototype, "pause", BCS_M_PAUSE, 0); + bcs_method(prototype, "unpipe", BCS_M_UNPIPE, 1); + bcs_method(prototype, "wrap", BCS_M_WRAP, 1); + for (int p = BCS_P_READABLE; p <= BCS_P_READABLE_FLOWING; p++) bcs_accessor(prototype, p); + bcs_accessor(prototype, BCS_P_DESTROYED); + bcs_accessor(prototype, BCS_P_CLOSED); + bcs_accessor(prototype, BCS_P_ERRORED); + break; + case BCS_WRITABLE: + bcs_method(prototype, "pipe", BCS_M_WRITABLE_PIPE, 0); + bcs_writable_members(prototype); + bcs_method(prototype, "destroy", BCS_M_DESTROY, 2); + bcs_method(prototype, "_destroy", BCS_M_DESTROY_DEFAULT, 2); + bcs_accessor(prototype, BCS_P_DESTROYED); + bcs_accessor(prototype, BCS_P_CLOSED); + bcs_accessor(prototype, BCS_P_ERRORED); + break; + case BCS_DUPLEX: + bcs_writable_members(prototype); + bcs_accessor(prototype, BCS_P_DESTROYED); + break; + case BCS_TRANSFORM: + bcs_method(prototype, "_transform", BCS_M_TRANSFORM_DEFAULT, 3); + break; + case BCS_PASSTHROUGH: + bcs_method(prototype, "_transform", BCS_M_PASSTHROUGH_TRANSFORM, 3); + break; + default: + break; + } + if (scr_exc_pending()) scr_exc_clear(); +} + +static const ScrDynStreamOps bcs_ops = { bcs_construct, bcs_prototype, bcs_event }; + +/* A stream class value (the BC_* kinds of scr_dyn_builtin_class.c) with + * its stream half attached, +1. */ +ScrDyn *scr_dyn_builtin_stream_class(double kind) { + scr_dyn_builtin_class_streams(&bcs_ops); + return scr_dyn_builtin_class(kind); +} + +/* Whether dest's method is its own rather than the stream prototypes'. */ +static bool bcs_overrides(ScrDyn *dest, const char *name) { + ScrDyn *own = bcs_get(dest, name); + if (!own) return true; + ScrDyn *fallback = bcs_default_method(BCS_WRITABLE, name); + ScrDyn *duplex = bcs_default_method(BCS_DUPLEX, name); + bool overridden = !(fallback && scr_dyn_strict_eq(own, fallback)) && !(duplex && scr_dyn_strict_eq(own, duplex)); + if (fallback) scr_dyn_release(fallback); + if (duplex) scr_dyn_release(duplex); + scr_dyn_release(own); + return overridden; +} + +/* pipe and unpipe on a runtime stream's untyped view, which also take + * constructor-function streams. */ +static ScrDyn *bcs_view_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + ScrStream *s = scr_box_get_ref(closure->caps[0]); + bool pipe = scr_box_get_f64(closure->caps[1]) != 0; + ScrDyn *result = NULL; + if (pipe) { + result = bcs_pipe(s, args, argc); + } else { + ScrDyn *first = bcs_arg(args, argc, 0); + ScrStream *d = first->kind == SCR_DYN_UNDEF ? NULL : scr_dyn_builtin_stream_of(first); + if (first->kind == SCR_DYN_UNDEF || d) { + scr_stream_release(scr_stream_unpipe(s, d)); + if (d) scr_stream_release(d); + } else { + scr_dyn_arg_type_fail("destination", "an instance of Stream", first); + } + if (!scr_exc_pending()) result = scr_dyn_this_get(); + } + scr_stream_release(s); + return result; +} + +/* The untyped view of a runtime stream crossing into JavaScript, used in + * place of scr_stream_dyn_view by programs that link this unit: a + * constructor-function stream's own object, or the runtime view with pipe + * and unpipe that take those objects. +1. */ +ScrDyn *scr_dyn_builtin_stream_view(ScrStream *s) { + ScrDyn *object = scr_dyn_builtin_stream_object(s); + if (object) return object; + ScrDyn *view = scr_stream_dyn_view(s); + ScrDyn *prototype = scr_dyn_get_prototype(view); + if (prototype && prototype->kind == SCR_DYN_OBJ) { + for (int pipe = 0; pipe < 2; pipe++) { + ScrClosure *closure = scr_closure_new((void *)bcs_view_call, 2); + closure->caps[0] = scr_box_new_obj(scr_stream_retain_v, scr_stream_release_v, scr_stream_trace); + scr_box_set_ref(closure->caps[0], scr_stream_retain(s)); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], pipe); + const char *name = pipe ? "pipe" : "unpipe"; + bcs_set(prototype, name, scr_dyn_new_func(closure, bcs_view_call, 0, "native stream method", name)); + } + } + if (prototype) scr_dyn_release(prototype); + if (scr_exc_pending()) scr_exc_clear(); + return view; +} + +/* A checked cast of an untyped value to a runtime stream class (kind: + * the BC_* numbering): the stream of a constructor-function stream of a + * matching class, +1, or NULL. */ +ScrStream *scr_dyn_builtin_stream_check(const ScrDyn *value, double kind) { + if (value->kind != SCR_DYN_OBJ) return NULL; + ScrStream *s = bcs_of(value); + if (!s) return NULL; + const ScrVt *vt = s->vt; + bool readable = vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post; + bool writable = vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post; + bool duplex = vt->pre >= scr_duplex_vt.pre && vt->pre <= scr_duplex_vt.post; + bool ok = false; + switch ((int)kind) { + case BCS_READABLE: ok = readable; break; + case BCS_WRITABLE: ok = writable || duplex; break; + case BCS_DUPLEX: ok = duplex; break; + case BCS_TRANSFORM: ok = vt->pre >= scr_transform_vt.pre && vt->pre <= scr_transform_vt.post; break; + case BCS_PASSTHROUGH: ok = vt->pre >= scr_passthrough_vt.pre && vt->pre <= scr_passthrough_vt.post; break; + default: break; + } + return ok ? scr_stream_retain(s) : NULL; +} + +/* stream.dynNative (checked-dynamic stream slots, scr_stream_dyn.c) in + * programs that link this unit: a constructor-function stream with the + * requested side (1 readable, 2 writable) answers its runtime stream (+1), + * so a pipe into it, and an unpipe of it, are the runtime's own, unless it + * overrides write() or end(), which the runtime's pipe would bypass: that + * one is NULL and pipes through its JavaScript members. Other values take + * scr_stream_dyn_native. Never throws. */ +ScrStream *scr_dyn_builtin_stream_dyn_native(const ScrDyn *value, double side) { + ScrStream *s = value->kind == SCR_DYN_OBJ ? bcs_of(value) : NULL; + if (s) { + const ScrVt *vt = s->vt; + bool readable = vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post; + bool writable = vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post; + bool duplex = vt->pre >= scr_duplex_vt.pre && vt->pre <= scr_duplex_vt.post; + if (!(duplex || (side == 1 ? readable : writable))) return NULL; + ScrDyn *object = (ScrDyn *)value; + if (side == 2 && (bcs_overrides(object, "write") || bcs_overrides(object, "end"))) { + if (scr_exc_pending()) scr_exc_clear(); + return NULL; + } + return scr_stream_retain(s); + } + return scr_stream_dyn_native(value, side); +} + +/* stream.unboxDyn (an untyped pipe destination or pipeline stage) in + * programs that link this unit: a constructor-function stream answers its + * runtime stream (+1), unless it overrides write() or end(), which the + * runtime's pipe would bypass; other values take scr_stream_unbox_dyn. */ +ScrStream *scr_dyn_builtin_stream_unbox(const ScrDyn *value, const ScrStr *message) { + ScrStream *s = bcs_of(value); + if (s && value->kind == SCR_DYN_OBJ) { + ScrDyn *dest = (ScrDyn *)value; + if (bcs_overrides(dest, "write") || bcs_overrides(dest, "end")) { + if (!scr_exc_pending()) + bcs_unsupported("pipes into a constructor-function stream with its own write() or end() are not supported yet"); + return NULL; + } + return scr_stream_retain(s); + } + return scr_stream_unbox_dyn(value, message); +} diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 8a1bfa728..782fab829 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1558,6 +1558,17 @@ static ScrDyn *scr_dyn_invoke_impl( } return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined()); } + /* The typed array methods below are not DataView's; it inherits + * Object.prototype's. */ + if (dyn_name_is(method, "toString") || dyn_name_is(method, "toLocaleString")) { + ScrStr *tag = scr_str_new("[object DataView]", 17); + ScrDyn *out = scr_dyn_new_str(tag); + scr_str_release(tag); + return out; + } + if (dyn_name_is(method, "valueOf")) return scr_dyn_retain(recv); + dyn_throw_not_fn(what); + return NULL; } size_t blen = bytes->len; if (dyn_name_is(method, "at")) { @@ -1660,7 +1671,7 @@ static ScrDyn *dyn_builtin_method_call(ScrClosure *closure, ScrDyn *const *args, if (!strcmp(prototype->data, "Object")) { ScrDyn *key = argc ? args[0] : scr_dyn_undefined(); if (!strcmp(method->data, "toString")) { - ScrStr *tag = scr_dyn_object_tag(receiver); + ScrStr *tag = scr_dyn_object_tag_of(receiver); if (tag) { result = scr_dyn_new_str(tag); scr_str_release(tag); } } else if (!strcmp(method->data, "hasOwnProperty")) { bool own = scr_dyn_has_own_computed(receiver, key); @@ -1673,7 +1684,8 @@ static ScrDyn *dyn_builtin_method_call(ScrClosure *closure, ScrDyn *const *args, scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); } else if (receiver->kind == SCR_DYN_OBJ || receiver->kind == SCR_DYN_ARR || receiver->kind == SCR_DYN_FUNC || receiver->kind == SCR_DYN_HANDLE || - receiver->kind == SCR_DYN_TYPED_REF || receiver->kind == SCR_DYN_PROXY) result = scr_dyn_retain(receiver); + receiver->kind == SCR_DYN_TYPED_REF || receiver->kind == SCR_DYN_PROXY || + receiver->kind == SCR_DYN_PROMISE || receiver->kind == SCR_DYN_BYTES) result = scr_dyn_retain(receiver); else dyn_throw_unsupported("Object", method->data); } else { bool compatible = false; @@ -1746,6 +1758,358 @@ ScrDyn *scr_dyn_builtin_method(const ScrStr *prototype, const ScrStr *method) { return scr_dyn_retain(value); } +/* ── Builtin constructors, inherited Object.prototype members and builtin + * functions as dynamic values ────────────────────────────────────────── + * This unit links only into programs that use them (the frontend marks + * dyn.globalConstructor, dyn.builtinFunction and constant-key reads of the + * inherited names), so programs that never read them keep their exact + * executables. + * + * JavaScript reads `Object`, `Date`, `RegExp`, `Map`, `Set`, `Promise`, + * `WeakMap` and `WeakSet` as interned functions, and the same functions + * answer `.constructor` on the matching dynamic values, so + * `o.constructor === Object` and `a.constructor === b.constructor` are + * exact identity tests. `Object(v)` works through the stored value; calling + * the others through it is fenced. */ +/* String constants live in named arrays in their own section rather than + * in the unit's merged string pool (the explicit section also keeps the + * compiler from folding the unit's existing literals into them), so + * programs that link this unit for other reasons keep their exact bytes. */ +#if defined(__APPLE__) +#define SCR_DYN_COMPAT_STR __attribute__((section("__TEXT,__scr_dyn_compat"))) +#elif defined(_WIN32) +#define SCR_DYN_COMPAT_STR +#else +#define SCR_DYN_COMPAT_STR __attribute__((section(".rodata.scr_dyn_compat"))) +#endif +static const char dyn_s_error_tag[] SCR_DYN_COMPAT_STR = "[object Error]"; +static const char dyn_s0[] SCR_DYN_COMPAT_STR = "calling the %s constructor%s through a stored value is not supported yet"; +static const char dyn_s1[] SCR_DYN_COMPAT_STR = "calling '%s' through a stored value is not supported yet"; +static const char dyn_s3[] SCR_DYN_COMPAT_STR = "scriptc: invalid global constructor name\n"; +static const char dyn_s4[] SCR_DYN_COMPAT_STR = "propertyIsEnumerable"; +static const char dyn_s5[] SCR_DYN_COMPAT_STR = "ERR_INVALID_ARG_TYPE"; +static const char dyn_s6[] SCR_DYN_COMPAT_STR = "native:constructor"; +static const char dyn_s7[] SCR_DYN_COMPAT_STR = "process.nextTick"; +static const char dyn_s8[] SCR_DYN_COMPAT_STR = " on a primitive"; +static const char dyn_s9[] SCR_DYN_COMPAT_STR = "hasOwnProperty"; +static const char dyn_s10[] SCR_DYN_COMPAT_STR = "native:builtin"; +static const char dyn_s11[] SCR_DYN_COMPAT_STR = "queueMicrotask"; +static const char dyn_s12[] SCR_DYN_COMPAT_STR = "clearImmediate"; +static const char dyn_s13[] SCR_DYN_COMPAT_STR = "setImmediate"; +static const char dyn_s14[] SCR_DYN_COMPAT_STR = "constructor"; +static const char dyn_s15[] SCR_DYN_COMPAT_STR = "setInterval"; +static const char dyn_s16[] SCR_DYN_COMPAT_STR = "setTimeout"; +static const char dyn_s17[] SCR_DYN_COMPAT_STR = "callback"; +static const char dyn_s18[] SCR_DYN_COMPAT_STR = "toString"; +static const char dyn_s20[] SCR_DYN_COMPAT_STR = "Function"; +static const char dyn_s21[] SCR_DYN_COMPAT_STR = "Promise"; +static const char dyn_s22[] SCR_DYN_COMPAT_STR = "WeakMap"; +static const char dyn_s23[] SCR_DYN_COMPAT_STR = "valueOf"; +static const char dyn_s24[] SCR_DYN_COMPAT_STR = "WeakSet"; +static const char dyn_s25[] SCR_DYN_COMPAT_STR = "RegExp"; +static const char dyn_s26[] SCR_DYN_COMPAT_STR = "Object"; +static const char dyn_s27[] SCR_DYN_COMPAT_STR = "SC2020"; +static const char dyn_s28[] SCR_DYN_COMPAT_STR = "clear"; +static const char dyn_s29[] SCR_DYN_COMPAT_STR = "Date"; +static const char dyn_s30[] SCR_DYN_COMPAT_STR = "Map"; +static const char dyn_s31[] SCR_DYN_COMPAT_STR = "Set"; +static const char dyn_s32[] SCR_DYN_COMPAT_STR = ""; + +static const char *const dyn_global_ctor_names[] = { + dyn_s26, dyn_s29, dyn_s25, dyn_s30, dyn_s31, dyn_s21, dyn_s22, dyn_s24, +}; +#define DYN_GLOBAL_CTORS (sizeof dyn_global_ctor_names / sizeof dyn_global_ctor_names[0]) +static SCR_TL ScrDyn *dyn_global_ctors[DYN_GLOBAL_CTORS]; +static SCR_TL bool dyn_global_ctors_registered; + +static void dyn_global_ctors_cleanup(void) { + for (size_t i = 0; i < DYN_GLOBAL_CTORS; i++) { + scr_dyn_release(dyn_global_ctors[i]); + dyn_global_ctors[i] = NULL; + } +} + +static ScrDyn *dyn_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + const char *name = closure->fn; + bool object = strcmp(name, dyn_s26) == 0; + if (object) { + const ScrDyn *value = argc ? args[0] : scr_dyn_undefined(); + if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL) return scr_dyn_new_obj(); + if (value->kind != SCR_DYN_NUM && value->kind != SCR_DYN_STR && value->kind != SCR_DYN_BOOL && + value->kind != SCR_DYN_BIGINT && value->kind != SCR_DYN_SYMBOL) { + return scr_dyn_retain((ScrDyn *)value); + } + } + char message[128]; + int length = snprintf(message, sizeof message, dyn_s0, + name, object ? dyn_s8 : dyn_s32); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, dyn_s27); + return NULL; +} + +static ScrDyn *dyn_global_ctor(const char *name) { + for (size_t i = 0; i < DYN_GLOBAL_CTORS; i++) { + if (strcmp(dyn_global_ctor_names[i], name) != 0) continue; + if (!dyn_global_ctors_registered) { + dyn_global_ctors_registered = true; + scr_atexit(dyn_global_ctors_cleanup); + } + if (!dyn_global_ctors[i]) { + /* Each constructor's own length: Object 1, Date 7, RegExp 2, Promise 1, + * the collections 0. */ + uint32_t length = i == 0 || i == 5 ? 1 : i == 1 ? 7 : i == 2 ? 2 : 0; + dyn_global_ctors[i] = scr_dyn_new_func(scr_closure_new((void *)dyn_global_ctor_names[i], 0), + dyn_global_ctor_call, length, dyn_s6, + dyn_global_ctor_names[i]); + } + return scr_dyn_retain(dyn_global_ctors[i]); + } + return NULL; +} + +static ScrDyn *dyn_function_probe_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; (void)args; (void)argc; + return scr_dyn_retain(scr_dyn_undefined()); +} + +/* The Function constructor: the value any ordinary function answers for + * `.constructor` (scr_json.c keeps its singletons). */ +static ScrDyn *dyn_function_constructor(void) { + ScrDyn *probe = scr_dyn_new_func(scr_closure_new(NULL, 0), dyn_function_probe_call, 0, dyn_s10, dyn_s20); + ScrDyn *ctor = scr_dyn_fn_get(probe, dyn_s14, 11); + scr_dyn_release(probe); + return ctor; +} + +ScrDyn *scr_dyn_global_constructor(const ScrStr *name) { + if (strcmp(name->data, dyn_s20) == 0) return dyn_function_constructor(); + ScrDyn *ctor = dyn_global_ctor(name->data); + if (!ctor) scr_trap(dyn_s3); + return ctor; +} + +/* Object.prototype.toString's tag: errors answer "[object Error]" (the + * shared tag walk in scr_json.c answers "[object Object]" for them). */ +ScrStr *scr_dyn_object_tag_of(const ScrDyn *d) { + if (d->kind == SCR_DYN_OBJ && scr_dyn_err_instanceof(d, 0)) return scr_str_new(dyn_s_error_tag, sizeof dyn_s_error_tag - 1); + return scr_dyn_object_tag(d); +} + +/* Which Object.prototype members a value inherits when no nearer prototype + * defines them: types with their own toString or valueOf keep theirs. */ +enum { DYN_INHERIT_VALUE_OF = 1, DYN_INHERIT_TO_STRING = 2, DYN_INHERIT_OWN = 4, DYN_INHERIT_ALL = 7 }; + +static ScrDyn *dyn_inherited_from(const char *ctor, int methods, const ScrStr *key) { + if (strcmp(key->data, dyn_s14) == 0) return ctor ? dyn_global_ctor(ctor) : NULL; + bool applies = (strcmp(key->data, dyn_s23) == 0 && (methods & DYN_INHERIT_VALUE_OF)) || + (strcmp(key->data, dyn_s18) == 0 && (methods & DYN_INHERIT_TO_STRING)) || + ((strcmp(key->data, dyn_s9) == 0 || strcmp(key->data, dyn_s4) == 0) && + (methods & DYN_INHERIT_OWN)); + if (!applies) return NULL; + ScrStr *prototype = scr_str_new(dyn_s26, 6); + ScrDyn *method = scr_dyn_builtin_method(prototype, key); + scr_str_release(prototype); + return method; +} + +/* The inherited member `key` of `d` when its own chain does not define it, + * or NULL (the read's undefined stands). */ +static ScrDyn *dyn_inherited_member(const ScrDyn *d, const ScrStr *key) { + switch (d->kind) { + case SCR_DYN_TYPED_REF: { + ScrDyn *view = scr_dyn_typed_ref_materialize(d); + ScrDyn *member = view && !scr_exc_pending() ? dyn_inherited_member(view, key) : NULL; + scr_dyn_release(view); + return member; + } + case SCR_DYN_OBJ: { + /* A plain object, or an Object.create chain ending in Object.prototype; + * a member present anywhere on the chain (even as undefined) stands. */ + ScrDyn *object_prototype = scr_dyn_object_prototype(); + const ScrDyn *tail = d; + bool present = false; + for (const ScrDyn *link = d; link; link = link->prototype) { + tail = link; + if (link == object_prototype) break; + if (link->kind != SCR_DYN_OBJ || scr_dyn_has_own(link, key)) { + present = true; + break; + } + } + scr_dyn_release(object_prototype); + if (present || (tail != object_prototype && tail->null_proto)) return NULL; + /* Errors keep Error.prototype.toString and their constructor values. */ + if (scr_dyn_err_instanceof(d, 0)) return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key); + return dyn_inherited_from(dyn_s26, DYN_INHERIT_ALL, key); + } + case SCR_DYN_ARR: + /* Array.prototype defines toString and answers constructor itself. */ + if (d->null_proto || d->prototype || scr_dyn_has_own(d, key)) return NULL; + return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key); + case SCR_DYN_FUNC: + /* Function.prototype defines toString and answers constructor itself. */ + if (scr_dyn_has_own(d, key)) return NULL; + return dyn_inherited_from(NULL, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key); + case SCR_DYN_PROMISE: + return dyn_inherited_from(dyn_s21, DYN_INHERIT_ALL, key); + case SCR_DYN_HANDLE: + switch (d->v.handle.tag) { + case SCR_DYNH_DATE: return dyn_inherited_from(dyn_s29, DYN_INHERIT_OWN, key); + case SCR_DYNH_REGEXP: return dyn_inherited_from(dyn_s25, DYN_INHERIT_VALUE_OF | DYN_INHERIT_OWN, key); + case SCR_DYNH_MAP: return dyn_inherited_from(dyn_s30, DYN_INHERIT_ALL, key); + case SCR_DYNH_SET: return dyn_inherited_from(dyn_s31, DYN_INHERIT_ALL, key); + case SCR_DYNH_WEAK_MAP: return dyn_inherited_from(dyn_s22, DYN_INHERIT_ALL, key); + case SCR_DYNH_WEAK_SET: return dyn_inherited_from(dyn_s24, DYN_INHERIT_ALL, key); + default: return NULL; + } + default: + return NULL; + } +} + +/* Object.prototype's own constructor and shared methods, read on the + * prototype itself (`const { hasOwnProperty } = Object.prototype`, the + * `proto.constructor` of an isPlainObject check): the member, or NULL when + * `d` is another value and the ordinary read applies. */ +ScrDyn *scr_dyn_object_prototype_member(const ScrDyn *d, const ScrStr *key) { + ScrDyn *object_prototype = scr_dyn_object_prototype(); + bool prototype = d == object_prototype; + scr_dyn_release(object_prototype); + return prototype ? dyn_inherited_from(dyn_s26, DYN_INHERIT_ALL, key) : NULL; +} + +/* A constant-key read of constructor/valueOf/toString/hasOwnProperty/ + * propertyIsEnumerable: `read` is the ordinary read's +1 result (NULL when + * it threw). Takes it; answers the inherited member in place of an + * undefined read, the read otherwise. */ +ScrDyn *scr_dyn_inherited_read(const ScrDyn *d, const ScrStr *key, ScrDyn *read) { + if (!read || read->kind != SCR_DYN_UNDEF) return read; + ScrDyn *member = dyn_inherited_member(d, key); + if (scr_exc_pending()) { + scr_dyn_release(member); + scr_dyn_release(read); + return NULL; + } + if (!member) return read; + scr_dyn_release(read); + return member; +} + +/* Builtin global functions stored in JavaScript (the `var nextTick = typeof + * setImmediate === "function" ? setImmediate : setTimeout` probe): one + * interned function per name, so typeof answers "function" and `===` is + * identity. Calls run the timer and tick APIs with Node's argument + * contract — a function callback (else ERR_INVALID_ARG_TYPE), the delay + * through ToNumber, the remaining arguments passed to the callback — and + * timer functions answer the numeric handles the direct calls answer, + * which the clear functions accept. */ +static SCR_TL ScrDyn *dyn_builtin_fns; + +static void dyn_builtin_fns_cleanup(void) { + scr_dyn_release(dyn_builtin_fns); + dyn_builtin_fns = NULL; +} + +/* Library builds have no event loop (a library that schedules timers is + * refused at SC4005 before emission), so the calls that schedule are left + * out of them. */ +#ifndef SCR_LIB +static bool dyn_builtin_fn_callback(ScrDyn *const *args, size_t argc) { + if (argc > 0 && args[0]->kind == SCR_DYN_FUNC) return true; + static const char message[] = "The \"callback\" argument must be of type function."; + scr_throw_error_msg_code(SCR_ERR_TYPE, message, sizeof message - 1, dyn_s5); + return false; +} + +static void dyn_builtin_fn_fire(ScrClosure *c) { + ScrDyn *fn = (ScrDyn *)scr_box_get_ref(c->caps[0]); + ScrDyn *args = (ScrDyn *)scr_box_get_ref(c->caps[1]); + ScrDyn *r = scr_dyn_call(fn, args->v.arr.items, args->v.arr.len, dyn_s17); + if (r != NULL) scr_dyn_release(r); + scr_dyn_release(fn); + scr_dyn_release(args); +} + +/* The closure that calls args[0] with args[first..] when the loop runs it. */ +static ScrClosure *dyn_builtin_fn_deferred(ScrDyn *const *args, size_t argc, size_t first) { + ScrDyn *rest = scr_dyn_new_arr(); + for (size_t i = first; i < argc; i++) scr_dyn_arr_push(rest, scr_dyn_retain(args[i])); + ScrClosure *c = scr_closure_new((void *)dyn_builtin_fn_fire, 2); + c->caps[0] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); + scr_box_set_ref(c->caps[0], scr_dyn_retain(args[0])); + c->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, NULL); + scr_box_set_ref(c->caps[1], rest); /* moves */ + return c; +} + +#endif + +static ScrDyn *dyn_builtin_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { +#ifdef SCR_LIB + (void)clo; + (void)args; + (void)argc; + static const char message[] = "Builtin timer functions require an executable event loop"; + scr_throw_error_msg_code(SCR_ERR_ERROR, message, sizeof message - 1, "SC4005"); + return NULL; +#else + ScrStr *name = scr_box_get_ref(clo->caps[0]); + ScrDyn *result = NULL; + bool timeout = strcmp(name->data, dyn_s16) == 0; + if (timeout || strcmp(name->data, dyn_s15) == 0) { + double ms = 0; + if (dyn_builtin_fn_callback(args, argc) && (argc < 2 || scr_dyn_number_coerce_js(args[1], &ms))) { + ScrClosure *c = dyn_builtin_fn_deferred(args, argc, 2); + result = scr_dyn_new_num(timeout ? scr_set_timeout_handle(c, ms) : scr_set_interval(c, ms)); + } + } else if (strcmp(name->data, dyn_s13) == 0) { + if (dyn_builtin_fn_callback(args, argc)) + result = scr_dyn_new_num(scr_set_immediate(dyn_builtin_fn_deferred(args, argc, 1))); + } else if (strcmp(name->data, dyn_s7) == 0) { + if (dyn_builtin_fn_callback(args, argc)) { + scr_next_tick(dyn_builtin_fn_deferred(args, argc, 1)); + result = scr_dyn_retain(scr_dyn_undefined()); + } + } else if (strcmp(name->data, dyn_s11) == 0) { + if (dyn_builtin_fn_callback(args, argc)) { + scr_queue_microtask_dyn(args[0]); + result = scr_dyn_retain(scr_dyn_undefined()); + } + } else if (strncmp(name->data, dyn_s28, 5) == 0) { + /* Node ignores anything that is not a live handle. */ + if (argc > 0 && args[0]->kind == SCR_DYN_NUM) { + if (strcmp(name->data, dyn_s12) == 0) scr_clear_immediate(args[0]->v.num); + else scr_clear_interval(args[0]->v.num); + } + result = scr_dyn_retain(scr_dyn_undefined()); + } else { + char message[160]; + int length = snprintf(message, sizeof message, dyn_s1, name->data); + scr_throw_error_msg_code(SCR_ERR_ERROR, message, (size_t)length, dyn_s27); + } + scr_str_release(name); + return result; +#endif +} + +ScrDyn *scr_dyn_builtin_function(const ScrStr *name) { + if (!dyn_builtin_fns) { + dyn_builtin_fns = scr_dyn_new_obj_null_proto(); + scr_atexit(dyn_builtin_fns_cleanup); + } + ScrDyn *value = scr_dyn_obj_get(dyn_builtin_fns, name->data, name->len); + if (!value) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_STR); + scr_box_set_ref(closure->caps[0], scr_str_retain((ScrStr *)name)); + const char *dot = memchr(name->data, '.', name->len); + bool two = strcmp(name->data, dyn_s16) == 0 || strcmp(name->data, dyn_s15) == 0; + value = scr_dyn_new_func(closure, dyn_builtin_fn_call, two ? 2 : 1, dyn_s10, dot ? dot + 1 : name->data); + scr_dyn_obj_set(dyn_builtin_fns, name->data, name->len, value); /* the table owns it */ + } + return scr_dyn_retain(value); +} + ScrDyn *scr_dyn_array_prototype(void) { static SCR_TL bool initialized; ScrDyn *prototype = scr_dyn_array_prototype_base(); diff --git a/packages/runtime/src/scr_error.c b/packages/runtime/src/scr_error.c index 6f616c35a..4f514681b 100644 --- a/packages/runtime/src/scr_error.c +++ b/packages/runtime/src/scr_error.c @@ -354,6 +354,12 @@ ScrStr *scr_error_stack(ScrError *e) { return scr_str_retain(e->stack); } +void scr_error_set_stack(ScrError *e, ScrStr *stack) { + ScrStr *replacement = scr_str_retain(stack); + scr_str_release(e->stack); + e->stack = replacement; +} + bool scr_error_is(const void *obj) { const ScrVt *vt = ((const ScrError *)obj)->vt; return scr_error_vts[SCR_ERR_ERROR].pre <= vt->pre && diff --git a/packages/runtime/src/scr_error.h b/packages/runtime/src/scr_error.h index ce60dac23..a7c921f23 100644 --- a/packages/runtime/src/scr_error.h +++ b/packages/runtime/src/scr_error.h @@ -252,6 +252,9 @@ void scr_error_install_message_reader(ScrStr *(*reader)(ScrError *)); void scr_error_install_name_reader(ScrStr *(*reader)(ScrError *)); ScrStr *scr_error_default_name(const ScrError *e); ScrStr *scr_error_stack(ScrError *e); +/* Replaces the error's stack string, as an assignment to `stack` does. + * Borrowed operands. */ +void scr_error_set_stack(ScrError *e, ScrStr *stack); ScrError *scr_error_retain(ScrError *e); void scr_error_release(ScrError *e); /* dispatches through e->vt */ @@ -279,6 +282,21 @@ SCR_COLD void scr_undef_global_read(ScrStr *name); * +1 or NULL when absent (the compiler's undefined arm). The _msg_code * thrower is scr_throw_error_msg with the code stamped on the payload. */ void scr_error_set_code(ScrError *e, const char *code); +/* The program's `err.code = s` write into the same slot (and the error's + * checked-dynamic node, when it has one). Borrows both (scr_json.c). */ +void scr_error_set_code_value(ScrError *e, ScrStr *code); +/* `err.code = v` with a value that is not statically a string: a string + * takes the slot as above; anything else clears the slot and lands on the + * error's checked-dynamic node, as a dynamic property write would. + * Borrows both (scr_json.c). */ +void scr_error_set_code_dyn(ScrError *e, struct ScrDyn *value); +/* Object.assign(err, ...sources): the copy onto the error's checked-dynamic + * node, then the typed slots re-read from it. Borrows both (scr_json.c). */ +void scr_error_assign_dyn(ScrError *e, const struct ScrDyn *sources); +/* `key in err` for a key no compiled class declares: a runtime-set own + * property or an Object.prototype member. Borrows both (scr_json.c). */ +bool scr_error_has_key(ScrError *e, const ScrStr *key); +bool scr_error_has_code(ScrError *e); const char *scr_error_message_code(const ScrStr *msg); ScrStr *scr_error_code(ScrError *e); SCR_COLD void scr_throw_error_msg_code(int kind, const char *message, size_t len, const char *code); diff --git a/packages/runtime/src/scr_events_emitter.c b/packages/runtime/src/scr_events_emitter.c index ceecb15e3..4ac7abdf1 100644 --- a/packages/runtime/src/scr_events_emitter.c +++ b/packages/runtime/src/scr_events_emitter.c @@ -324,6 +324,13 @@ ScrEmitter *scr_emitter_new(void) { return em; } +/* x.constructor.name of an emitter, a stream, or an instance of an emitted + * subclass: the class name every allocation stamps (EventEmitter, + * Readable, ReadStream, Gzip, Socket, the program class's name). +1. */ +ScrStr *scr_emitter_class_name(const ScrEmitter *em) { + return scr_str_new(em->cls, strlen(em->cls)); +} + /* super() into the emitter prefix of an emitted subclass: the allocation * already zeroed reg and stamped the concrete class's display name, so * construction has nothing left to do — the call exists so the super() @@ -511,8 +518,25 @@ static ScrClosure *scr_ee_once_closure(void *adapter, ScrClosure *boxer, ScrEeOn return c; } +static ScrBox *scr_ee_once_promise_box(ScrPromise *p) { + ScrBox *box = scr_box_new_obj(&scr_promise_retain_v, &scr_promise_release_v, &scr_promise_trace_v); + scr_box_set_ref(box, scr_promise_retain(p)); + return box; +} + +static void scr_ee_once_adopt_job(ScrClosure *job) { + ScrPromise *outer = scr_box_get_ref(job->caps[0]); + ScrPromise *inner = scr_box_get_ref(job->caps[1]); + scr_promise_race_join(outer, inner, &scr_promise_adapt_copy); + scr_promise_release(inner); + scr_promise_release(outer); +} + /* boxer and err_boxer are borrowed; the result is +1. err_boxer is NULL - * when the awaited event is 'error' itself. */ + * when the awaited event is 'error' itself. Node's once is an async + * function returning the executor's promise, so the result adopts that + * promise through a thenable job queued here, whose reaction settles the + * result one turn after the executor's promise settles. */ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, ScrEeInvoke err_inv) { @@ -551,9 +575,13 @@ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, voi scr_caught_release(thrown); scr_promise_reject_pending(st->promise); } - ScrPromise *p = scr_promise_retain(st->promise); + ScrPromise *outer = scr_promise_new(); + ScrClosure *job = scr_closure_new((void *)&scr_ee_once_adopt_job, 2); + job->caps[0] = scr_ee_once_promise_box(outer); + job->caps[1] = scr_ee_once_promise_box(st->promise); + scr_queue_microtask(job); scr_ee_once_release(st); - return p; + return outer; } /* The adapter's tail: `cb` is the firing listener closure, `args` the @@ -1123,3 +1151,9 @@ double scr_emitter_get_default_max(void) { return scr_emitter_default_max; } static SCR_TL double scr_emitter_esm_default_max = 10; double scr_emitter_esm_default_max_get(void) { return scr_emitter_esm_default_max; } void scr_emitter_esm_sync(void) { scr_emitter_esm_default_max = scr_emitter_default_max; } + +/* events.once with a signal (scr_events_once_signal.c) undoes an add whose + * 'newListener' announcement threw. */ +void scr_emitter_unadd_quiet(ScrEmitter *em, ScrStr *name, ScrClosure *cb) { + scr_ee_unadd(em, name, cb); +} diff --git a/packages/runtime/src/scr_events_emitter.h b/packages/runtime/src/scr_events_emitter.h index 99da57a50..39a7f4b5b 100644 --- a/packages/runtime/src/scr_events_emitter.h +++ b/packages/runtime/src/scr_events_emitter.h @@ -38,6 +38,7 @@ extern SCR_TL ScrVt scr_emitter_vt; /* main() stamps pre/post */ extern SCR_TL double scr_emitter_default_max; ScrEmitter *scr_emitter_new(void); /* +1, collector-headered */ +ScrStr *scr_emitter_class_name(const ScrEmitter *em); /* +1 */ /* Internal owner hook; observes listener presence without running script. */ void scr_emitter_observe(ScrEmitter *em, void (*observe)(void *, const ScrStr *, bool), void *context); @@ -63,6 +64,20 @@ ScrPromise *scr_events_once(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, voi ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, ScrEeInvoke err_inv); void scr_events_once_fire(ScrClosure *cb, ScrArr *args); +/* events.once(emitter, name, { signal }) (scr_events_once_signal.c): the + * scr_events_once arguments plus the checked-dynamic signal (borrowed; + * undefined for the plain form). Never throws: a non-AbortSignal value, + * an aborted signal or a throwing 'newListener' listener rejects the +1 + * result. Its adapters end in scr_events_once_signal_fire. */ +typedef struct ScrDyn ScrDyn; /* full definition below (C11 repeat) */ +ScrPromise *scr_events_once_signal(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, + ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, + ScrEeInvoke err_inv, ScrDyn *signal); +void scr_events_once_signal_fire(ScrClosure *cb, ScrArr *args); +/* Removes the listener entry whose closure is `cb` (borrowed) without a + * 'removeListener' event: the undo of an add whose 'newListener' + * announcement threw. */ +void scr_emitter_unadd_quiet(ScrEmitter *em, ScrStr *name, ScrClosure *cb); ScrEmitter *scr_emitter_on(ScrEmitter *em, ScrStr *name, ScrClosure *cb /*moves*/, ScrEeInvoke inv, bool once, bool prepend); /* The dyn-adapted registration family (JS-lane checked-dynamic diff --git a/packages/runtime/src/scr_events_once_signal.c b/packages/runtime/src/scr_events_once_signal.c new file mode 100644 index 000000000..c3c2db37d --- /dev/null +++ b/packages/runtime/src/scr_events_once_signal.c @@ -0,0 +1,303 @@ +/* events.once(emitter, name, { signal }) over typed listeners — Node 24's + * lib/events.js `once` with an options object. Linked only by programs + * that pass the options (the emitter and event-target units together). + * + * The signal is validated first: undefined is the plain form, a value that + * is not an AbortSignal rejects with ERR_INVALID_ARG_TYPE, and an aborted + * signal rejects with an AbortError whose cause is signal.reason, all + * without registering a listener. Otherwise the event's one-shot listener, + * the 'error' one (unless the event is 'error') and a once, + * resist-stop-propagation 'abort' listener on the signal share the state + * below. The event or the error removes the other emitter listener and the + * abort listener, then settles; the abort removes both emitter listeners + * and rejects with the AbortError. + * + * Ownership: the state owns the promise, the name and the signal. The + * emitter is weak in the state; the abort listener holds it strongly (the + * abort is the one settlement that does not come from the emitter itself, + * like the closure Node's abortListener keeps). The three listener closures + * reach the state through link objects; a link clears the state's pointer + * to its closure when that closure is freed, so removals never compare a + * stale identity. The cycle through the signal's listener ends when the + * promise settles or the signal aborts (or at the exit teardown of the + * signal's listeners). */ +#include "scr_runtime.h" + +#include + +typedef struct ScrEosState { + size_t rc; + ScrPromise *promise; /* owned */ + ScrEmitter *em; /* weak: alive whenever a listener fires */ + ScrStr *name; /* owned */ + ScrDyn *signal; /* owned; undefined without an abort listener */ + ScrClosure *event_cb; /* weak, NULL once freed */ + ScrClosure *error_cb; /* weak, NULL once freed or for 'error' */ + ScrDyn *listener; /* weak abort listener, NULL once freed */ + bool settled; +} ScrEosState; + +enum { SCR_EOS_EVENT, SCR_EOS_ERROR, SCR_EOS_ABORT }; + +typedef struct ScrEosLink { + size_t rc; + ScrEosState *st; /* owned */ + int slot; +} ScrEosLink; + +static void scr_eos_oom(void) { scr_trap("scriptc: out of memory in events.once\n"); } + +static void scr_eos_state_release(ScrEosState *st) { + if (--st->rc > 0) return; + scr_promise_release(st->promise); + scr_str_release(st->name); + scr_dyn_release(st->signal); + free(st); +} + +static void *scr_eos_link_retain(void *p) { + ((ScrEosLink *)p)->rc++; + return p; +} + +static void scr_eos_link_release(void *p) { + ScrEosLink *link = p; + if (--link->rc > 0) return; + ScrEosState *st = link->st; + if (link->slot == SCR_EOS_EVENT) st->event_cb = NULL; + else if (link->slot == SCR_EOS_ERROR) st->error_cb = NULL; + else st->listener = NULL; + scr_eos_state_release(st); + free(link); +} + +static ScrBox *scr_eos_link_box(ScrEosState *st, int slot) { + ScrEosLink *link = calloc(1, sizeof *link); + if (!link) scr_eos_oom(); + link->rc = 1; + link->st = st; + link->slot = slot; + st->rc++; + ScrBox *box = scr_box_new_obj(&scr_eos_link_retain, &scr_eos_link_release, NULL); + scr_box_set_ref(box, link); + return box; +} + +/* An emitter listener: caps[0] its boxer (the compiled adapter calls it), + * caps[1] its link. */ +static ScrClosure *scr_eos_closure(void *adapter, ScrClosure *boxer, ScrEosState *st, int slot) { + ScrClosure *c = scr_closure_new(adapter, 2); + ScrBox *bx = scr_box_new(SCR_BOX_FUNC); + scr_box_set_ref(bx, scr_closure_retain(boxer)); + c->caps[0] = bx; + c->caps[1] = scr_eos_link_box(st, slot); + return c; +} + +/* new AbortError(undefined, { cause: signal.reason }), thrown. */ +static void scr_eos_throw_abort(ScrDyn *signal) { + ScrStr *message = scr_str_new("The operation was aborted", 25); + ScrError *e = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(message); + scr_str_release(e->name); + e->name = scr_str_new("AbortError", 10); + /* AbortError assigns code, then name: own enumerable properties */ + scr_error_set_code(e, "ABORT_ERR"); + e->name_present = true; + e->name_enumerable = true; + ScrStr *k = scr_str_new("reason", 6); + ScrDyn *reason = scr_dyn_handle_key_get(signal, k); + scr_str_release(k); + scr_error_define_cause(e, reason ? reason : scr_dyn_undefined()); + if (reason) scr_dyn_release(reason); + scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()); +} + +/* removeEventListener('abort', listener) on the signal, when the listener + * is still registered. */ +static void scr_eos_remove_abort(ScrEosState *st) { + if (st->listener == NULL) return; + bool aborted; + ScrEventTargetCore *core = scr_et_signal_core_fn ? scr_et_signal_core_fn(st->signal, &aborted) : NULL; + if (!core) return; + ScrStr *abort = scr_str_new("abort", 5); + ScrDyn *type = scr_dyn_new_str(abort); + scr_str_release(abort); + ScrDyn *listener = scr_dyn_retain(st->listener); + ScrDyn *remove_args[2] = {type, listener}; + ScrDyn *r = scr_et_remove_listener(core, remove_args, 2); + if (r) scr_dyn_release(r); + scr_dyn_release(type); + scr_dyn_release(listener); +} + +/* emitter.removeListener(name, cb) for a listener closure still alive. */ +static bool scr_eos_off(ScrEmitter *em, ScrStr *name, ScrClosure *cb) { + if (cb == NULL) return true; + scr_emitter_release(scr_emitter_off(em, name, cb)); + return !scr_exc_pending(); +} + +/* The compiled adapter's tail: `cb` is the firing emitter listener, `args` + * the boxed event arguments (moves in). */ +void scr_events_once_signal_fire(ScrClosure *cb, ScrArr *args) { + ScrEosLink *link = scr_box_get_ref(cb->caps[1]); + ScrEosState *st = link->st; + if (!st->settled) { + st->settled = true; + bool error = link->slot == SCR_EOS_ERROR; + /* Node's errorListener removes the event's listener and resolver the + * error's, each through removeListener; then the abort listener. */ + bool ok; + if (error) { + ok = scr_eos_off(st->em, st->name, st->event_cb); + } else { + ScrStr *error_name = scr_str_new("error", 5); + ok = scr_eos_off(st->em, error_name, st->error_cb); + scr_str_release(error_name); + } + if (ok) { + scr_eos_remove_abort(st); + if (error) { + ScrDyn *reason = (ScrDyn *)scr_arr_get_ref(args, 0); + scr_dyn_throw(reason); /* moves reason */ + scr_promise_reject_pending(st->promise); + } else { + scr_promise_fulfill_ref(st->promise, scr_arr_retain(args), scr_arr_retain_v, + scr_arr_release_v, args->cyc ? scr_arr_trace_v : NULL); + } + } + } + scr_arr_release(args); + scr_eos_link_release(link); +} + +/* The signal's 'abort' listener: caps[0] its link, caps[1] the emitter. */ +static ScrDyn *scr_eos_abort_thunk(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + (void)args; + (void)argc; + ScrEosLink *link = scr_box_get_ref(clo->caps[0]); + ScrEmitter *em = scr_box_get_ref(clo->caps[1]); + ScrEosState *st = link->st; + bool ok = true; + if (!st->settled) { + st->settled = true; + ok = scr_eos_off(em, st->name, st->event_cb); + if (ok) { + ScrStr *error_name = scr_str_new("error", 5); + ok = scr_eos_off(em, error_name, st->error_cb); + scr_str_release(error_name); + } + if (ok) { + scr_eos_throw_abort(st->signal); + scr_promise_reject_pending(st->promise); + } + } + scr_emitter_release(em); + scr_eos_link_release(link); + return ok ? scr_dyn_retain(scr_dyn_undefined()) : NULL; +} + +static ScrBox *scr_eos_promise_box(ScrPromise *p) { + ScrBox *box = scr_box_new_obj(&scr_promise_retain_v, &scr_promise_release_v, &scr_promise_trace_v); + scr_box_set_ref(box, scr_promise_retain(p)); + return box; +} + +static void scr_eos_adopt_job(ScrClosure *job) { + ScrPromise *outer = scr_box_get_ref(job->caps[0]); + ScrPromise *inner = scr_box_get_ref(job->caps[1]); + scr_promise_race_join(outer, inner, &scr_promise_adapt_copy); + scr_promise_release(inner); + scr_promise_release(outer); +} + +/* Node's once is an async function returning the executor's promise: its + * result adopts that promise through a thenable job queued now, whose + * reaction settles the result one turn after the inner promise settles. + * Moves `inner` in; the result is +1. */ +static ScrPromise *scr_eos_adopt(ScrPromise *inner) { + ScrPromise *outer = scr_promise_new(); + ScrClosure *job = scr_closure_new((void *)&scr_eos_adopt_job, 2); + job->caps[0] = scr_eos_promise_box(outer); + job->caps[1] = scr_eos_promise_box(inner); + scr_promise_release(inner); + scr_queue_microtask(job); + return outer; +} + +/* boxer and err_boxer are borrowed (err_boxer is NULL when the awaited + * event is 'error'); signal is borrowed. The result is +1 and never throws: + * every failure rejects it, as Node's async function does. */ +ScrPromise *scr_events_once_signal(ScrEmitter *em, ScrStr *name, ScrClosure *boxer, void *adapter, + ScrEeInvoke inv, ScrClosure *err_boxer, void *err_adapter, + ScrEeInvoke err_inv, ScrDyn *signal) { + ScrPromise *result = scr_promise_new(); + bool with_signal = signal->kind != SCR_DYN_UNDEF; + if (with_signal) { + bool aborted = false; + ScrEventTargetCore *core = + scr_et_signal_core_fn ? scr_et_signal_core_fn(signal, &aborted) : NULL; + if (!core) { + scr_dyn_prop_type_fail("options.signal", "an instance of AbortSignal", signal); + scr_promise_reject_pending(result); + return result; + } + if (aborted) { + scr_eos_throw_abort(signal); + scr_promise_reject_pending(result); + return result; + } + } + ScrEosState *st = calloc(1, sizeof *st); + if (!st) scr_eos_oom(); + st->rc = 1; + st->promise = result; + st->em = em; + st->name = scr_str_retain(name); + st->signal = scr_dyn_retain(signal); + ScrClosure *event_cb = scr_eos_closure(adapter, boxer, st, SCR_EOS_EVENT); + ScrClosure *error_cb = + err_boxer != NULL ? scr_eos_closure(err_adapter, err_boxer, st, SCR_EOS_ERROR) : NULL; + st->event_cb = event_cb; + st->error_cb = error_cb; + /* Node registers inside the promise's executor: a throwing 'newListener' + * listener rejects the promise, and the listener it announced is never + * added (the event's listener stays when only the 'error' announcement + * throws). No abort listener follows a throw. */ + ScrCaught *thrown = NULL; + scr_emitter_release(scr_emitter_on(em, name, event_cb, inv, true, false)); + if (scr_exc_pending()) { + thrown = scr_exc_take(); + scr_emitter_unadd_quiet(em, name, event_cb); + if (error_cb != NULL) scr_closure_release(error_cb); /* never registered */ + } else if (error_cb != NULL) { + ScrStr *error = scr_str_new("error", 5); + scr_emitter_release(scr_emitter_on(em, error, error_cb, err_inv, true, false)); + if (scr_exc_pending()) { + thrown = scr_exc_take(); + scr_emitter_unadd_quiet(em, error, error_cb); + } + scr_str_release(error); + } + if (thrown != NULL) { + st->settled = true; + scr_rethrow(thrown); + scr_caught_release(thrown); + scr_promise_reject_pending(st->promise); + } else if (with_signal) { + ScrClosure *clo = scr_closure_new((void *)&scr_eos_abort_thunk, 2); + clo->caps[0] = scr_eos_link_box(st, SCR_EOS_ABORT); + clo->caps[1] = scr_box_new_obj(&scr_emitter_retain_v, &scr_emitter_release_v, NULL); + scr_box_set_ref(clo->caps[1], scr_emitter_retain(em)); + ScrDyn *listener = scr_dyn_new_func(clo, &scr_eos_abort_thunk, 0, "()", "abortListener"); + st->listener = listener; + ScrDyn *dispose = scr_events_add_abort_listener(signal, listener); + if (dispose) scr_dyn_release(dispose); + /* The signal's listener entry keeps it; the state's pointer is weak. */ + scr_dyn_release(listener); + } + ScrPromise *inner = scr_promise_retain(st->promise); + scr_eos_state_release(st); + return scr_eos_adopt(inner); +} diff --git a/packages/runtime/src/scr_exception.c b/packages/runtime/src/scr_exception.c index 3a79cf85b..c7ff71349 100644 --- a/packages/runtime/src/scr_exception.c +++ b/packages/runtime/src/scr_exception.c @@ -459,9 +459,10 @@ ScrStr *scr_caught_to_string(const ScrCaught *c) { case SCR_EXC_REF: case SCR_EXC_PRIMITIVE_REF: if (c->retain_fn == scr_dyn_retain_v) return scr_dyn_string_constructor(c->payload); - /* Object.prototype.toString — exact for thrown records and class - * instances without a toString override; thrown arrays/closures/union - * boxes print this too where Node would vary (SEMANTICS.md). */ + /* Object.prototype.toString — exact for thrown class instances + * without a toString override; thrown closures and union boxes print + * this too where Node would vary. Thrown records and arrays arrive as + * checked-dynamic values (above). */ return scr_str_new("[object Object]", 15); case SCR_EXC_NONE: case SCR_EXC_TERMINATE: diff --git a/packages/runtime/src/scr_exception.h b/packages/runtime/src/scr_exception.h index 3c8261561..e99f84243 100644 --- a/packages/runtime/src/scr_exception.h +++ b/packages/runtime/src/scr_exception.h @@ -195,9 +195,10 @@ bool scr_caught_is_object(const ScrCaught *c); /* `String(e)` / `${e}` on a catch binding: JS's String() over the snapshot * — numbers/booleans/strings by value, Error payloads via * scr_error_to_string ("name: message" — String(e) carries no stack in - * Node either), every other ref payload "[object Object]" (exact for - * records and toString-less class instances; a documented approximation - * for thrown arrays/closures/union boxes). Borrows the box; returns +1. */ + * Node either), checked-dynamic payloads (thrown records, arrays and + * unions over them) by String() over the value, every other ref payload + * "[object Object]" (exact for toString-less class instances; an + * approximation for closures and union boxes). Borrows the box; returns +1. */ ScrStr *scr_caught_to_string(const ScrCaught *c); /* `e as C` on a catch binding (scr_lib.c): an OBJ payload inside C's * preorder interval extracts (retained, +1); every other payload THROWS a diff --git a/packages/runtime/src/scr_fetch.c b/packages/runtime/src/scr_fetch.c index 17beb9122..735183b89 100644 --- a/packages/runtime/src/scr_fetch.c +++ b/packages/runtime/src/scr_fetch.c @@ -5179,7 +5179,21 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) { * arrays, and plain objects (with the ordinary JS object coercion * protocol), while null and undefined continue to mean no body. */ ScrDyn *coerced_body = NULL; - if (body_present && body->kind != SCR_DYN_STR && + /* A URLSearchParams body is its serialization, sent as + * application/x-www-form-urlencoded;charset=UTF-8 (extract a body). */ + bool form_body = false; + if (body_present && body->kind == SCR_DYN_HANDLE && + body->v.handle.tag == SCR_DYNH_SEARCH_PARAMS) { + coerced_body = scr_dyn_handle_ops_of(body)->invoke(body->v.handle.ptr, (ScrDyn *)body, + "toString", NULL, 0, "URLSearchParams"); + if (!coerced_body || coerced_body->kind != SCR_DYN_STR) { + scr_dyn_release(coerced_body); + return sf_reject_now(promise, "fetch failed"); + } + body = coerced_body; + form_body = true; + } + if (body_present && !form_body && body->kind != SCR_DYN_STR && body->kind != SCR_DYN_BYTES && !body_stream) { ScrStr *text = scr_dyn_string_coerce_js(body); if (!text) { @@ -5322,7 +5336,10 @@ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init) { /* Fetch Metadata is controlled by the fetch implementation. Undici * replaces a caller-provided sec-fetch-mode with "cors". */ sf_strip_header(&headers, "sec-fetch-mode"); - if (body && body->kind == SCR_DYN_STR && + if (form_body && !sf_pairs_have(headers, "content-type")) { + sf_push_header_text(headers, "content-type", + "application/x-www-form-urlencoded;charset=UTF-8", 47); + } else if (body && body->kind == SCR_DYN_STR && !sf_pairs_have(headers, "content-type")) { sf_push_header_text(headers, "content-type", "text/plain;charset=UTF-8", 24); diff --git a/packages/runtime/src/scr_fs_stream.c b/packages/runtime/src/scr_fs_stream.c index 97bf4ebaa..70a5ab075 100644 --- a/packages/runtime/src/scr_fs_stream.c +++ b/packages/runtime/src/scr_fs_stream.c @@ -562,6 +562,7 @@ static ScrStream *scr_fss_new(const ScrDyn *path, const ScrDyn *opts_in, bool re scr_dyn_release(eo); } if (!s) goto fail; + s->cls = readable ? "ReadStream" : "WriteStream"; bool unknown_encoding = false; if (!readable) { /* setDefaultEncoding(options.encoding) after the Writable constructor: diff --git a/packages/runtime/src/scr_http.c b/packages/runtime/src/scr_http.c index 83a1b3583..d9876c30b 100644 --- a/packages/runtime/src/scr_http.c +++ b/packages/runtime/src/scr_http.c @@ -806,6 +806,8 @@ static bool scr_http_token_char(unsigned char ch) { (ch >= 'a' && ch <= 'z') || (ch != 0 && strchr("!#$%&'*+-.^_`|~", ch) != NULL); } +static void scr_http_throw_invalid_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*borrowed*/); + /* node:http's public validators use the same token and field-vchar rules * as OutgoingMessage. Strings are UTF-8 here, so the Latin-1 obs-text * range accepted by Node's JS regex is C2/C3 plus one continuation byte. */ @@ -814,7 +816,12 @@ void scr_http_validate_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*bo for (size_t i = 0; i < name->len && valid; i++) valid = scr_http_token_char((unsigned char)name->data[i]); if (valid) return; + scr_http_throw_invalid_header_name(name, label); +} +/* Node's ERR_INVALID_HTTP_TOKEN for a header name; `name` is the name's + * string form. */ +static void scr_http_throw_invalid_header_name(ScrStr *name /*borrowed*/, ScrStr *label /*borrowed*/) { ScrJsonBuf b; scr_jb_init(&b); ScrStr *shown = label->len ? label : NULL; @@ -5582,6 +5589,18 @@ static void scr_http_agent_destroy(ScrHttpAgent *a) { } } +/* The socketPath option of the next request built through + * scr_http_request_agent_ex (borrowed; the caller keeps it alive across + * the call), like https's servername: that request dials the Unix socket + * instead of host:port, and the agent keys its sockets by the path, as + * Node's getName does. */ +static SCR_TL ScrStr *scr_http_next_socket_path = NULL; +/* The Unix-socket dialer, set by the runtime options path that sets the + * path; a pointer (not static) so ordinary requests do not link the + * Unix-socket half of the net unit. */ +extern SCR_TL ScrNetSocket *(*scr_http_socket_dialer)(ScrStr *path, bool deferred); +SCR_TL ScrNetSocket *(*scr_http_socket_dialer)(ScrStr *path, bool deferred) = NULL; + ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double port, ScrStr *path /*borrowed*/, ScrStr *method /*borrowed*/, double timeout_ms, ScrArr *header_pairs /*borrowed*/, @@ -5590,6 +5609,8 @@ ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double po ScrHttpRespFn fn, int default_port, void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx, ScrStr *pool_key /*borrowed, nullable*/) { + ScrStr *socket_path = scr_http_next_socket_path; + scr_http_next_socket_path = NULL; /* the null/undefined arms: the scheme's global agent, like Node */ if (agent == NULL || agent->kind == SCR_DYN_UNDEF || agent->kind == SCR_DYN_NULL) agent = scr_http_global_agent(default_port == 443); @@ -5619,8 +5640,10 @@ ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double po scr_arr_push_ref(copy, scr_str_new("close", 5)); pairs = copy; } + ScrNetSocket *presock = socket_path != NULL ? scr_http_socket_dialer(socket_path, false) : NULL; ScrHttpClientReq *c = scr_http_request_impl(host, p, path, method, timeout_ms, pairs, - auto_end, cb, fn, default_port, wrap, wrap_ctx, NULL); + auto_end, cb, fn, default_port, wrap, wrap_ctx, + presock); if (copy) scr_arr_release(copy); return c; } @@ -5636,11 +5659,14 @@ ScrHttpClientReq *scr_http_request_agent_ex(ScrStr *host /*borrowed*/, double po char portbuf[16]; int portn = snprintf(portbuf, sizeof portbuf, "%d", (int)p); ScrStr *name = scr_http_agent_name(host->data, host->len, portbuf, (size_t)portn, - NULL, 0, 0, NULL, 0); + NULL, 0, 0, socket_path ? socket_path->data : NULL, + socket_path ? socket_path->len : 0); /* a free pooled socket for the name serves the request right away */ ScrHttpAgentFree *reuse = ag->keep_alive ? scr_http_agent_free_take(ag, name, pool_key) : NULL; bool queue = reuse == NULL && scr_http_agent_active(ag, name) >= ag->max_sockets; - ScrNetSocket *presock = queue || reuse ? scr_net_connect_deferred(p, host) : NULL; + ScrNetSocket *presock = socket_path != NULL ? scr_http_socket_dialer(socket_path, queue || reuse) + : queue || reuse ? scr_net_connect_deferred(p, host) + : NULL; ScrHttpClientReq *c = scr_http_request_impl(host, p, path, method, timeout_ms, header_pairs, auto_end, cb, fn, default_port, wrap, wrap_ctx, presock); @@ -6968,3 +6994,476 @@ ScrHttpClientReq *scr_http_request_url(ScrStr *url /*borrowed*/, ScrStr *method scr_str_release(path); return c; } + +/* ── http.request(options) with a runtime options record ───────────────── + * + * The options value of http.request/get when the compiler cannot see an + * object literal: a RequestOptions variable, or a literal that spreads one + * or sets socketPath. Node's ClientRequest constructor reads it at run + * time; so does this walk, in Node's validation order and with its error + * shapes. Documented options outside the implemented set throw the + * runtime fence when they carry a value, rather than being ignored; + * undocumented keys are ignored, as Node ignores them. */ + +/* The documented options this walk does not implement. A truthy value + * fences; setHost and setDefaultHeaders fence only when false. */ +static const char *const SCR_HTTP_REQ_FENCED_OPTIONS[] = { + "auth", "createConnection", "family", "hints", "httpValidation", "insecureHTTPParser", + "joinDuplicateHeaders", "localAddress", "localPort", "lookup", "uniqueHeaders", NULL, +}; + +static const char SCR_HTTP_REQ_SUPPORTED[] = + "hostname/host, port, path, method, headers, timeout, agent, signal, socketPath, " + "defaultPort, protocol, and maxHeaderSize are the supported options"; + +/* validateHeaderName and validateHeaderValue over one pair of the flat + * headers array: a name that is not a string throws + * ERR_INVALID_HTTP_TOKEN with its string form, and an array value + * validates each element. */ +static void scr_http_req_validate_header_pair(const ScrDyn *name, const ScrDyn *value) { + ScrStr *label = scr_str_new("", 0); + if (name->kind == SCR_DYN_STR) { + scr_http_validate_header_name(name->v.str, label); + } else { + ScrStr *text = scr_dyn_string_coerce_js(name); + if (text != NULL) { + scr_http_throw_invalid_header_name(text, label); + scr_str_release(text); + } + } + scr_str_release(label); + if (scr_exc_pending()) return; + if (value->kind == SCR_DYN_ARR) { + for (size_t k = 0; k < value->v.arr.len && !scr_exc_pending(); k++) + scr_http_validate_header_value(name->v.str, value->v.arr.items[k]); + } else { + scr_http_validate_header_value(name->v.str, value); + } +} + +/* util.inspect's one-line rendering of a short array ("[ 'x-a', 5 ]"): up + * to six primitive entries whose rendering stays within inspect's + * 80-column break length (a start of n + 11, plus the separators and each + * entry). NULL when inspect would group or break the entries, or an entry + * is not a primitive or renders outside ASCII (inspect counts UTF-16 + * units). */ +static ScrStr *scr_http_inspect_short_array(const ScrDyn *list) { + size_t n = list->v.arr.len; + if (n == 0 || n > 6) return NULL; + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "[ "); + size_t total = 2 * n + 11; + bool fits = true; + for (size_t i = 0; i < n && fits; i++) { + const ScrDyn *v = list->v.arr.items[i]; + ScrStr *part = NULL; + switch (v->kind) { + case SCR_DYN_STR: part = scr_str_inspect_quoted(v->v.str, false); break; + case SCR_DYN_NUM: + part = v->v.num == 0 && signbit(v->v.num) ? scr_str_new("-0", 2) : scr_f64_to_scrstr(v->v.num); + break; + case SCR_DYN_BOOL: part = v->v.b ? scr_str_new("true", 4) : scr_str_new("false", 5); break; + case SCR_DYN_NULL: part = scr_str_new("null", 4); break; + case SCR_DYN_UNDEF: part = scr_str_new("undefined", 9); break; + default: break; + } + if (part == NULL) { + fits = false; + break; + } + for (size_t k = 0; k < part->len && fits; k++) fits = (unsigned char)part->data[k] < 0x80; + total += part->len; + if (total > 80) fits = false; + if (i > 0) scr_jb_puts(&b, ", "); + scr_jb_write(&b, part->data, part->len); + scr_str_release(part); + } + scr_jb_puts(&b, " ]"); + ScrStr *out = scr_jb_finish(&b); + if (fits) return out; + scr_str_release(out); + return NULL; +} + +/* Node's ERR_INVALID_ARG_VALUE for a flat headers array of odd length. + * Arrays whose inspected form scr_http_inspect_short_array cannot + * reproduce fence instead. */ +static void scr_http_req_odd_headers_fail(const char *api, const ScrDyn *list) { + ScrStr *shown = scr_http_inspect_short_array(list); + if (shown == NULL) { + scr_http_req_opts_fence(api, "headers", + "the odd-length headers array error prints up to six short primitive entries here"); + return; + } + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "The argument 'headers' is invalid. Received "); + scr_jb_write(&b, shown->data, shown->len); + scr_str_release(shown); + ScrStr *msg = scr_jb_finish(&b); + scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_INVALID_ARG_VALUE"); + scr_str_release(msg); +} + +void scr_http_req_opts_fence(const char *api, const char *key, const char *hint) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, api); + scr_jb_puts(&b, " option '"); + scr_jb_puts(&b, key); + scr_jb_puts(&b, "' has no static lowering — "); + scr_jb_puts(&b, hint); + scr_throw_error(SCR_ERR_ERROR, scr_jb_finish(&b)); +} + +void scr_http_req_opts_free(ScrHttpReqOpts *o) { + scr_str_release(o->host); + scr_str_release(o->path); + scr_str_release(o->method); + scr_str_release(o->socket_path); + if (o->headers) scr_arr_release(o->headers); + scr_dyn_release(o->agent); + scr_dyn_release(o->signal); + scr_dyn_release(o->view); + memset(o, 0, sizeof *o); +} + +/* A member of the options record, NULL when absent or undefined. */ +static const ScrDyn *scr_http_req_opt(const ScrDyn *opts, const char *key) { + const ScrDyn *v = scr_dyn_obj_get(opts, key, strlen(key)); + return v == NULL || v->kind == SCR_DYN_UNDEF ? NULL : v; +} + +static bool scr_http_req_truthy(const ScrDyn *v) { return v != NULL && scr_dyn_truthy(v); } + +/* validatePort's accepted values: an integer in [0, 65535] or a string + * holding one. NaN when the value is not a valid port. */ +static double scr_http_req_port_value(const ScrDyn *v) { + if (v->kind == SCR_DYN_NUM) { + double n = v->v.num; + return n == n && trunc(n) == n && n >= 0 && n <= 65535 ? n : NAN; + } + if (v->kind == SCR_DYN_STR) { + bool blank = true; + for (size_t i = 0; i < v->v.str->len && blank; i++) + blank = isspace((unsigned char)v->v.str->data[i]) != 0; + if (blank) return NAN; + ScrStr *s = scr_str_retain(v->v.str); + double n = scr_string_to_number(s); + scr_str_release(s); + return n == n && trunc(n) == n && n >= 0 && n <= 65535 ? n : NAN; + } + return NAN; +} + +/* Reads `opts` (borrowed) into `o` in the ClientRequest constructor's + * order. False with the exception pending (`o` holds nothing). */ +bool scr_http_req_opts_read(const ScrDyn *opts, bool secure, const char *api, ScrHttpReqOpts *o) { + memset(o, 0, sizeof *o); + o->port = -1; + o->default_port = secure ? 443 : 80; + if (opts != NULL && opts->kind == SCR_DYN_TYPED_REF) { + o->view = scr_dyn_typed_ref_materialize(opts); + opts = o->view; + } + if (opts == NULL || opts->kind == SCR_DYN_UNDEF || opts->kind == SCR_DYN_NULL) { + opts = NULL; /* Node spreads it: every option takes its default */ + } else if (opts->kind != SCR_DYN_OBJ) { + scr_http_req_opts_fence(api, "options", "pass an options object or a URL string"); + scr_http_req_opts_free(o); + return false; + } +#define OPT(key) (opts != NULL ? scr_http_req_opt(opts, key) : NULL) + /* agent: false, null/undefined (the global agent), or an Agent */ + const ScrDyn *agent = OPT("agent"); + if (agent != NULL && agent->kind != SCR_DYN_NULL && + !(agent->kind == SCR_DYN_BOOL && !agent->v.b) && + !(agent->kind == SCR_DYN_HANDLE && agent->v.handle.tag == SCR_DYNH_HTTP_AGENT)) { + scr_dyn_prop_type_fail("options.agent", "one of Agent-like Object, undefined, or false", agent); + scr_http_req_opts_free(o); + return false; + } + if (agent != NULL) o->agent = scr_dyn_retain((ScrDyn *)agent); + bool agent_secure = secure; + if (agent != NULL && agent->kind == SCR_DYN_HANDLE) + agent_secure = ((ScrHttpAgent *)agent->v.handle.ptr)->secure; + /* path: String(options.path) when truthy, without unescaped characters */ + const ScrDyn *path = OPT("path"); + if (scr_http_req_truthy(path)) { + o->path = scr_dyn_string_coerce_js(path); + if (o->path == NULL) { + scr_http_req_opts_free(o); + return false; + } + bool bad = false; + for (size_t i = 0; i < o->path->len && !bad; i++) { + unsigned char ch = (unsigned char)o->path->data[i]; + if (ch < 0x21) bad = true; + else if (ch >= 0x80) { + /* U+0080..U+00FF (lead byte C2/C3) pass; anything above is unescaped */ + if (ch != 0xc2 && ch != 0xc3) bad = true; + else i++; + } + } + if (bad) { + static const char msg[] = "Request path contains unescaped characters"; + scr_throw_error_msg_code(SCR_ERR_TYPE, msg, sizeof msg - 1, "ERR_UNESCAPED_CHARACTERS"); + scr_http_req_opts_free(o); + return false; + } + } else { + o->path = scr_str_new("/", 1); + } + /* protocol: options.protocol || the agent's, which must match */ + const ScrDyn *protocol = OPT("protocol"); + const char *expected = agent_secure ? "https:" : "http:"; + if (scr_http_req_truthy(protocol)) { + ScrStr *p = scr_dyn_string_coerce_js(protocol); + if (p == NULL) { + scr_http_req_opts_free(o); + return false; + } + if (p->len != strlen(expected) || memcmp(p->data, expected, p->len) != 0) { + char msg[192]; + int n = snprintf(msg, sizeof msg, "Protocol \"%.*s\" not supported. Expected \"%s\"", + (int)(p->len < 64 ? p->len : 64), p->data, expected); + scr_str_release(p); + scr_throw_error_msg_code(SCR_ERR_TYPE, msg, (size_t)n, "ERR_INVALID_PROTOCOL"); + scr_http_req_opts_free(o); + return false; + } + scr_str_release(p); + } + /* defaultPort, then port (validated when the dial is TCP) */ + const ScrDyn *default_port = OPT("defaultPort"); + if (scr_http_req_truthy(default_port) && default_port->kind == SCR_DYN_NUM) + o->default_port = (int)default_port->v.num; + else if (agent != NULL && agent->kind == SCR_DYN_HANDLE) + o->default_port = (int)((ScrHttpAgent *)agent->v.handle.ptr)->default_port; + const ScrDyn *port = OPT("port"); + /* host: validateHost(hostname) || validateHost(host) || 'localhost' */ + static const char *const host_keys[] = { "hostname", "host" }; + for (size_t i = 0; i < 2; i++) { + const ScrDyn *h = OPT(host_keys[i]); + if (h == NULL || h->kind == SCR_DYN_NULL) continue; + if (h->kind != SCR_DYN_STR) { + char name[24]; + snprintf(name, sizeof name, "options.%s", host_keys[i]); + scr_dyn_prop_type_fail(name, "of type string or one of undefined or null", h); + scr_http_req_opts_free(o); + return false; + } + if (o->host == NULL && h->v.str->len > 0) o->host = scr_str_retain(h->v.str); + } + if (o->host == NULL) o->host = scr_str_new("localhost", 9); + const ScrDyn *set_host = OPT("setHost"); + if (set_host != NULL && set_host->kind == SCR_DYN_BOOL && !set_host->v.b) { + scr_http_req_opts_fence(api, "setHost", "the Host header is always sent — leave setHost at its true default"); + scr_http_req_opts_free(o); + return false; + } + const ScrDyn *set_default = OPT("setDefaultHeaders"); + if (set_default != NULL && set_default->kind == SCR_DYN_BOOL && !set_default->v.b) { + scr_http_req_opts_fence(api, "setDefaultHeaders", "the default headers are always sent — leave setDefaultHeaders at its true default"); + scr_http_req_opts_free(o); + return false; + } + const ScrDyn *socket_path = OPT("socketPath"); + /* timeout: getTimerDuration(options.timeout, 'timeout') */ + const ScrDyn *timeout = OPT("timeout"); + if (timeout != NULL) { + if (timeout->kind != SCR_DYN_NUM) { + scr_dyn_arg_type_fail("timeout", "of type number", timeout); + scr_http_req_opts_free(o); + return false; + } + double t = timeout->v.num; + if (!(t >= 0 && isfinite(t))) { + char recv[48], msg[192]; + scr_num_received(t, recv); + int n = snprintf(msg, sizeof msg, + "The value of \"timeout\" is out of range. It must be a non-negative finite number. Received %s", + recv); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)n, "ERR_OUT_OF_RANGE"); + scr_http_req_opts_free(o); + return false; + } + o->timeout = t; + } + /* method: a string token, upper-cased; GET when absent or empty */ + const ScrDyn *method = OPT("method"); + if (method != NULL && method->kind != SCR_DYN_NULL && method->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.method", "of type string", method); + scr_http_req_opts_free(o); + return false; + } + if (method != NULL && method->kind == SCR_DYN_STR && method->v.str->len > 0) { + o->method = scr_str_new(method->v.str->data, method->v.str->len); + for (size_t i = 0; i < o->method->len; i++) { + if (!scr_http_token_char((unsigned char)o->method->data[i])) { + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "Method must be a valid HTTP token [\""); + scr_jb_write(&b, o->method->data, o->method->len); + scr_jb_puts(&b, "\"]"); + ScrStr *msg = scr_jb_finish(&b); + scr_throw_error_msg_code(SCR_ERR_TYPE, msg->data, msg->len, "ERR_INVALID_HTTP_TOKEN"); + scr_str_release(msg); + scr_http_req_opts_free(o); + return false; + } + } + for (size_t i = 0; i < o->method->len; i++) { + unsigned char ch = (unsigned char)o->method->data[i]; + if (ch >= 'a' && ch <= 'z') o->method->data[i] = (char)(ch - 'a' + 'A'); + } + } else { + o->method = scr_str_new("GET", 3); + } + const ScrDyn *max_header = OPT("maxHeaderSize"); + if (max_header != NULL) { + if (max_header->kind != SCR_DYN_NUM) { + scr_dyn_arg_type_fail("maxHeaderSize", "of type number", max_header); + scr_http_req_opts_free(o); + return false; + } + o->max_header_size = max_header->v.num; + o->has_max_header_size = true; + } + for (size_t i = 0; SCR_HTTP_REQ_FENCED_OPTIONS[i] != NULL; i++) { + if (scr_http_req_truthy(OPT(SCR_HTTP_REQ_FENCED_OPTIONS[i]))) { + scr_http_req_opts_fence(api, SCR_HTTP_REQ_FENCED_OPTIONS[i], SCR_HTTP_REQ_SUPPORTED); + scr_http_req_opts_free(o); + return false; + } + } + /* headers: an object (String(number) values, arrays as repeated lines) + * or the flat [name, value, ...] array */ + const ScrDyn *headers = OPT("headers"); + ScrDyn *headers_view = NULL; + if (headers != NULL && headers->kind == SCR_DYN_TYPED_REF) { + headers_view = scr_dyn_typed_ref_materialize(headers); + headers = headers_view; + } + if (headers != NULL && headers->kind == SCR_DYN_OBJ) { + for (size_t i = 0; i < headers->v.obj.len; i++) { + const ScrDynEntry *e = &headers->v.obj.entries[i]; + ScrStr *name = scr_str_new(e->key, e->key_len); + ScrStr *label = scr_str_new("", 0); + scr_http_validate_header_name(name, label); + scr_str_release(label); + const ScrDyn *v = e->value; + if (!scr_exc_pending()) { + if (v->kind == SCR_DYN_ARR) { + for (size_t k = 0; k < v->v.arr.len && !scr_exc_pending(); k++) + scr_http_validate_header_value(name, v->v.arr.items[k]); + } else { + scr_http_validate_header_value(name, v); + } + } + scr_str_release(name); + if (scr_exc_pending()) break; + } + if (!scr_exc_pending()) o->headers = scr_http_dynh_pairs(headers); + } else if (headers != NULL && headers->kind == SCR_DYN_ARR) { + /* Node's _storeHeader checks that the flat [name, value, ...] form has + * an even length, then validates each name and value. The + * [[name, value], ...] entry form is not modeled. */ + size_t n = headers->v.arr.len; + if (n > 0 && headers->v.arr.items[0]->kind == SCR_DYN_ARR) { + scr_http_req_opts_fence( + api, "headers", + "headers is an object of string or number values or a flat [name, value, ...] array here"); + } else if (n % 2 != 0) { + scr_http_req_odd_headers_fail(api, headers); + } else { + for (size_t i = 0; i < n && !scr_exc_pending(); i += 2) + scr_http_req_validate_header_pair(headers->v.arr.items[i], headers->v.arr.items[i + 1]); + } + if (!scr_exc_pending()) o->headers = scr_http_dynh_flat_pairs(headers); + } else if (headers != NULL && headers->kind != SCR_DYN_NULL) { + scr_http_req_opts_fence(api, "headers", "headers is an object of string or number values here"); + } + scr_dyn_release(headers_view); + if (scr_exc_pending()) { + scr_http_req_opts_free(o); + return false; + } + if (o->headers == NULL) o->headers = scr_arr_new(SCR_ELEM_STR, 0); + const ScrDyn *signal = OPT("signal"); + if (signal != NULL) o->signal = scr_dyn_retain((ScrDyn *)signal); + /* the connection: a Unix socket (net validates the path) or host:port */ + if (scr_http_req_truthy(socket_path)) { + if (socket_path->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.path", "of type string", socket_path); + scr_http_req_opts_free(o); + return false; + } + o->socket_path = scr_str_retain(socket_path->v.str); + if (scr_http_req_truthy(port)) { + double n = scr_http_req_port_value(port); + if (n == n) o->port = n; + } + } else if (scr_http_req_truthy(port)) { + double n = scr_http_req_port_value(port); + if (n != n) { + char detail[96], msg[192]; + const char *d = scr_dyn_specific_type(port, detail, sizeof detail); + int len = snprintf(msg, sizeof msg, "Port should be >= 0 and < 65536. Received %s.", d); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_SOCKET_BAD_PORT"); + scr_http_req_opts_free(o); + return false; + } + o->port = n; + } +#undef OPT + return true; +} + +/* Builds the request `o` describes (its references stay with `o`). The + * signal is checked before construction and attached after it, like the + * literal lowering's sequence. +1, or NULL with the exception pending. */ +ScrHttpClientReq *scr_http_req_opts_send(ScrHttpReqOpts *o, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener), + void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx, + ScrStr *pool_key /*borrowed, nullable*/) { + if (o->signal != NULL) { + scr_http_client_signal_check(o->signal); + if (scr_exc_pending()) { + if (cb) scr_closure_release(cb); + return NULL; + } + } + /* without a port option the agent's default port applies (< 0), and + * without an agent the scheme's */ + double port = o->port >= 0 ? o->port : o->agent != NULL && o->agent->kind == SCR_DYN_HANDLE ? -1 + : o->default_port; + scr_http_next_socket_path = o->socket_path; + scr_http_socket_dialer = &scr_net_connect_path; + ScrHttpClientReq *c = scr_http_request_agent_ex( + o->host, port, o->path, o->method, o->timeout, o->headers, auto_end, o->agent, cb, fn, + o->default_port, wrap, wrap_ctx, pool_key); + if (c == NULL) return NULL; + if (o->has_max_header_size) { + scr_http_client_max_header_size(c, o->max_header_size); + if (scr_exc_pending()) return c; + } + if (o->signal != NULL) scr_http_client_add_abort_signal(c, o->signal, add_abort); + return c; +} + +/* http.request/http.get(options[, cb]) with a runtime options record. */ +ScrHttpClientReq *scr_http_request_dyn(const ScrDyn *opts /*borrowed*/, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener)) { + ScrHttpReqOpts o; + if (!scr_http_req_opts_read(opts, false, "http.request", &o)) { + if (cb) scr_closure_release(cb); + return NULL; + } + ScrHttpClientReq *c = scr_http_req_opts_send(&o, auto_end, cb, fn, add_abort, NULL, NULL, NULL); + scr_http_req_opts_free(&o); + return c; +} diff --git a/packages/runtime/src/scr_http.h b/packages/runtime/src/scr_http.h index a4dcc44ce..654362c06 100644 --- a/packages/runtime/src/scr_http.h +++ b/packages/runtime/src/scr_http.h @@ -371,6 +371,40 @@ ScrHttpClientReq *scr_http_request_agent(ScrStr *host /*borrowed*/, double port, double timeout_ms, ScrArr *header_pairs /*borrowed*/, bool auto_end, const ScrDyn *agent /*borrowed*/, ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn); /* +1 */ +/* http.request/get with a runtime options record (a RequestOptions + * variable, or a literal spreading one or setting socketPath). The walk + * reads the record in the ClientRequest constructor's order; https adds + * its TLS options (scr_tls.c) and sends through the same path. */ +typedef struct ScrHttpReqOpts { + ScrStr *host; /* +1: hostname || host || "localhost" */ + double port; /* < 0: no port option */ + int default_port; /* defaultPort, the agent's, or the scheme's */ + ScrStr *path; /* +1 */ + ScrStr *method; /* +1, upper-cased */ + double timeout; /* 0 = none */ + ScrArr *headers; /* +1 name/value pairs */ + ScrDyn *agent; /* +1, NULL = the global agent */ + ScrDyn *signal; /* +1, NULL = none */ + ScrStr *socket_path; /* +1, NULL = dial host:port */ + double max_header_size; + bool has_max_header_size; + ScrDyn *view; /* +1 materialized typed record, if any */ +} ScrHttpReqOpts; +bool scr_http_req_opts_read(const ScrDyn *opts /*borrowed*/, bool secure, const char *api, + ScrHttpReqOpts *o); /* false = exception pending */ +void scr_http_req_opts_free(ScrHttpReqOpts *o); +void scr_http_req_opts_fence(const char *api, const char *key, const char *hint); /* throws */ +ScrHttpClientReq *scr_http_req_opts_send(ScrHttpReqOpts *o, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener), + void (*wrap)(ScrNetSocket *, void *), void *wrap_ctx, + ScrStr *pool_key /*borrowed, nullable*/); /* +1 */ +ScrHttpClientReq *scr_http_request_dyn(const ScrDyn *opts /*borrowed*/, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener)); /* +1 */ +ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener)); /* +1 */ #ifdef SCR_RC_AUDIT long scr_http_live_count(void); #endif diff --git a/packages/runtime/src/scr_inspect.c b/packages/runtime/src/scr_inspect.c index 3194528d3..c1673891b 100644 --- a/packages/runtime/src/scr_inspect.c +++ b/packages/runtime/src/scr_inspect.c @@ -823,6 +823,41 @@ static ScrStr *scr_insp_array_buffer(const ScrBytes *h, const char *cls, double return out; } +/* DataView: formatValue's `DataView { [byteLength], [byteOffset], + * [buffer] }`, the buffer rendered one level deeper. `[DataView]` beyond + * depth. */ +static ScrStr *scr_insp_data_view(const ScrBytes *b, double recurse, double depth) { + if (recurse > depth) return scr_str_new("[DataView]", 10); + const ScrBytes *owner = b->backing ? b->backing : b; + char line[64]; + scr_insp_begin(recurse + 1); + int n = snprintf(line, sizeof line, "[byteLength]: %.0f", scr_bytes_byte_len(b)); + ScrStr *entry = scr_str_new(line, (size_t)n); + scr_insp_entry(entry, false); + scr_str_release(entry); + n = snprintf(line, sizeof line, "[byteOffset]: %.0f", scr_bytes_byte_offset(b)); + entry = scr_str_new(line, (size_t)n); + scr_insp_entry(entry, false); + scr_str_release(entry); + ScrStr *buffer = scr_insp_array_buffer(owner, owner->shared ? "SharedArrayBuffer" : "ArrayBuffer", + recurse + 1, depth); + InspBuf eb = {0}; + ib_cstr(&eb, "[buffer]: "); + ib_bytes(&eb, buffer->data, buffer->len); + scr_str_release(buffer); + entry = ib_take(&eb); + scr_insp_entry(entry, false); + scr_str_release(entry); + ScrStr *base = scr_str_new("", 0); + ScrStr *b0 = scr_str_new("DataView {", 10); + ScrStr *b1 = scr_str_new("}", 1); + ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, false, false); + scr_str_release(base); + scr_str_release(b0); + scr_str_release(b1); + return out; +} + static ScrStr *scr_insp_dyn_error(ScrDyn *d, double recurse, double depth); /* A runtime error through its node: the cached one, or one built from its @@ -1062,6 +1097,16 @@ static ScrStr *scr_insp_class_tag(const char *cls, const char *tail) { return ib_take(&out); } +/* Node prints a @@toStringTag that ends with "." and the constructor name + * (WebAssembly.Module for Module) in place of the name, without the + * bracketed tag. */ +static bool insp_tag_names_class(const char *tag, const char *cls) { + if (!tag) return false; + size_t tag_len = strlen(tag), cls_len = strlen(cls); + return tag_len > cls_len + 1 && tag[tag_len - cls_len - 1] == '.' && + memcmp(tag + tag_len - cls_len, cls, cls_len) == 0; +} + /* A native Set or Map held as a checked-dynamic value: `Set(n) { a, b }`, * `Map(n) { k => v }`, at most 100 entries like Node's maxArrayLength. */ /* Node's formatWeakCollection: the entries of a WeakMap or WeakSet are not @@ -1362,20 +1407,35 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { case SCR_DYN_STR: return scr_insp_str(d->v.str); case SCR_DYN_ARR: { - if (d->v.arr.len == 0) return scr_str_new("[]", 2); + size_t len = d->v.arr.len; + if (len == 0) return scr_str_new("[]", 2); if (recurse > depth) return scr_str_new("[Array]", 7); scr_insp_begin(recurse + 1); - size_t shown = d->v.arr.len < 100 ? d->v.arr.len : 100; - for (size_t i = 0; i < shown; i++) { - ScrDyn *item = d->v.arr.items[i]; + /* Node's maxArrayLength (100) counts output entries; a run of holes + * is one `` entry (formatSpecialArray). */ + const unsigned char *presence = d->v.arr.presence; + size_t i = 0; + for (size_t shown = 0; i < len && shown < 100; shown++) { + if (presence && !presence[i]) { + size_t next = i + 1; + while (next < len && !presence[next]) next++; + char hole[48]; + int hn = snprintf(hole, sizeof hole, "<%zu empty item%s>", next - i, next - i == 1 ? "" : "s"); + ScrStr *s = scr_str_new(hole, (size_t)hn); + scr_insp_entry(s, false); + scr_str_release(s); + i = next; + continue; + } + ScrDyn *item = d->v.arr.items[i++]; ScrStr *s = scr_insp_dyn(item, recurse + 1, depth); scr_insp_entry(s, item->kind == SCR_DYN_NUM); scr_str_release(s); } - bool more = d->v.arr.len > 100; + bool more = i < len; if (more) { - ScrStr *m = scr_insp_more_items((double)(d->v.arr.len - 100)); - scr_insp_entry(m, d->v.arr.items[100]->kind == SCR_DYN_NUM); + ScrStr *m = scr_insp_more_items((double)(len - i)); + scr_insp_entry(m, d->v.arr.items[i]->kind == SCR_DYN_NUM); scr_str_release(m); } ScrStr *base = scr_str_new("", 0); @@ -1445,11 +1505,15 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { ScrStr *body = scr_insp_dyn(view, recurse, depth); scr_dyn_release(view); InspBuf out = {0}; - ib_cstr(&out, ops->cls); - if (ops->to_string_tag) { - ib_cstr(&out, " ["); + if (insp_tag_names_class(ops->to_string_tag, ops->cls)) { ib_cstr(&out, ops->to_string_tag); - ib_char(&out, ']'); + } else { + ib_cstr(&out, ops->cls); + if (ops->to_string_tag) { + ib_cstr(&out, " ["); + ib_cstr(&out, ops->to_string_tag); + ib_char(&out, ']'); + } } ib_char(&out, ' '); ib_bytes(&out, body->data, body->len); @@ -1527,6 +1591,7 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) { } case SCR_DYN_BYTES: { ScrBytes *b = d->v.bytes; + if (b->is_data_view) return scr_insp_data_view(b, recurse, depth); if (d->buffer) { /* Buffer-flavored (a stream chunk): Node's form. */ return scr_insp_buffer(b); diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 83b56bea4..9746e8eb1 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -1585,8 +1585,12 @@ static void scr_dyn_obj_put(ScrDyn *obj, const char *key, size_t key_len, ScrDyn } if (obj->non_extensible) { scr_dyn_release(value); - static const char message[] = "Cannot add property, object is not extensible"; - scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Cannot add property "); + scr_jb_write(&buffer, key, key_len); + scr_jb_puts(&buffer, ", object is not extensible"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); return; } if (obj->v.obj.len == obj->v.obj.cap) { @@ -1922,9 +1926,40 @@ ScrDyn *scr_dyn_set_prototype(ScrDyn *object, ScrDyn *prototype) { return scr_dyn_retain(object); } +/* A class instance's integrity state lives in its native layout, which only + * statically typed calls can reach (the compiler adds the state to the + * hierarchies they name). Through an untyped value the operation throws. */ +static bool scr_dyn_class_integrity_fence(const ScrDyn *object, const char *operation) { + if (object->kind != SCR_DYN_TYPED_REF) return false; + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, operation); + scr_jb_puts(&buffer, " of a class instance held in an untyped value is not supported yet"); + ScrStr *message = scr_jb_finish(&buffer); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); + return true; +} + +/* Integrity queries on a class instance answer from its checked view, which + * carries the instance's attributes (scr_dyn_class_view_attributes). */ +static bool scr_dyn_typed_ref_view_answer(ScrDyn *object, bool (*query)(const ScrDyn *), + bool *answer) { + if (object->kind != SCR_DYN_TYPED_REF) return false; + ScrDyn *view = scr_dyn_typed_ref_materialize(object); + *answer = scr_exc_pending() ? false : query(view); + scr_dyn_release(view); + return true; +} + +static bool scr_dyn_is_extensible_view(const ScrDyn *view) { + return (view->kind == SCR_DYN_OBJ || view->kind == SCR_DYN_ARR) && !view->non_extensible; +} + ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) { if (scr_dyn_class_reflection_fence(object)) return NULL; if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.preventExtensions"); return NULL; } + if (scr_dyn_class_integrity_fence(object, "Object.preventExtensions")) return NULL; if (object->kind == SCR_DYN_OBJ || object->kind == SCR_DYN_ARR) { object->non_extensible = true; if (object->kind == SCR_DYN_ARR && object->v.arr.properties) @@ -1936,12 +1971,15 @@ ScrDyn *scr_dyn_prevent_extensions(ScrDyn *object) { bool scr_dyn_is_extensible(ScrDyn *object) { if (scr_dyn_class_reflection_fence(object)) return false; if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.isExtensible"); return false; } - return (object->kind == SCR_DYN_OBJ || object->kind == SCR_DYN_ARR) && !object->non_extensible; + bool answer; + if (scr_dyn_typed_ref_view_answer(object, scr_dyn_is_extensible_view, &answer)) return answer; + return scr_dyn_is_extensible_view(object); } ScrDyn *scr_dyn_seal(ScrDyn *object) { if (scr_dyn_class_reflection_fence(object)) return NULL; if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.seal"); return NULL; } + if (scr_dyn_class_integrity_fence(object, "Object.seal")) return NULL; if (object->symbol_properties) scr_dyn_release(scr_dyn_seal(object->symbol_properties)); if (object->kind == SCR_DYN_OBJ) { object->non_extensible = true; @@ -1958,6 +1996,8 @@ ScrDyn *scr_dyn_seal(ScrDyn *object) { bool scr_dyn_is_sealed(const ScrDyn *object) { if (scr_dyn_class_reflection_fence(object)) return false; if (object->kind == SCR_DYN_PROXY) { scr_dyn_proxy_unsupported("Object.isSealed"); return false; } + bool answer; + if (scr_dyn_typed_ref_view_answer((ScrDyn *)object, scr_dyn_is_sealed, &answer)) return answer; if (object->symbol_properties && !scr_dyn_is_sealed(object->symbol_properties)) return false; if (object->kind == SCR_DYN_ARR) return object->non_extensible && (object->v.arr.len == 0 || object->v.arr.sealed) && @@ -2122,6 +2162,70 @@ static bool scr_dyn_array_proto_name(const ScrStr *key) { return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); } +/* Own string keys of %TypedArray%.prototype, plus BYTES_PER_ELEMENT from + * each typed array's own prototype. */ +static bool scr_dyn_typed_array_proto_name(const ScrStr *key) { + static const char *const names[] = { + "constructor", "buffer", "byteLength", "byteOffset", "length", "entries", "keys", "values", + "at", "copyWithin", "every", "fill", "filter", "find", "findIndex", "findLast", + "findLastIndex", "forEach", "includes", "indexOf", "join", "lastIndexOf", "map", "reverse", + "reduce", "reduceRight", "set", "slice", "some", "sort", "subarray", "toReversed", "toSorted", + "with", "toLocaleString", "toString", "BYTES_PER_ELEMENT" + }; + return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); +} + +/* Own string keys of Buffer.prototype that %TypedArray%.prototype lacks. */ +static bool scr_dyn_buffer_proto_name(const ScrStr *key) { + static const char *const names[] = { + "readBigUInt64LE", "readBigUInt64BE", "readBigUint64LE", "readBigUint64BE", "readBigInt64LE", + "readBigInt64BE", "writeBigUInt64LE", "writeBigUInt64BE", "writeBigUint64LE", + "writeBigUint64BE", "writeBigInt64LE", "writeBigInt64BE", "readUIntLE", "readUInt32LE", + "readUInt16LE", "readUInt8", "readUIntBE", "readUInt32BE", "readUInt16BE", "readUintLE", + "readUint32LE", "readUint16LE", "readUint8", "readUintBE", "readUint32BE", "readUint16BE", + "readIntLE", "readInt32LE", "readInt16LE", "readInt8", "readIntBE", "readInt32BE", + "readInt16BE", "writeUIntLE", "writeUInt32LE", "writeUInt16LE", "writeUInt8", "writeUIntBE", + "writeUInt32BE", "writeUInt16BE", "writeUintLE", "writeUint32LE", "writeUint16LE", + "writeUint8", "writeUintBE", "writeUint32BE", "writeUint16BE", "writeIntLE", "writeInt32LE", + "writeInt16LE", "writeInt8", "writeIntBE", "writeInt32BE", "writeInt16BE", "readFloatLE", + "readFloatBE", "readDoubleLE", "readDoubleBE", "writeFloatLE", "writeFloatBE", "writeDoubleLE", + "writeDoubleBE", "asciiSlice", "base64Slice", "base64urlSlice", "latin1Slice", "hexSlice", + "ucs2Slice", "utf8Slice", "asciiWrite", "base64Write", "base64urlWrite", "latin1Write", + "hexWrite", "ucs2Write", "utf8Write", "parent", "offset", "copy", "equals", "inspect", + "compare", "write", "toJSON", "swap16", "swap32", "swap64" + }; + return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); +} + +/* Own string keys of DataView.prototype. */ +static bool scr_dyn_data_view_proto_name(const ScrStr *key) { + static const char *const names[] = { + "constructor", "buffer", "byteLength", "byteOffset", "getInt8", "setInt8", "getUint8", + "setUint8", "getInt16", "setInt16", "getUint16", "setUint16", "getInt32", "setInt32", + "getUint32", "setUint32", "getFloat32", "setFloat32", "getFloat64", "setFloat64", + "getBigInt64", "setBigInt64", "getBigUint64", "setBigUint64", "getFloat16", "setFloat16" + }; + return scr_dyn_name_in(key, names, sizeof names / sizeof names[0]); +} + +/* `in` on a typed array, Buffer or DataView. On a typed array, a canonical + * numeric key (one that ToString(ToNumber(key)) reproduces, or "-0") + * answers by bounds alone: an integral index, not -0, below the length. + * Other keys name members of %TypedArray%.prototype, Buffer.prototype for a + * Buffer, or Object.prototype. A DataView has no indexed elements: its keys + * name members of DataView.prototype or Object.prototype. */ +static bool scr_dyn_bytes_in(const ScrDyn *v, const ScrStr *key) { + if (v->v.bytes->is_data_view) return scr_dyn_data_view_proto_name(key) || scr_dyn_object_proto_name(key); + if (key->len == 2 && memcmp(key->data, "-0", 2) == 0) return false; + double index = scr_string_to_number(key); + ScrStr *canonical = scr_f64_to_scrstr(index); + bool numeric = canonical->len == key->len && memcmp(canonical->data, key->data, key->len) == 0; + scr_str_release(canonical); + if (numeric) return index >= 0 && index == floor(index) && index < (double)v->v.bytes->len; + return scr_dyn_typed_array_proto_name(key) || (v->buffer && scr_dyn_buffer_proto_name(key)) || + scr_dyn_object_proto_name(key); +} + static bool scr_dyn_object_proto_key(const ScrDyn *target, const ScrStr *key) { return !target->null_proto && scr_dyn_object_proto_name(key); } @@ -3456,10 +3560,19 @@ ScrDyn *scr_bytes_construct(const ScrDyn *callee, const ScrDyn *args, const ScrS /* Shared view metadata and indexed reads for both compiler backends. */ ScrDyn *scr_dyn_bytes_key_get(const ScrDyn *value, const ScrStr *key) { ScrBytes *bytes = value->v.bytes; - if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return scr_dyn_new_num(scr_bytes_len(bytes)); if (key->len == 10 && memcmp(key->data, "byteLength", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_len(bytes)); if (key->len == 10 && memcmp(key->data, "byteOffset", 10) == 0) return scr_dyn_new_num(scr_bytes_byte_offset(bytes)); if (key->len == 6 && memcmp(key->data, "buffer", 6) == 0) return scr_array_buffer_from_bytes(bytes); + if (bytes->is_data_view) { + /* A DataView has no length or indexed elements. */ + if (key->len == 11 && memcmp(key->data, "constructor", 11) == 0) { + static const char message[] = "DataView's constructor through unknown is not supported yet"; + scr_throw_error_msg(SCR_ERR_ERROR, message, sizeof message - 1); + return NULL; + } + return scr_dyn_retain(scr_dyn_undefined()); + } + if (key->len == 6 && memcmp(key->data, "length", 6) == 0) return scr_dyn_new_num(scr_bytes_len(bytes)); if (key->len == 11 && memcmp(key->data, "constructor", 11) == 0) { if (!value->buffer) return scr_bytes_constructor_for(bytes->elem); /* Buffer retains its separate builtin identity. */ @@ -4243,6 +4356,50 @@ void scr_dyn_obj_set(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value scr_dyn_obj_put(obj, key, key_len, value); } +/* The getter (or, with `setter`, the setter) of the own accessor property + * `key`: +1, or the undefined value when the property is not an accessor + * or has none. */ +ScrDyn *scr_dyn_obj_accessor_fn(const ScrDyn *obj, const char *key, size_t key_len, bool setter) { + const ScrDynEntry *entry = obj->kind == SCR_DYN_OBJ ? scr_dyn_find_entry(obj, key, key_len) : NULL; + ScrDyn *fn = entry && entry->accessor ? (setter ? entry->setter : entry->getter) : NULL; + return scr_dyn_retain(fn ? fn : scr_dyn_undefined()); +} + +/* An object literal's get/set accessor property on a fresh copy: + * enumerable and configurable, with the given getter and setter functions + * (either NULL when the literal declares none). Moves both. */ +void scr_dyn_obj_set_accessor(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *getter, ScrDyn *setter) { + scr_dyn_obj_put(obj, key, key_len, scr_dyn_retain(scr_dyn_undefined())); + ScrDynEntry *entry = scr_dyn_find_entry(obj, key, key_len); + if (!entry) { + scr_dyn_release(getter); + scr_dyn_release(setter); + return; + } + entry->accessor = true; + entry->getter = getter; + entry->setter = setter; +} + +/* A class instance's view takes its native field values whatever the + * attributes copied into it (a frozen instance's view is built the same way + * as any other). Consumes value. */ +void scr_dyn_obj_set_field(ScrDyn *obj, const char *key, size_t key_len, ScrDyn *value) { + ScrDynEntry *existing = obj->v.obj.index ? scr_dyn_find_entry(obj, key, key_len) + : scr_dyn_find_linear_entry(obj, key, key_len); + if (existing && !existing->accessor) { + scr_dyn_region_store(obj, value); + ScrDyn *old = existing->value; + existing->value = value; + scr_dyn_release(old); + return; + } + bool non_extensible = obj->non_extensible; + obj->non_extensible = false; + scr_dyn_obj_put(obj, key, key_len, value); + obj->non_extensible = non_extensible; +} + static ScrStr *scr_dyn_property_key(const ScrDyn *key) { return scr_dyn_string_coerce_js(key); } @@ -4271,6 +4428,7 @@ static void scr_dyn_descriptor_fields_drop(ScrDyn **fields) { } static bool scr_dyn_key_is_index(const char *key, size_t len, double *out); static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key); +static void scr_error_sync_slots(const ScrDyn *view, ScrDyn *obj, const ScrStr *key); /* Property tables belong to closures, not temporary checked wrappers. Seed * the configurable, non-writable builtin members once so redefinitions and @@ -6104,9 +6262,12 @@ ScrDyn *scr_dyn_define_property(ScrDyn *target, ScrDyn *key, ScrDyn *descriptor) (!accessor && ((!prior->writable && writable) || (!prior->writable && value && !scr_dyn_property_same_value(prior->value, value)))))) { scr_dyn_descriptor_fields_drop(fields); + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Cannot redefine property: "); + scr_jb_write(&buffer, name->data, name->len); scr_str_release(name); - static const char msg[] = "Cannot redefine property"; - scr_throw_error_msg(SCR_ERR_TYPE, msg, sizeof msg - 1); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); return NULL; } if (prior) { @@ -6286,6 +6447,28 @@ void scr_dyn_throw(ScrDyn *d) { scr_dyn_release(d); return; } + /* A checked-dynamic primitive throws as the primitive itself, as a + * statically typed `throw 5` does: catch bindings test and extract the + * same snapshot kinds (`typeof e === "number"`), and a primitive has no + * identity to keep. */ + if (d->kind == SCR_DYN_NUM) { + double value = d->v.num; + scr_dyn_release(d); + scr_throw_f64(value); + return; + } + if (d->kind == SCR_DYN_BOOL) { + bool value = d->v.b; + scr_dyn_release(d); + scr_throw_bool(value); + return; + } + if (d->kind == SCR_DYN_STR) { + ScrStr *value = scr_str_retain(d->v.str); + scr_dyn_release(d); + scr_throw_str(value); + return; + } scr_throw_ref_classified(d, scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v, scr_dyn_is_object(d)); } @@ -6364,7 +6547,7 @@ ScrStr *scr_dyn_object_tag(const ScrDyn *d) { ScrJsonBuf b; scr_jb_init(&b); scr_jb_puts(&b, "[object "); - scr_jb_puts(&b, scr_bytes_elem_name(d->v.bytes->elem)); + scr_jb_puts(&b, d->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(d->v.bytes->elem)); scr_jb_putc(&b, ']'); return scr_jb_finish(&b); } @@ -6537,6 +6720,10 @@ ScrStr *scr_dyn_format_j(const ScrDyn *d) { typedef struct { ScrError *err; /* retained (pins the address — no pointer reuse) */ ScrDyn *dyn; /* retained */ + /* An own `code` that is not a string (`err.code = undefined`), retained, + * or NULL. The code slot holds only strings, and a subclass's typed view + * rebuilds its properties from the slots on every read. */ + ScrDyn *code; } ScrErrDynEnt; static SCR_TL ScrErrDynEnt *scr_errdyn_cache = NULL; @@ -6640,6 +6827,7 @@ static void scr_errdyn_sweep(void) { for (size_t i = 0; i < n_dead; i++) { scr_error_release(dead[i].err); scr_errdyn_release_node(dead[i].dyn); + scr_dyn_release(dead[i].code); } free(dead); } @@ -6660,6 +6848,7 @@ static void scr_errdyn_append(ScrError *e, ScrDyn *d) { } scr_errdyn_cache[scr_errdyn_n].err = scr_error_retain(e); scr_errdyn_cache[scr_errdyn_n].dyn = scr_dyn_retain(d); + scr_errdyn_cache[scr_errdyn_n].code = NULL; scr_errdyn_n++; if (scr_errdyn_n * 2 > scr_errdyn_slots) { free(scr_errdyn_by_err); @@ -6715,18 +6904,116 @@ static void scr_errdyn_teardown(void) { for (size_t i = 0; i < n; i++) { scr_error_release(cache[i].err); scr_errdyn_release_node(cache[i].dyn); + scr_dyn_release(cache[i].code); } free(cache); } +/* An error's own `stack`: a non-enumerable accessor shared by every error, + * as V8 defines it. The getter formats the stack on first access, so its + * heading is the name and message at that point, and later reads answer + * the same string. Both functions find the error through the receiver or + * its prototype chain; on any other receiver the getter answers undefined + * and the setter changes nothing but the receiver's own accessor. */ +static SCR_TL ScrDyn *scr_error_stack_accessors[2]; +static SCR_TL bool scr_error_stack_accessors_registered; + +static void scr_error_stack_accessors_cleanup(void) { + for (size_t i = 0; i < 2; i++) { + scr_dyn_release(scr_error_stack_accessors[i]); + scr_error_stack_accessors[i] = NULL; + } + scr_error_stack_accessors_registered = false; +} + +/* The native error behind an accessor receiver. +1 or NULL. */ +static ScrError *scr_error_stack_holder(const ScrDyn *receiver) { + for (const ScrDyn *current = receiver; current; current = scr_dyn_proto_next(current)) { + ScrError *error = scr_errdyn_err_of(current); + if (error) return error; + if (current->kind != SCR_DYN_OBJ) return NULL; + } + return NULL; +} + +static ScrDyn *scr_error_stack_get_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; (void)args; (void)argc; + ScrDyn *self = scr_dyn_this_get(); + ScrError *error = scr_error_stack_holder(self); + scr_dyn_release(self); + if (!error) return scr_dyn_retain(scr_dyn_undefined()); + ScrStr *stack = scr_error_stack(error); + scr_error_release(error); + /* A name or message getter that throws leaves its exception pending. */ + if (!stack) return NULL; + ScrDyn *out = scr_dyn_new_str(stack); + scr_str_release(stack); + return out; +} + +static ScrDyn *scr_error_stack_set_call(ScrClosure *closure, ScrDyn *const *args, size_t argc); + +/* Whether an own property is an error's `stack` accessor. */ +static bool scr_dyn_entry_is_error_stack(const ScrDynEntry *entry) { + return entry->accessor && entry->setter && entry->setter->kind == SCR_DYN_FUNC && + entry->setter->v.fn.thunk == scr_error_stack_set_call; +} + +static ScrDyn *scr_error_stack_set_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { + (void)closure; + ScrDyn *self = scr_dyn_this_get(); + ScrError *error = scr_error_stack_holder(self); + ScrDyn *value = argc > 0 ? args[0] : scr_dyn_undefined(); + if (error && value->kind == SCR_DYN_STR) { + scr_error_set_stack(error, value->v.str); + } else if (self->kind == SCR_DYN_OBJ) { + /* The native slot holds strings only, and a typed reference's + * snapshot names no error: the receiver's own accessor becomes a data + * property holding the value, which a snapshot's commit stores. */ + ScrStr *key = scr_str_new("stack", 5); + ScrDynEntry *entry = scr_dyn_entry(self, key); + scr_str_release(key); + if (entry && scr_dyn_entry_is_error_stack(entry)) { + ScrDyn *old_value = entry->value, *old_getter = entry->getter, *old_setter = entry->setter; + entry->value = scr_dyn_retain(value); + entry->getter = entry->setter = NULL; + entry->accessor = false; + entry->writable = true; + scr_dyn_release(old_value); + scr_dyn_release(old_getter); + scr_dyn_release(old_setter); + } + } + scr_error_release(error); + scr_dyn_release(self); + return scr_dyn_retain(scr_dyn_undefined()); +} + +static void scr_error_dyn_add_stack(ScrDyn *d) { + if (!scr_error_stack_accessors_registered) { + scr_error_stack_accessors_registered = true; + scr_atexit(scr_error_stack_accessors_cleanup); + } + if (!scr_error_stack_accessors[0]) { + scr_error_stack_accessors[0] = + scr_dyn_new_func(scr_closure_new(NULL, 0), scr_error_stack_get_call, 0, "native:error.stack", NULL); + scr_error_stack_accessors[1] = + scr_dyn_new_func(scr_closure_new(NULL, 0), scr_error_stack_set_call, 1, "native:error.stack", NULL); + } + scr_dyn_obj_set(d, "stack", 5, scr_dyn_retain(scr_dyn_undefined())); + ScrDynEntry *entry = &d->v.obj.entries[d->v.obj.len - 1]; + entry->accessor = true; + entry->getter = scr_dyn_retain(scr_error_stack_accessors[0]); + entry->setter = scr_dyn_retain(scr_error_stack_accessors[1]); + entry->enumerable = false; +} + ScrDyn *scr_error_dyn_fields(const ScrError *e) { ScrDyn *d = scr_dyn_new_obj(); scr_dyn_obj_set(d, "%error", 6, scr_dyn_new_bool(true)); /* the checked-dynamic tree's error marker */ d->v.obj.entries[d->v.obj.len - 1].enumerable = false; - if (e->name_present) { - scr_dyn_obj_set(d, "name", 4, scr_dyn_new_str(e->name)); - d->v.obj.entries[d->v.obj.len - 1].enumerable = e->name_enumerable; - } + /* Node lists `stack` first among an error's own properties. */ + scr_error_dyn_add_stack(d); if (!e->name_present || !e->message_present) { ScrDyn *prototype = scr_dyn_new_obj(); if (!e->name_present) { @@ -6744,11 +7031,6 @@ ScrDyn *scr_error_dyn_fields(const ScrError *e) { scr_dyn_obj_set(d, "message", 7, scr_dyn_new_str(e->message)); d->v.obj.entries[d->v.obj.len - 1].enumerable = e->message_enumerable; } - if (e->stack) { - /* Node's own `stack` is non-enumerable. */ - scr_dyn_obj_set(d, "stack", 5, scr_dyn_new_str(e->stack)); - d->v.obj.entries[d->v.obj.len - 1].enumerable = false; - } if (e->system_message) { /* SystemError: code, the read-only `info` context ({ message, path, * syscall, errno, code }), then errno, syscall, path and dest. */ @@ -6774,7 +7056,13 @@ ScrDyn *scr_error_dyn_fields(const ScrError *e) { /* errno rides fs errors (with syscall) and zlib errors (without). */ if (e->system_call || e->system_errno != 0) scr_dyn_obj_set(d, "errno", 5, scr_dyn_new_num(e->system_errno)); - if (e->code) scr_dyn_obj_set(d, "code", 4, scr_dyn_new_str(e->code)); + if (e->code) { + scr_dyn_obj_set(d, "code", 4, scr_dyn_new_str(e->code)); + } else { + size_t hit = scr_errdyn_find(e, true); + if (hit != SIZE_MAX && scr_errdyn_cache[hit].code != NULL) + scr_dyn_obj_set(d, "code", 4, scr_dyn_retain(scr_errdyn_cache[hit].code)); + } if (e->system_call) scr_dyn_obj_set(d, "syscall", 7, scr_dyn_new_str(e->system_call)); } if (!e->errno_order && e->system_path) scr_dyn_obj_set(d, "path", 4, scr_dyn_new_str(e->system_path)); @@ -6807,9 +7095,37 @@ ScrDyn *scr_error_dyn_fields(const ScrError *e) { scr_dyn_obj_set(d, "errors", 6, scr_aggregate_errors((ScrError *)e)); d->v.obj.entries[d->v.obj.len - 1].enumerable = false; } + /* An own `name` is assigned after construction defines the others. */ + if (e->name_present) { + scr_dyn_obj_set(d, "name", 4, scr_dyn_new_str(e->name)); + d->v.obj.entries[d->v.obj.len - 1].enumerable = e->name_enumerable; + } return d; } +/* Moves an Error subclass view's own `name` to follow the property + * `after`, or every other property when `after` is NULL: the position + * the class's construction creates it at. Nothing changes when the view + * has no own `name` or no `after` property. */ +void scr_error_dyn_place_name(ScrDyn *view, const char *after, size_t after_len) { + if (view->kind != SCR_DYN_OBJ) return; + ScrDynEntry *entries = view->v.obj.entries; + size_t n = view->v.obj.len, from = n, to = n; + for (size_t i = 0; i < n; i++) { + if (entries[i].key_len == 4 && memcmp(entries[i].key, "name", 4) == 0) from = i; + else if (after && entries[i].key_len == after_len && memcmp(entries[i].key, after, after_len) == 0) to = i; + } + if (from == n || (after && to == n)) return; + size_t at = after ? (to > from ? to : to + 1) : n - 1; + if (at == from) return; + ScrDynEntry name = entries[from]; + memmove(&entries[from], &entries[from + 1], (n - from - 1) * sizeof *entries); + memmove(&entries[at + 1], &entries[at], (n - 1 - at) * sizeof *entries); + entries[at] = name; + free(view->v.obj.index); + view->v.obj.index = NULL; +} + ScrDyn *scr_dyn_from_error(const ScrError *e) { size_t hit = scr_errdyn_find(e, true); if (hit != SIZE_MAX) return scr_dyn_retain(scr_errdyn_cache[hit].dyn); @@ -6948,18 +7264,35 @@ ScrError *scr_errdyn_err_of(const ScrDyn *d) { void scr_errdyn_put(ScrError *e, ScrDyn *d) { scr_errdyn_append(e, d); } -/* Error.cause has one live value even when the Error already crossed a - * checked-value boundary. Reflective data-property edits update its slot. */ -static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key) { +/* An Error's slots (name, message, stack, cause, code) have one live value + * even when the Error already crossed a checked-value boundary: writes and + * deletes of those own properties through its checked-dynamic node update + * the slots typed reads use. `view` is the error's node; `obj` holds the + * properties (the node itself, or a typed view's materialized object). */ +static void scr_error_sync_slots(const ScrDyn *view, ScrDyn *obj, const ScrStr *key) { bool cause = key->len == 5 && memcmp(key->data, "cause", 5) == 0; bool stack = key->len == 5 && memcmp(key->data, "stack", 5) == 0; bool name = key->len == 4 && memcmp(key->data, "name", 4) == 0; bool message = key->len == 7 && memcmp(key->data, "message", 7) == 0; - if (!cause && !stack && !name && !message) return; + bool code = key->len == 4 && memcmp(key->data, "code", 4) == 0; + if (!cause && !stack && !name && !message && !code) return; + if (obj == NULL || obj->kind != SCR_DYN_OBJ) return; size_t hit = scr_errdyn_find(view, false); if (hit == SIZE_MAX) return; - ScrDynEntry *entry = scr_dyn_entry(view, key); + ScrDynEntry *entry = scr_dyn_entry(obj, key); ScrError *e = scr_errdyn_cache[hit].err; + if (code) { + /* A string takes the slot; any other value clears it and stays an own + * property, which the entry keeps for views rebuilt from the slots. */ + bool string = entry && !entry->accessor && entry->value->kind == SCR_DYN_STR; + ScrStr *next = string ? scr_str_retain(entry->value->v.str) : NULL; + scr_str_release(e->code); + e->code = next; + ScrDyn *other = entry && !string && !entry->accessor ? scr_dyn_retain(entry->value) : NULL; + scr_dyn_release(scr_errdyn_cache[hit].code); + scr_errdyn_cache[hit].code = other; + return; + } if (name || message) { /* The native error's toString and inspect read these fields. */ if (!entry || entry->accessor || entry->value->kind != SCR_DYN_STR) return; @@ -6990,6 +7323,10 @@ static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key) { e->cause_enumerable = entry && entry->enumerable; } +static void scr_error_sync_cause(ScrDyn *view, const ScrStr *key) { + scr_error_sync_slots(view, view, key); +} + /* Error.prototype.toString over the checked-dynamic error encoding (an * object carrying the reserved "%error" marker): `name` defaults to "Error" * and `message` to "", both read through the prototype; either empty leaves @@ -7059,6 +7396,8 @@ ScrStr *scr_dyn_to_string(const ScrDyn *d, const ScrStr *enc) { return d->v.symbol.render(d->v.symbol.value); case SCR_DYN_BYTES: if (d->buffer) return scr_bytes_to_str(d->v.bytes, enc); + /* DataView has no toString of its own: Object.prototype.toString. */ + if (d->v.bytes->is_data_view) return scr_str_new("[object DataView]", 17); { ScrStr *separator = scr_str_new(",", 1); ScrStr *out = scr_bytes_join(d->v.bytes, separator); @@ -7911,6 +8250,7 @@ bool scr_dyn_in(const ScrDyn *v, const ScrStr *key) { return out; } if (v->kind == SCR_DYN_PROXY) return scr_dyn_proxy_has(v, key); + if (v->kind == SCR_DYN_BYTES) return scr_dyn_bytes_in(v, key); if (scr_dyn_has_key(v, key)) return true; if (scr_exc_pending()) return false; if (v->kind == SCR_DYN_OBJ) return scr_dyn_builtin_inherited(v, key); @@ -8018,7 +8358,12 @@ void scr_dyn_key_set(ScrDyn *recv, ScrStr *key, ScrDyn *value) { if (scr_event_ref_hooks && scr_event_ref_hooks->set(recv, key, value)) return; ScrDyn *materialized = scr_dyn_typed_ref_materialize(recv); scr_dyn_key_set(materialized, key, value); - if (!scr_exc_pending()) scr_dyn_typed_ref_commit(recv); + if (!scr_exc_pending()) { + scr_dyn_typed_ref_commit(recv); + /* An Error subclass's class commit writes its declared fields; the + * slots it inherits from Error take the write here. */ + scr_error_sync_slots(recv, materialized, key); + } scr_dyn_release(materialized); return; } @@ -8211,7 +8556,7 @@ static const char *scr_dyn_kind_name(const ScrDyn *d) { case SCR_DYN_ARR: return "array"; case SCR_DYN_OBJ: return "object"; case SCR_DYN_UNDEF: return "undefined"; - case SCR_DYN_BYTES: return scr_bytes_elem_name(d->v.bytes->elem); + case SCR_DYN_BYTES: return d->v.bytes->is_data_view ? "DataView" : scr_bytes_elem_name(d->v.bytes->elem); case SCR_DYN_FUNC: return "function"; case SCR_DYN_HANDLE: return scr_dyn_handle_cls(d); /* "got IncomingMessage" */ case SCR_DYN_PROMISE: return "Promise"; /* "got Promise" */ @@ -9792,7 +10137,10 @@ void scr_dyn_key_delete(ScrDyn *recv, const ScrStr *key, bool strict) { scr_dyn_entry(materialized, key) != NULL; if (!scr_exc_pending()) scr_dyn_key_delete(materialized, key, strict); bool deleted = own && !scr_exc_pending(); - if (!scr_exc_pending()) scr_dyn_typed_ref_commit(recv); + if (!scr_exc_pending()) { + scr_dyn_typed_ref_commit(recv); + scr_error_sync_slots(recv, materialized, key); + } scr_dyn_release(materialized); if (deleted && scr_exc_pending()) { /* The native layout requires this field, so the commit rejected the @@ -10860,6 +11208,17 @@ static ScrDyn *scr_sc_clone(const ScrDyn *v, const ScrScParent *up, ScrDynRegion ScrDyn *out = scr_dyn_new_obj(); for (size_t i = 0; i < v->v.obj.len; i++) { const ScrDynEntry *e = &v->v.obj.entries[i]; + if (scr_dyn_entry_is_error_stack(e)) { + /* A stack that was already formatted copies as a non-enumerable + * data property; an unformatted one has nothing to copy. */ + ScrError *error = scr_errdyn_err_of(v); + if (error && error->stack) { + scr_dyn_obj_set(out, "stack", 5, scr_dyn_new_str(error->stack)); + out->v.obj.entries[out->v.obj.len - 1].enumerable = false; + } + scr_error_release(error); + continue; + } ScrDyn *c = scr_sc_clone(e->value, &self, region); if (c == NULL) { scr_dyn_release(out); @@ -11383,7 +11742,20 @@ ScrDyn *scr_dyn_get_own_property_names(const ScrDyn *value) { scr_dyn_release(table); return out; } - if (value->kind == SCR_DYN_OBJ) return scr_dyn_obj_own_keys(value); + if (value->kind == SCR_DYN_OBJ) { + ScrDyn *keys = scr_dyn_obj_own_keys(value); + if (!scr_dyn_obj_get(value, "%error", 6)) return keys; + /* The checked-dynamic tree's error marker is not a property. */ + ScrDyn *names = scr_dyn_new_arr(); + scr_dyn_arr_reserve(names, keys->v.arr.len); + for (size_t i = 0; i < keys->v.arr.len; i++) { + const ScrStr *key = keys->v.arr.items[i]->v.str; + if (key->len == 6 && memcmp(key->data, "%error", 6) == 0) continue; + scr_dyn_arr_push(names, scr_dyn_retain(keys->v.arr.items[i])); + } + scr_dyn_release(keys); + return names; + } if (value->kind == SCR_DYN_ARR || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BYTES) { ScrDyn *keys = scr_dyn_new_arr(); size_t length = value->kind == SCR_DYN_ARR ? value->v.arr.len : @@ -12149,7 +12521,17 @@ ScrDyn *scr_dyn_assign(ScrDyn *target, const ScrDyn *src) { if (target->kind == SCR_DYN_TYPED_REF) { ScrDyn *materialized = scr_dyn_typed_ref_materialize(target); ScrDyn *assigned = scr_dyn_assign(materialized, src); - if (!scr_exc_pending()) scr_dyn_typed_ref_commit(target); + if (!scr_exc_pending()) { + scr_dyn_typed_ref_commit(target); + /* An Error subclass's inherited slots take the copied values. */ + static const char *const slots[] = { "name", "message", "stack", "cause", "code" }; + for (size_t i = 0; i < sizeof slots / sizeof slots[0]; i++) { + ScrStr *key = scr_str_new(slots[i], strlen(slots[i])); + if (materialized->kind == SCR_DYN_OBJ && scr_dyn_entry(materialized, key) != NULL) + scr_error_sync_slots(target, materialized, key); + scr_str_release(key); + } + } scr_dyn_release(assigned); scr_dyn_release(materialized); return scr_exc_pending() ? NULL : scr_dyn_retain(target); @@ -13955,6 +14337,7 @@ ScrStr *scr_dyn_to_string_method(const ScrDyn *d, const ScrStr *enc, const ScrSt ScrDyn *scr_dyn_freeze(ScrDyn *value) { if (scr_dyn_class_reflection_fence(value)) return NULL; + if (scr_dyn_class_integrity_fence(value, "Object.freeze")) return NULL; if (value->symbol_properties) scr_dyn_release(scr_dyn_freeze(value->symbol_properties)); if (value->kind == SCR_DYN_ARR) { value->non_extensible = true; @@ -13982,6 +14365,8 @@ ScrDyn *scr_dyn_freeze(ScrDyn *value) { bool scr_dyn_is_frozen(const ScrDyn *value) { if (scr_dyn_class_reflection_fence(value)) return false; + bool answer; + if (scr_dyn_typed_ref_view_answer((ScrDyn *)value, scr_dyn_is_frozen, &answer)) return answer; if (value->symbol_properties && !scr_dyn_is_frozen(value->symbol_properties)) return false; if (value->kind == SCR_DYN_ARR) return scr_dyn_is_sealed(value) && (value->v.arr.len == 0 || value->v.arr.frozen) && @@ -14036,6 +14421,130 @@ ScrDyn *scr_caught_to_dyn(const ScrCaught *c) { } } +/* ── class instance attributes ─────────────────────────────────────────── + * A hierarchy passed to Object.freeze, Object.seal, Object.preventExtensions + * or Object.defineProperty keeps an integrity state per instance and an + * attribute word per redefined field (CLASS_* and FIELD_* bits in the + * compiler's ir.ts). The checked view of an instance carries them, so + * reflection over the view answers as Node does. */ +enum { + SCR_CLASS_NOT_EXTENSIBLE = 1, + SCR_CLASS_SEALED = 2, + SCR_CLASS_FROZEN = 4, + SCR_FIELD_NOT_WRITABLE = 1, + SCR_FIELD_NOT_ENUMERABLE = 2, + SCR_FIELD_NOT_CONFIGURABLE = 4, +}; + +void scr_dyn_class_field_attributes(ScrDyn *view, ScrStr *name, double bits) { + unsigned b = (unsigned)bits; + if (b == 0 || view->kind != SCR_DYN_OBJ) return; + ScrDynEntry *entry = scr_dyn_entry(view, name); + if (!entry) return; + if (b & SCR_FIELD_NOT_WRITABLE) entry->writable = false; + if (b & SCR_FIELD_NOT_ENUMERABLE) entry->enumerable = false; + if (b & SCR_FIELD_NOT_CONFIGURABLE) entry->configurable = false; +} + +void scr_dyn_class_view_attributes(ScrDyn *view, double bits) { + unsigned b = (unsigned)bits; + if (b == 0 || view->kind != SCR_DYN_OBJ) return; + view->non_extensible = true; + for (size_t i = 0; i < view->v.obj.len; i++) { + ScrDynEntry *entry = &view->v.obj.entries[i]; + if (b & (SCR_CLASS_SEALED | SCR_CLASS_FROZEN)) entry->configurable = false; + if ((b & SCR_CLASS_FROZEN) && !entry->accessor) entry->writable = false; + } +} + +/* Object.defineProperty on a declared class field. The descriptor applies to + * a one-property object holding the field's current value and attributes, + * so validation and errors match ordinary objects. Answers that object (+1), + * or NULL with a pending exception. */ +ScrDyn *scr_class_define_field(ScrDyn *current, double field_bits, double object_bits, ScrStr *name, + ScrDyn *descriptor) { + unsigned f = (unsigned)field_bits, o = (unsigned)object_bits; + ScrDyn *holder = scr_dyn_new_obj(); + scr_dyn_obj_set(holder, name->data, name->len, scr_dyn_retain(current)); + ScrDynEntry *entry = scr_dyn_entry(holder, name); + entry->writable = !(f & SCR_FIELD_NOT_WRITABLE) && !(o & SCR_CLASS_FROZEN); + entry->enumerable = !(f & SCR_FIELD_NOT_ENUMERABLE); + entry->configurable = + !(f & SCR_FIELD_NOT_CONFIGURABLE) && !(o & (SCR_CLASS_SEALED | SCR_CLASS_FROZEN)); + ScrDyn *key = scr_dyn_new_str(name); + ScrDyn *defined = scr_dyn_define_property(holder, key, descriptor); + scr_dyn_release(key); + if (!defined) { + scr_dyn_release(holder); + return NULL; + } + scr_dyn_release(defined); + entry = scr_dyn_entry(holder, name); + if (entry->accessor) { + scr_dyn_release(holder); + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Object.defineProperty with an accessor descriptor for the declared field '"); + scr_jb_write(&buffer, name->data, name->len); + scr_jb_puts(&buffer, "' is not supported yet"); + ScrStr *message = scr_jb_finish(&buffer); + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + scr_str_release(message); + return NULL; + } + return holder; +} + +/* The field attribute word of a scr_class_define_field result. */ +double scr_class_defined_bits(ScrDyn *holder, ScrStr *name) { + const ScrDynEntry *entry = scr_dyn_entry(holder, name); + unsigned bits = 0; + if (!entry->writable) bits |= SCR_FIELD_NOT_WRITABLE; + if (!entry->enumerable) bits |= SCR_FIELD_NOT_ENUMERABLE; + if (!entry->configurable) bits |= SCR_FIELD_NOT_CONFIGURABLE; + return (double)bits; +} + +/* The value a scr_class_define_field result holds (+1). */ +ScrDyn *scr_class_defined_value(ScrDyn *holder, ScrStr *name) { + return scr_dyn_retain(scr_dyn_entry(holder, name)->value); +} + +/* Whether a property descriptor names a value (Node's ToPropertyDescriptor + * reads `value` with [[HasProperty]]). */ +bool scr_class_descriptor_has_value(ScrDyn *descriptor) { + if (descriptor->kind == SCR_DYN_TYPED_REF) { + ScrDyn *view = scr_dyn_typed_ref_materialize(descriptor); + bool has = !scr_exc_pending() && scr_class_descriptor_has_value(view); + scr_dyn_release(view); + return has; + } + return descriptor->kind == SCR_DYN_OBJ && scr_dyn_obj_get(descriptor, "value", 5) != NULL; +} + +/* Node's TypeError for a write to a read-only field of a class instance. */ +void scr_class_throw_read_only(ScrStr *field, ScrStr *owner) { + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Cannot assign to read only property '"); + scr_jb_write(&buffer, field->data, field->len); + scr_jb_puts(&buffer, "' of object '#<"); + scr_jb_write(&buffer, owner->data, owner->len); + scr_jb_puts(&buffer, ">'"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); +} + +/* Node's TypeError for a class field initializer on a non-extensible + * instance (a base constructor froze or sealed `this`). */ +void scr_class_throw_not_extensible(ScrStr *field) { + ScrJsonBuf buffer; + scr_jb_init(&buffer); + scr_jb_puts(&buffer, "Cannot define property "); + scr_jb_write(&buffer, field->data, field->len); + scr_jb_puts(&buffer, ", object is not extensible"); + scr_throw_error(SCR_ERR_TYPE, scr_jb_finish(&buffer)); +} + /* ── compiled generators through untyped values ─────────────────────── */ static SCR_TL ScrDyn *scr_generator_next_getter; @@ -14147,3 +14656,80 @@ ScrDyn *scr_dyn_iter_take(const ScrDyn *src, size_t n, const char *spell) { bool scr_dyn_iterates_by_protocol(const ScrDyn *value) { return scr_dyn_sync_generator(value); } + +/* The program's writes onto compiled errors (scr_error.h). Kept at the end + * of the file, after every function they call, so the rest of the unit + * emits in its existing order. */ + +/* `err.code = s` from the program (an Error asserted to carry `code`): + * the same slot every coded throw stamps, so reads and `'code' in err` + * observe the write. Borrows both. */ +void scr_error_set_code_value(ScrError *e, ScrStr *code) { + scr_str_release(e->code); + e->code = scr_str_retain(code); + /* An error that already crossed into the checked-dynamic tree renders + * and reads through that node, so the write lands there too. */ + ScrDyn *view = scr_errdyn_view_of(e); + if (view != NULL && view->kind == SCR_DYN_OBJ) scr_dyn_obj_set(view, "code", 4, scr_dyn_new_str(code)); +} + +void scr_error_set_code_dyn(ScrError *e, ScrDyn *value) { + if (value != NULL && value->kind == SCR_DYN_STR) { + scr_error_set_code_value(e, value->v.str); + return; + } + scr_str_release(e->code); + e->code = NULL; + ScrDyn *view = scr_dyn_from_error(e); + ScrStr *key = scr_str_new("code", 4); + scr_dyn_key_set(view, key, value); + scr_str_release(key); + scr_dyn_release(view); +} + +/* Object.assign(err, ...sources) onto a compiled error: each source's own + * members land on the error's checked-dynamic node, then the slots that + * typed reads use (name, message, stack, cause, code) take the result. + * Borrows both; throws what the copy throws (a nullish source is skipped, + * as in JS). */ +void scr_error_assign_dyn(ScrError *e, const ScrDyn *sources) { + ScrDyn *view = scr_dyn_from_error(e); + ScrDyn *r = scr_dyn_assign_all(view, sources); + if (r != NULL) { + scr_dyn_release(r); + if (view->kind == SCR_DYN_OBJ) { + scr_error_commit_dyn(e, view); + if (!scr_exc_pending()) { + ScrDyn *code = scr_dyn_obj_get(view, "code", 4); + ScrStr *next = code != NULL && code->kind == SCR_DYN_STR ? scr_str_retain(code->v.str) : NULL; + scr_str_release(e->code); + e->code = next; + } + } + } + scr_dyn_release(view); +} + +/* `'code' in err`: the code slot holds a string, or the error has an own + * `code` property with another value (`err.code = undefined`), which lives + * on its checked-dynamic node. Borrows; never throws. */ +bool scr_error_has_code(ScrError *e) { + if (e->code != NULL) return true; + const ScrDyn *view = scr_errdyn_view_of(e); + if (view == NULL) return false; + ScrStr *key = scr_str_new("code", 4); + bool own = scr_dyn_has_own(view, key); + scr_str_release(key); + return own; +} + +/* `key in err` for a key no compiled class of the receiver declares: an own + * property set at runtime, which lives on the error's checked-dynamic node + * (that node exists once anything reflected or wrote through it), or a + * member of Object.prototype. Borrows both; never throws. */ +bool scr_error_has_key(ScrError *e, const ScrStr *key) { + const ScrDyn *view = scr_errdyn_view_of(e); + if (view != NULL && view->kind == SCR_DYN_OBJ && scr_dyn_find_entry(view, key->data, key->len) != NULL) + return true; + return scr_dyn_object_proto_name(key); +} diff --git a/packages/runtime/src/scr_lib.c b/packages/runtime/src/scr_lib.c index 930d44818..99f0128c9 100644 --- a/packages/runtime/src/scr_lib.c +++ b/packages/runtime/src/scr_lib.c @@ -2037,6 +2037,19 @@ double scr_process_exit_code_or_zero(void) { return (double)scr_exit_code_hint_get(); } +static SCR_TL bool scr_exit_code_assigned = false; + +void scr_process_set_exit_code_tracked(double code) { + int converted; + if (!scr_process_exit_code_of(code, &converted)) return; + scr_exit_code_note(converted); + scr_exit_code_assigned = true; +} + +bool scr_process_has_exit_code(void) { + return scr_exit_code_assigned || scr_exit_code_hint_get() != 0; +} + #if defined(__linux__) #include #include @@ -7263,45 +7276,112 @@ static ScrStr *scr_digest_encode(const unsigned char *d, size_t n, int enc) { /* ── MD5 (RFC 1321) ───────────────────────────────────────────────── */ -static const uint32_t scr_md5_k[64] = { - 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, - 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, - 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340, - 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, - 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, - 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, - 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa, - 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, - 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, - 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, - 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; -static const unsigned char scr_md5_r[64] = { - 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; +/* The 64 steps are unrolled with their message word, constant and + * rotation inlined. a + m + K does not depend on the previous step, so + * each step's critical path is the round function of b, an add, the + * rotate and the add of b. G is written as (b & d) + (c & ~d) (the two + * terms share no bits) so that its c & ~d half joins that early sum. The + * empty asm statement keeps the compiler from moving the constant's add + * after the round function, which puts it back on the critical path. */ +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__wasm__) +#define SCR_MD5_PIN(v) __asm__("" : "+r"(v)) +#else +#define SCR_MD5_PIN(v) ((void)0) +#endif +#define SCR_MD5_F(b, c, d) ((d) ^ ((b) & ((c) ^ (d)))) +#define SCR_MD5_H(b, c, d) ((b) ^ (c) ^ (d)) +#define SCR_MD5_I(b, c, d) ((c) ^ ((b) | ~(d))) +#define SCR_MD5_STEP(f, a, b, c, d, xk, s) \ + do { \ + (a) += (xk); \ + SCR_MD5_PIN(a); \ + (a) += f((b), (c), (d)); \ + (a) = ((a) << (s) | (a) >> (32 - (s))) + (b); \ + } while (0) +#define SCR_MD5_GSTEP(a, b, c, d, xk, s) \ + do { \ + (a) += ((c) & ~(d)) + (xk); \ + SCR_MD5_PIN(a); \ + (a) += (b) & (d); \ + (a) = ((a) << (s) | (a) >> (32 - (s))) + (b); \ + } while (0) -SCR_DIGEST_ALIGNED static void scr_md5_block(uint32_t h[4], const unsigned char *p) { - uint32_t m[16]; - for (int i = 0; i < 16; i++) { - m[i] = (uint32_t)p[i * 4] | ((uint32_t)p[i * 4 + 1] << 8) | - ((uint32_t)p[i * 4 + 2] << 16) | ((uint32_t)p[i * 4 + 3] << 24); - } +SCR_DIGEST_ALIGNED static void scr_md5_blocks(uint32_t h[4], const unsigned char *p, size_t blocks) { uint32_t a = h[0], b = h[1], c = h[2], d = h[3]; - for (int i = 0; i < 64; i++) { - uint32_t f, g; - if (i < 16) { f = (b & c) | (~b & d); g = (uint32_t)i; } - else if (i < 32) { f = (d & b) | (~d & c); g = (5u * i + 1) & 15; } - else if (i < 48) { f = b ^ c ^ d; g = (3u * i + 5) & 15; } - else { f = c ^ (b | ~d); g = (7u * i) & 15; } - uint32_t t = d; - d = c; - c = b; - uint32_t x = a + f + scr_md5_k[i] + m[g]; - b = b + ((x << scr_md5_r[i]) | (x >> (32 - scr_md5_r[i]))); - a = t; + for (; blocks != 0; blocks--, p += 64) { + uint32_t m[16]; + for (int i = 0; i < 16; i++) { + m[i] = (uint32_t)p[i * 4] | ((uint32_t)p[i * 4 + 1] << 8) | + ((uint32_t)p[i * 4 + 2] << 16) | ((uint32_t)p[i * 4 + 3] << 24); + } + uint32_t a0 = a, b0 = b, c0 = c, d0 = d; + SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[0] + 0xd76aa478u, 7); + SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[1] + 0xe8c7b756u, 12); + SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[2] + 0x242070dbu, 17); + SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[3] + 0xc1bdceeeu, 22); + SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[4] + 0xf57c0fafu, 7); + SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[5] + 0x4787c62au, 12); + SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[6] + 0xa8304613u, 17); + SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[7] + 0xfd469501u, 22); + SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[8] + 0x698098d8u, 7); + SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[9] + 0x8b44f7afu, 12); + SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[10] + 0xffff5bb1u, 17); + SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[11] + 0x895cd7beu, 22); + SCR_MD5_STEP(SCR_MD5_F, a, b, c, d, m[12] + 0x6b901122u, 7); + SCR_MD5_STEP(SCR_MD5_F, d, a, b, c, m[13] + 0xfd987193u, 12); + SCR_MD5_STEP(SCR_MD5_F, c, d, a, b, m[14] + 0xa679438eu, 17); + SCR_MD5_STEP(SCR_MD5_F, b, c, d, a, m[15] + 0x49b40821u, 22); + SCR_MD5_GSTEP(a, b, c, d, m[1] + 0xf61e2562u, 5); + SCR_MD5_GSTEP(d, a, b, c, m[6] + 0xc040b340u, 9); + SCR_MD5_GSTEP(c, d, a, b, m[11] + 0x265e5a51u, 14); + SCR_MD5_GSTEP(b, c, d, a, m[0] + 0xe9b6c7aau, 20); + SCR_MD5_GSTEP(a, b, c, d, m[5] + 0xd62f105du, 5); + SCR_MD5_GSTEP(d, a, b, c, m[10] + 0x02441453u, 9); + SCR_MD5_GSTEP(c, d, a, b, m[15] + 0xd8a1e681u, 14); + SCR_MD5_GSTEP(b, c, d, a, m[4] + 0xe7d3fbc8u, 20); + SCR_MD5_GSTEP(a, b, c, d, m[9] + 0x21e1cde6u, 5); + SCR_MD5_GSTEP(d, a, b, c, m[14] + 0xc33707d6u, 9); + SCR_MD5_GSTEP(c, d, a, b, m[3] + 0xf4d50d87u, 14); + SCR_MD5_GSTEP(b, c, d, a, m[8] + 0x455a14edu, 20); + SCR_MD5_GSTEP(a, b, c, d, m[13] + 0xa9e3e905u, 5); + SCR_MD5_GSTEP(d, a, b, c, m[2] + 0xfcefa3f8u, 9); + SCR_MD5_GSTEP(c, d, a, b, m[7] + 0x676f02d9u, 14); + SCR_MD5_GSTEP(b, c, d, a, m[12] + 0x8d2a4c8au, 20); + SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[5] + 0xfffa3942u, 4); + SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[8] + 0x8771f681u, 11); + SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[11] + 0x6d9d6122u, 16); + SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[14] + 0xfde5380cu, 23); + SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[1] + 0xa4beea44u, 4); + SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[4] + 0x4bdecfa9u, 11); + SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[7] + 0xf6bb4b60u, 16); + SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[10] + 0xbebfbc70u, 23); + SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[13] + 0x289b7ec6u, 4); + SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[0] + 0xeaa127fau, 11); + SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[3] + 0xd4ef3085u, 16); + SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[6] + 0x04881d05u, 23); + SCR_MD5_STEP(SCR_MD5_H, a, b, c, d, m[9] + 0xd9d4d039u, 4); + SCR_MD5_STEP(SCR_MD5_H, d, a, b, c, m[12] + 0xe6db99e5u, 11); + SCR_MD5_STEP(SCR_MD5_H, c, d, a, b, m[15] + 0x1fa27cf8u, 16); + SCR_MD5_STEP(SCR_MD5_H, b, c, d, a, m[2] + 0xc4ac5665u, 23); + SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[0] + 0xf4292244u, 6); + SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[7] + 0x432aff97u, 10); + SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[14] + 0xab9423a7u, 15); + SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[5] + 0xfc93a039u, 21); + SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[12] + 0x655b59c3u, 6); + SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[3] + 0x8f0ccc92u, 10); + SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[10] + 0xffeff47du, 15); + SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[1] + 0x85845dd1u, 21); + SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[8] + 0x6fa87e4fu, 6); + SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[15] + 0xfe2ce6e0u, 10); + SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[6] + 0xa3014314u, 15); + SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[13] + 0x4e0811a1u, 21); + SCR_MD5_STEP(SCR_MD5_I, a, b, c, d, m[4] + 0xf7537e82u, 6); + SCR_MD5_STEP(SCR_MD5_I, d, a, b, c, m[11] + 0xbd3af235u, 10); + SCR_MD5_STEP(SCR_MD5_I, c, d, a, b, m[2] + 0x2ad7d2bbu, 15); + SCR_MD5_STEP(SCR_MD5_I, b, c, d, a, m[9] + 0xeb86d391u, 21); + a += a0; b += b0; c += c0; d += d0; } - h[0] += a; h[1] += b; h[2] += c; h[3] += d; + h[0] = a; h[1] = b; h[2] = c; h[3] = d; } typedef struct ScrMd5Ctx { @@ -7329,14 +7409,15 @@ static void scr_md5_update(ScrMd5Ctx *ctx, const unsigned char *data, size_t len data += take; len -= take; if (ctx->tail_len == 64) { - scr_md5_block(ctx->h, ctx->tail); + scr_md5_blocks(ctx->h, ctx->tail, 1); ctx->tail_len = 0; } } - while (len >= 64) { - scr_md5_block(ctx->h, data); - data += 64; - len -= 64; + if (len >= 64) { + size_t blocks = len / 64; + scr_md5_blocks(ctx->h, data, blocks); + data += blocks * 64; + len -= blocks * 64; } if (len != 0) { memcpy(ctx->tail, data, len); @@ -7354,8 +7435,7 @@ static size_t scr_md5_final(const ScrMd5Ctx *source, unsigned char out[32]) { memset(tail + rem + 1, 0, pad - rem - 1 - 8); uint64_t bits = ctx.bytes * 8; for (int b = 0; b < 8; b++) tail[pad - 8 + b] = (unsigned char)(bits >> (8 * b)); - scr_md5_block(ctx.h, tail); - if (pad == 128) scr_md5_block(ctx.h, tail + 64); + scr_md5_blocks(ctx.h, tail, pad / 64); for (int j = 0; j < 4; j++) { for (int b = 0; b < 4; b++) out[j * 4 + b] = (unsigned char)(ctx.h[j] >> (8 * b)); } @@ -7405,29 +7485,319 @@ static uint64_t scr_sha512_rotr(uint64_t x, unsigned n) { return (x >> n) | (x << (64 - n)); } -SCR_DIGEST_ALIGNED static void scr_sha512_block(uint64_t h[8], const unsigned char *data) { - uint64_t w[80]; - for (size_t i = 0; i < 16; i++) { - w[i] = 0; - for (size_t b = 0; b < 8; b++) w[i] = (w[i] << 8) | data[i * 8 + b]; +#define SCR_SHA512_BSIG0(x) \ + (scr_sha512_rotr(x, 28) ^ scr_sha512_rotr(x, 34) ^ scr_sha512_rotr(x, 39)) +#define SCR_SHA512_BSIG1(x) \ + (scr_sha512_rotr(x, 14) ^ scr_sha512_rotr(x, 18) ^ scr_sha512_rotr(x, 41)) +#define SCR_SHA512_SSIG0(x) (scr_sha512_rotr(x, 1) ^ scr_sha512_rotr(x, 8) ^ ((x) >> 7)) +#define SCR_SHA512_SSIG1(x) (scr_sha512_rotr(x, 19) ^ scr_sha512_rotr(x, 61) ^ ((x) >> 6)) + +/* One round with the eight working variables passed in rotated order, so + * an unrolled sequence of rounds needs no register moves. */ +#define SCR_SHA512_ROUND(a, b, c, d, e, f, g, h, k, w) \ + do { \ + uint64_t t1 = (h) + SCR_SHA512_BSIG1(e) + ((g) ^ ((e) & ((f) ^ (g)))) + (k) + (w); \ + (d) += t1; \ + (h) = t1 + SCR_SHA512_BSIG0(a) + (((a) & (b)) | ((c) & ((a) | (b)))); \ + } while (0) + +static uint64_t scr_sha512_load(const unsigned char *p) { + uint64_t v = 0; + for (size_t b = 0; b < 8; b++) v = (v << 8) | p[b]; + return v; +} + +/* The message schedule is kept as a 16-word ring and extended 16 words + * at a time between groups of 16 rounds. */ +SCR_DIGEST_ALIGNED static void scr_sha512_blocks_portable(uint64_t state[8], const unsigned char *p, + size_t blocks) { + uint64_t a = state[0], b = state[1], c = state[2], d = state[3]; + uint64_t e = state[4], f = state[5], g = state[6], h = state[7]; + for (; blocks != 0; blocks--, p += 128) { + uint64_t w[16]; + for (size_t i = 0; i < 16; i++) w[i] = scr_sha512_load(p + i * 8); + for (size_t i = 0; i < 80; i += 16) { + if (i != 0) { + for (size_t j = 0; j < 16; j++) { + w[j] += SCR_SHA512_SSIG1(w[(j + 14) & 15]) + w[(j + 9) & 15] + + SCR_SHA512_SSIG0(w[(j + 1) & 15]); + } + } + const uint64_t *k = scr_sha512_k + i; + SCR_SHA512_ROUND(a, b, c, d, e, f, g, h, k[0], w[0]); + SCR_SHA512_ROUND(h, a, b, c, d, e, f, g, k[1], w[1]); + SCR_SHA512_ROUND(g, h, a, b, c, d, e, f, k[2], w[2]); + SCR_SHA512_ROUND(f, g, h, a, b, c, d, e, k[3], w[3]); + SCR_SHA512_ROUND(e, f, g, h, a, b, c, d, k[4], w[4]); + SCR_SHA512_ROUND(d, e, f, g, h, a, b, c, k[5], w[5]); + SCR_SHA512_ROUND(c, d, e, f, g, h, a, b, k[6], w[6]); + SCR_SHA512_ROUND(b, c, d, e, f, g, h, a, k[7], w[7]); + SCR_SHA512_ROUND(a, b, c, d, e, f, g, h, k[8], w[8]); + SCR_SHA512_ROUND(h, a, b, c, d, e, f, g, k[9], w[9]); + SCR_SHA512_ROUND(g, h, a, b, c, d, e, f, k[10], w[10]); + SCR_SHA512_ROUND(f, g, h, a, b, c, d, e, k[11], w[11]); + SCR_SHA512_ROUND(e, f, g, h, a, b, c, d, k[12], w[12]); + SCR_SHA512_ROUND(d, e, f, g, h, a, b, c, k[13], w[13]); + SCR_SHA512_ROUND(c, d, e, f, g, h, a, b, k[14], w[14]); + SCR_SHA512_ROUND(b, c, d, e, f, g, h, a, k[15], w[15]); + } + a = state[0] += a; b = state[1] += b; c = state[2] += c; d = state[3] += d; + e = state[4] += e; f = state[5] += f; g = state[6] += g; h = state[7] += h; } - for (size_t i = 16; i < 80; i++) { - uint64_t s0 = scr_sha512_rotr(w[i - 15], 1) ^ scr_sha512_rotr(w[i - 15], 8) ^ (w[i - 15] >> 7); - uint64_t s1 = scr_sha512_rotr(w[i - 2], 19) ^ scr_sha512_rotr(w[i - 2], 61) ^ (w[i - 2] >> 6); - w[i] = w[i - 16] + s0 + w[i - 7] + s1; +} + +#ifdef SCR_SHA_NI +/* ── SHA-512 kernels (x86-64) ───────────────────────────────────────── + * With AVX2 and BMI2, the message schedule is computed two words at a + * time in vector registers, interleaved with the scalar rounds, and the + * rounds rotate with RORX. With AVX-512VL the schedule uses vector + * rotates and three-input XOR. The CPU and OS state checks run once and + * are cached. */ +static int scr_sha512_x86_state; /* 0 unknown, 1 portable, 2 AVX2, 3 AVX-512VL */ + +static int scr_sha512_x86(void) { + if (scr_sha512_x86_state == 0) { + unsigned a, b, c, d; + int kind = 1; + if (__get_cpuid(1, &a, &b, &c, &d) && (c & bit_OSXSAVE) && (c & bit_AVX)) { + unsigned lo, hi; + __asm__ volatile("xgetbv" : "=a"(lo), "=d"(hi) : "c"(0)); + (void)hi; + /* XMM and YMM state enabled. */ + if ((lo & 6) == 6 && __get_cpuid_count(7, 0, &a, &b, &c, &d) && (b & bit_AVX2) && + (b & bit_BMI2)) { + kind = 2; + /* Opmask, ZMM0-15 upper halves and ZMM16-31 state enabled. */ + if ((b & bit_AVX512F) && (b & bit_AVX512VL) && (lo & 0xe0) == 0xe0) kind = 3; + } + } + scr_sha512_x86_state = kind; } - uint64_t a = h[0], b = h[1], c = h[2], d = h[3]; - uint64_t e = h[4], f = h[5], g = h[6], v = h[7]; - for (size_t i = 0; i < 80; i++) { - uint64_t s1 = scr_sha512_rotr(e, 14) ^ scr_sha512_rotr(e, 18) ^ scr_sha512_rotr(e, 41); - uint64_t t1 = v + s1 + ((e & f) ^ (~e & g)) + scr_sha512_k[i] + w[i]; - uint64_t s0 = scr_sha512_rotr(a, 28) ^ scr_sha512_rotr(a, 34) ^ scr_sha512_rotr(a, 39); - uint64_t t2 = s0 + ((a & b) ^ (a & c) ^ (b & c)); - v = g; g = f; f = e; e = d + t1; - d = c; c = b; b = a; a = t1 + t2; + return scr_sha512_x86_state; +} + +__attribute__((target("avx2,bmi2"))) static inline __m128i scr_sha512_ssig0_avx2(__m128i x) { + return _mm_xor_si128(_mm_xor_si128(_mm_or_si128(_mm_srli_epi64(x, 1), _mm_slli_epi64(x, 63)), + _mm_or_si128(_mm_srli_epi64(x, 8), _mm_slli_epi64(x, 56))), + _mm_srli_epi64(x, 7)); +} + +__attribute__((target("avx2,bmi2"))) static inline __m128i scr_sha512_ssig1_avx2(__m128i x) { + return _mm_xor_si128(_mm_xor_si128(_mm_or_si128(_mm_srli_epi64(x, 19), _mm_slli_epi64(x, 45)), + _mm_or_si128(_mm_srli_epi64(x, 61), _mm_slli_epi64(x, 3))), + _mm_srli_epi64(x, 6)); +} + +__attribute__((target("avx512f,avx512vl,bmi2"))) static inline __m128i +scr_sha512_ssig0_avx512(__m128i x) { + return _mm_ternarylogic_epi64(_mm_ror_epi64(x, 1), _mm_ror_epi64(x, 8), _mm_srli_epi64(x, 7), + 0x96); +} + +__attribute__((target("avx512f,avx512vl,bmi2"))) static inline __m128i +scr_sha512_ssig1_avx512(__m128i x) { + return _mm_ternarylogic_epi64(_mm_ror_epi64(x, 19), _mm_ror_epi64(x, 61), _mm_srli_epi64(x, 6), + 0x96); +} + +/* A round for the vector kernels. wk is the schedule word plus its + * constant. Maj(a, b, c) is ((a ^ b) & (b ^ c)) ^ b, where b ^ c is the + * previous round's a ^ b. The empty asm statements keep the compiler from + * reassociating the additions: adding Sigma1(e) last keeps the sum of h, + * wk and Ch off the dependency chain through e. */ +#define SCR_SHA512_PIN(v) __asm__("" : "+r"(v)) +#define SCR_SHA512_ROUND_WK(a, b, c, d, e, f, g, h, wk, ab, bc) \ + do { \ + uint64_t t1 = (h) + (wk); \ + SCR_SHA512_PIN(t1); \ + t1 += (g) ^ ((e) & ((f) ^ (g))); \ + SCR_SHA512_PIN(t1); \ + t1 += SCR_SHA512_BSIG1(e); \ + SCR_SHA512_PIN(t1); \ + (d) += t1; \ + (ab) = (a) ^ (b); \ + uint64_t m = t1 + (((ab) & (bc)) ^ (b)); \ + SCR_SHA512_PIN(m); \ + (h) = m + SCR_SHA512_BSIG0(a); \ + } while (0) + +/* Words 2j and 2j+1 of the schedule, in the ring slot of words 2j-16 and + * 2j-15, and their sum with the round constants into wk. */ +#define SCR_SHA512_SCHEDULE(j, ssig0, ssig1) \ + do { \ + if ((j) < 40) { \ + __m128i s15 = _mm_alignr_epi8(w[((j) + 1) & 7], w[(j) & 7], 8); \ + __m128i s7 = _mm_alignr_epi8(w[((j) + 5) & 7], w[((j) + 4) & 7], 8); \ + __m128i next = _mm_add_epi64(_mm_add_epi64(w[(j) & 7], ssig0(s15)), \ + _mm_add_epi64(s7, ssig1(w[((j) + 7) & 7]))); \ + w[(j) & 7] = next; \ + _mm_store_si128((__m128i *)&wk[(j) * 2], \ + _mm_add_epi64(next, _mm_loadu_si128((const __m128i *)&scr_sha512_k[(j) * 2]))); \ + } \ + } while (0) + +/* Rounds 2i to 2i+7 compute the schedule words 2i+16 to 2i+23. */ +#define SCR_SHA512_VECTOR_BODY(ssig0, ssig1) \ + const __m128i bswap = _mm_set_epi64x(0x08090a0b0c0d0e0fULL, 0x0001020304050607ULL); \ + uint64_t a = state[0], b = state[1], c = state[2], d = state[3]; \ + uint64_t e = state[4], f = state[5], g = state[6], h = state[7]; \ + for (; blocks != 0; blocks--, p += 128) { \ + __m128i w[8]; \ + uint64_t wk[80] __attribute__((aligned(16))); \ + _Pragma("GCC unroll 8") for (int i = 0; i < 8; i++) { \ + w[i] = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i *)(p + i * 16)), bswap); \ + _mm_store_si128((__m128i *)&wk[i * 2], \ + _mm_add_epi64(w[i], _mm_loadu_si128((const __m128i *)&scr_sha512_k[i * 2]))); \ + } \ + uint64_t x = b ^ c, y; \ + _Pragma("GCC unroll 10") for (int i = 0; i < 40; i += 4) { \ + SCR_SHA512_SCHEDULE(i + 8, ssig0, ssig1); \ + SCR_SHA512_ROUND_WK(a, b, c, d, e, f, g, h, wk[2 * i + 0], y, x); \ + SCR_SHA512_ROUND_WK(h, a, b, c, d, e, f, g, wk[2 * i + 1], x, y); \ + SCR_SHA512_SCHEDULE(i + 9, ssig0, ssig1); \ + SCR_SHA512_ROUND_WK(g, h, a, b, c, d, e, f, wk[2 * i + 2], y, x); \ + SCR_SHA512_ROUND_WK(f, g, h, a, b, c, d, e, wk[2 * i + 3], x, y); \ + SCR_SHA512_SCHEDULE(i + 10, ssig0, ssig1); \ + SCR_SHA512_ROUND_WK(e, f, g, h, a, b, c, d, wk[2 * i + 4], y, x); \ + SCR_SHA512_ROUND_WK(d, e, f, g, h, a, b, c, wk[2 * i + 5], x, y); \ + SCR_SHA512_SCHEDULE(i + 11, ssig0, ssig1); \ + SCR_SHA512_ROUND_WK(c, d, e, f, g, h, a, b, wk[2 * i + 6], y, x); \ + SCR_SHA512_ROUND_WK(b, c, d, e, f, g, h, a, wk[2 * i + 7], x, y); \ + } \ + a = state[0] += a; b = state[1] += b; c = state[2] += c; d = state[3] += d; \ + e = state[4] += e; f = state[5] += f; g = state[6] += g; h = state[7] += h; \ } - h[0] += a; h[1] += b; h[2] += c; h[3] += d; - h[4] += e; h[5] += f; h[6] += g; h[7] += v; + +__attribute__((target("avx2,bmi2"), aligned(64))) static void +scr_sha512_blocks_avx2(uint64_t state[8], const unsigned char *p, size_t blocks) { + SCR_SHA512_VECTOR_BODY(scr_sha512_ssig0_avx2, scr_sha512_ssig1_avx2) +} + +__attribute__((target("avx512f,avx512vl,bmi2"), aligned(64))) static void +scr_sha512_blocks_avx512(uint64_t state[8], const unsigned char *p, size_t blocks) { + SCR_SHA512_VECTOR_BODY(scr_sha512_ssig0_avx512, scr_sha512_ssig1_avx512) +} +#endif + + +#if defined(__aarch64__) && (defined(__clang__) || defined(__GNUC__)) && !defined(_WIN32) +/* ── SHA-512 kernel (arm64) ─────────────────────────────────────────── + * On CPUs with the SHA-512 extension (FEAT_SHA512), the blocks run with + * SHA512H, SHA512H2, SHA512SU0 and SHA512SU1; other CPUs use the portable + * blocks. The CPU check runs once and is cached. */ +#include +#if defined(__APPLE__) +#include +#else +#include +#endif +#define SCR_SHA512_ARM 1 +#if defined(__clang__) +#define SCR_SHA512_ARM_TARGET __attribute__((target("sha3"))) +#else +#define SCR_SHA512_ARM_TARGET __attribute__((target("+sha3"))) +#endif + +static int scr_sha512_arm_state; /* 0 unknown, 1 available, 2 absent */ + +static bool scr_sha512_arm(void) { + if (scr_sha512_arm_state == 0) { + bool ok; +#if defined(__APPLE__) + int value = 0; + size_t size = sizeof value; + ok = sysctlbyname("hw.optional.armv8_2_sha512", &value, &size, NULL, 0) == 0 && value != 0; +#else + ok = (getauxval(AT_HWCAP) & (1ul << 21)) != 0; /* HWCAP_SHA512 */ +#endif + scr_sha512_arm_state = ok ? 1 : 2; + } + return scr_sha512_arm_state == 1; +} + +/* Two rounds per step. With the state pairs in the roles (A, B, C, D) = + * (ab, cd, ef, gh), a step computes I = SHA512H(swap(K + W) + D, ext(C, D), + * ext(B, C)), D = SHA512H2(I, B, A) and B += I; the roles then rotate to + * (D, A, B, C). */ +#define SCR_SHA512_ARM_STEP(A, B, C, D, msg, k) \ + do { \ + uint64x2_t kw = vaddq_u64((msg), vld1q_u64(k)); \ + uint64x2_t sum = vaddq_u64(vextq_u64(kw, kw, 1), (D)); \ + uint64x2_t i = vsha512hq_u64(sum, vextq_u64((C), (D), 1), vextq_u64((B), (C), 1)); \ + (D) = vsha512h2q_u64(i, (B), (A)); \ + (B) = vaddq_u64((B), i); \ + } while (0) + +/* Message words 2j and 2j+1 from the eight pairs before them. */ +#define SCR_SHA512_ARM_SCHEDULE(m0, m1, m4, m5, m7) \ + (m0) = vsha512su1q_u64(vsha512su0q_u64((m0), (m1)), (m7), vextq_u64((m4), (m5), 1)) + +SCR_SHA512_ARM_TARGET static void scr_sha512_blocks_arm(uint64_t state[8], const unsigned char *p, + size_t blocks) { + uint64x2_t ab = vld1q_u64(&state[0]), cd = vld1q_u64(&state[2]); + uint64x2_t ef = vld1q_u64(&state[4]), gh = vld1q_u64(&state[6]); + for (; blocks != 0; blocks--, p += 128) { + uint64x2_t ab0 = ab, cd0 = cd, ef0 = ef, gh0 = gh; + uint64x2_t m0 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 0))); + uint64x2_t m1 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 16))); + uint64x2_t m2 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 32))); + uint64x2_t m3 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 48))); + uint64x2_t m4 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 64))); + uint64x2_t m5 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 80))); + uint64x2_t m6 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 96))); + uint64x2_t m7 = vreinterpretq_u64_u8(vrev64q_u8(vld1q_u8(p + 112))); + const uint64_t *k = scr_sha512_k; + for (int i = 0; i < 5; i++, k += 16) { + SCR_SHA512_ARM_STEP(ab, cd, ef, gh, m0, k + 0); + SCR_SHA512_ARM_STEP(gh, ab, cd, ef, m1, k + 2); + SCR_SHA512_ARM_STEP(ef, gh, ab, cd, m2, k + 4); + SCR_SHA512_ARM_STEP(cd, ef, gh, ab, m3, k + 6); + SCR_SHA512_ARM_STEP(ab, cd, ef, gh, m4, k + 8); + SCR_SHA512_ARM_STEP(gh, ab, cd, ef, m5, k + 10); + SCR_SHA512_ARM_STEP(ef, gh, ab, cd, m6, k + 12); + SCR_SHA512_ARM_STEP(cd, ef, gh, ab, m7, k + 14); + if (i == 4) break; + SCR_SHA512_ARM_SCHEDULE(m0, m1, m4, m5, m7); + SCR_SHA512_ARM_SCHEDULE(m1, m2, m5, m6, m0); + SCR_SHA512_ARM_SCHEDULE(m2, m3, m6, m7, m1); + SCR_SHA512_ARM_SCHEDULE(m3, m4, m7, m0, m2); + SCR_SHA512_ARM_SCHEDULE(m4, m5, m0, m1, m3); + SCR_SHA512_ARM_SCHEDULE(m5, m6, m1, m2, m4); + SCR_SHA512_ARM_SCHEDULE(m6, m7, m2, m3, m5); + SCR_SHA512_ARM_SCHEDULE(m7, m0, m3, m4, m6); + } + ab = vaddq_u64(ab, ab0); + cd = vaddq_u64(cd, cd0); + ef = vaddq_u64(ef, ef0); + gh = vaddq_u64(gh, gh0); + } + vst1q_u64(&state[0], ab); + vst1q_u64(&state[2], cd); + vst1q_u64(&state[4], ef); + vst1q_u64(&state[6], gh); +} +#endif + +static void scr_sha512_blocks(uint64_t h[8], const unsigned char *p, size_t blocks) { +#ifdef SCR_SHA_NI + switch (scr_sha512_x86()) { + case 3: + scr_sha512_blocks_avx512(h, p, blocks); + return; + case 2: + scr_sha512_blocks_avx2(h, p, blocks); + return; + default: + break; + } +#endif +#ifdef SCR_SHA512_ARM + if (scr_sha512_arm()) { + scr_sha512_blocks_arm(h, p, blocks); + return; + } +#endif + scr_sha512_blocks_portable(h, p, blocks); } static void scr_sha512_init(ScrSha512Ctx *ctx, bool sha384) { @@ -7456,14 +7826,15 @@ static void scr_sha512_update(ScrSha512Ctx *ctx, const unsigned char *data, size data += take; len -= take; if (ctx->tail_len == 128) { - scr_sha512_block(ctx->h, ctx->tail); + scr_sha512_blocks(ctx->h, ctx->tail, 1); ctx->tail_len = 0; } } - while (len >= 128) { - scr_sha512_block(ctx->h, data); - data += 128; - len -= 128; + if (len >= 128) { + size_t blocks = len / 128; + scr_sha512_blocks(ctx->h, data, blocks); + data += blocks * 128; + len -= blocks * 128; } if (len != 0) { memcpy(ctx->tail, data, len); @@ -7482,8 +7853,7 @@ static size_t scr_sha512_final(const ScrSha512Ctx *source, unsigned char out[64] tail[pad - 1 - b] = (unsigned char)(low >> (8 * b)); tail[pad - 9 - b] = (unsigned char)(high >> (8 * b)); } - scr_sha512_block(ctx.h, tail); - if (pad == 256) scr_sha512_block(ctx.h, tail + 128); + scr_sha512_blocks(ctx.h, tail, pad / 128); size_t len = sha384 ? 48 : 64; for (size_t i = 0; i < len; i++) out[i] = (unsigned char)(ctx.h[i / 8] >> (56 - 8 * (i % 8))); return len; @@ -8473,6 +8843,8 @@ void scr_crypto_hash_stream_close(ScrCryptoHash *hash) { if (stream) stream_release(stream); } +bool scr_crypto_hash_is_hmac(const ScrCryptoHash *hash) { return hash->hmac; } + bool scr_crypto_hash_stream_usable(const ScrCryptoHash *hash) { if (!hash->stream_closed) return true; static const char msg[] = "Stream use of a Hash or Hmac after its stream closed has no lowering"; @@ -10333,7 +10705,9 @@ ScrStr *scr_crypto_x509_valid_to_str(ScrStr *pem) { * keep other surrogates lone, WTF-8 encode. */ static ScrStr *scr_str_from_units(size_t n, uint32_t (*unit)(void *, size_t), void *src) { if (n == 1) return scr_str_from_char_code_one((double)unit(src, 0)); - char *out = malloc(n * 3 + 1); /* worst case: 3 bytes per UTF-16 unit */ + /* Up to 64 units encode on the stack (3 bytes per UTF-16 unit at most). */ + char small[64 * 3 + 1]; + char *out = n <= 64 ? small : malloc(n * 3 + 1); if (!out) { scr_trap("scriptc: out of memory\n"); } @@ -10350,10 +10724,19 @@ static ScrStr *scr_str_from_units(size_t n, uint32_t (*unit)(void *, size_t), vo o += scr_wtf8_put(out + o, cp); } ScrStr *s = scr_str_new(out, o); - free(out); + if (out != small) free(out); return s; } +static uint32_t scr_fcc_units_unit(void *src, size_t i) { + return scr_to_uint32(((const double *)src)[i]) & 0xFFFFu; +} + +/* String.fromCharCode(c0, ..., cn-1) with the codes in a caller's array. */ +ScrStr *scr_str_from_char_codes(const double *codes, size_t n) { + return scr_str_from_units(n, scr_fcc_units_unit, (void *)codes); +} + static uint32_t scr_fcc_arr_unit(void *src, size_t i) { return scr_to_uint32(scr_arr_get_f64((ScrArr *)src, (double)i)) & 0xFFFFu; } @@ -10555,9 +10938,10 @@ ScrStr *scr_date_to_utc_string(double ms) { /* ── new Date(dateString).getTime() ────────────────────────────────────── * - * The BOUNDED date-string parse (documented divergence — V8's parser - * accepts far more): two grammars answer a time value, everything else is - * NaN (Node's invalid-date getTime). + * The BOUNDED date-string parse, the fast path: two grammars answer a time + * value directly; scr_date_parse falls back to V8's full parser + * (scr_date_parse_v8 below), which also answers everything they accept, + * for the strings they reject. * * 1. The ASN1_TIME_print shape X509Certificate.validFrom/validTo answer * ("Jul 1 00:00:00 2026 GMT", "Jul 17 17:52:11 2026 GMT") — the @@ -10566,8 +10950,8 @@ ScrStr *scr_date_to_utc_string(double ms) { * 2. ECMA's own date-time string format with an EXPLICIT offset: * YYYY[-MM[-DD]] date-only forms (UTC, per the spec) and full * date-times YYYY-MM-DDTHH:mm[:ss[.sss]] ending in Z or ±HH:MM. - * Offset-less date-times are LOCAL time in JS; that arm answers NaN - * here rather than guessing a zone (the divergence note). + * Offset-less date-times are LOCAL time in JS; they take the + * fallback, which converts them in the host zone. */ /* Howard Hinnant's days_from_civil (the to_iso walk inverted). */ @@ -10701,6 +11085,16 @@ double scr_date_parse_get_time(ScrStr *s) { } } +static double scr_date_parse_v8(ScrStr *s); + +/* Date.parse and new Date(string): the bounded grammars answer first; only + * a string they reject reaches V8's full parser, so the timestamps they + * accept cost nothing more. */ +double scr_date_parse(ScrStr *s) { + double t = scr_date_parse_get_time(s); + return isnan(t) ? scr_date_parse_v8(s) : t; +} + /* Date.UTC(year, month, date, hours, minutes, seconds, ms) — the spec's * MakeDay/MakeTime/TimeClip pipeline over ALREADY-NUMBER arguments (tsc * pins them; the frontend completes omitted trailing arguments with the @@ -10909,6 +11303,657 @@ double scr_date_get_timezone_offset(double ms) { return scr_date_local_parts(ms, &p) ? p.timezone_offset : NAN; } +/* ── Local time to UTC, and the year/month field constructor ─────────── + * The spec's UTC(t) over the host zone: t is a local wall-clock time + * value. The offset in effect a day before and a day after t brackets any + * transition (none come closer together than that in tzdata). A time in + * a DST overlap has two instants and answers the earlier; a time in a gap + * has none and is read with the offset BEFORE the transition, exactly + * V8's ICU getOffsetFromLocal(FORMER, FORMER). Offsets come from the same + * localtime/surrogate-year walk as the local getters, so a Date built + * from local fields reads those fields back. */ + +/* The host zone's offset (local - UTC) in ms at UTC instant `ms` (an + * integral time value within about ±8.65e15). */ +static bool scr_date_offset_at(double ms, double *out) { + long long cms = (long long)ms; + double secd = (double)(cms / 1000 - (cms % 1000 < 0)); + struct tm tmv; + double basis_secd = secd; + if (!scr_date_localtime(basis_secd, &tmv)) { + ScrDateParts utc; + scr_date_utc_parts_unchecked(ms, &utc); + long long cycle_year = (utc.year - 2000) % 400; + if (cycle_year < 0) cycle_year += 400; + basis_secd = + scr_days_from_civil(2000 + cycle_year, utc.month + 1, utc.date) * 86400.0 + + utc.hours * 3600.0 + utc.minutes * 60.0 + utc.seconds; + if (!scr_date_localtime(basis_secd, &tmv)) return false; + } + double local_as_utc = + scr_days_from_civil((long long)tmv.tm_year + 1900, tmv.tm_mon + 1, tmv.tm_mday) * 86400.0 + + tmv.tm_hour * 3600.0 + tmv.tm_min * 60.0 + tmv.tm_sec; + *out = (local_as_utc - basis_secd) * 1000.0; + return true; +} + +static volatile const double scr_date_day_ms = 86400000.0; + +static double scr_date_local_to_utc(double t) { + double before, after, at_first, at_second, day = scr_date_day_ms; + if (!scr_date_offset_at(t - day, &before) || !scr_date_offset_at(t + day, &after)) { + return NAN; + } + double first = t - before; + if (!scr_date_offset_at(first, &at_first)) return NAN; + if (before == after) { + if (at_first == before) return first; + /* Two transitions inside the window: settle on the instant's own offset. */ + double again = t - at_first, at_again; + if (scr_date_offset_at(again, &at_again) && at_again == at_first) return again; + return first; + } + double second = t - after; + if (!scr_date_offset_at(second, &at_second)) return NAN; + bool first_ok = at_first == before, second_ok = at_second == after; + if (first_ok && second_ok) return first < second ? first : second; /* overlap */ + if (second_ok && !first_ok) return second; + return first; /* the earlier offset's instant, or a gap */ +} + +/* V8's MakeDay (date.cc): bounds the UNtruncated year and month, then + * truncates (FastD2I) and normalizes the month into the year. */ +static double scr_date_v8_make_day(double year, double month, double date) { + if (!(fabs(year) <= 1000000.0 && fabs(month) <= 10000000.0 && isfinite(date))) return NAN; + int y = (int)year, m = (int)month; + y += m / 12; + m %= 12; + if (m < 0) { + m += 12; + y -= 1; + } + static const int year_delta = 399999; + static const int base_day = 365 * (1970 + year_delta) + (1970 + year_delta) / 4 - + (1970 + year_delta) / 100 + (1970 + year_delta) / 400; + int day_from_year = 365 * (y + year_delta) + (y + year_delta) / 4 - + (y + year_delta) / 100 + (y + year_delta) / 400 - base_day; + static const int common[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + static const int leap[12] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}; + bool is_leap = !((y % 4 != 0) || (y % 100 == 0 && y % 400 != 0)); + day_from_year += is_leap ? leap[m] : common[m]; + return (double)(day_from_year - 1) + trunc(date); +} + +static double scr_date_v8_make_time(double h, double m, double s, double ms) { + if (!isfinite(h) || !isfinite(m) || !isfinite(s) || !isfinite(ms)) return NAN; + return trunc(h) * 3600000.0 + trunc(m) * 60000.0 + trunc(s) * 1000.0 + trunc(ms); +} + +/* V8's bound on a local time value before UTC(t): TimeClip's range plus + * ten days. Read from memory so the value stays in its own section instead + * of this unit's shared constant pool, which every program linking the + * unit carries. */ +static volatile const double scr_date_local_limit = 8640000864000000.0; + +/* MakeDate over local fields, then UTC(t) and TimeClip. V8 refuses a local + * time value more than ten days past TimeClip's range before converting. */ +static double scr_date_from_local(double day, double time) { + if (!isfinite(day) || !isfinite(time)) return NAN; + double t = time + day * 86400000.0; + if (!(fabs(t) <= scr_date_local_limit)) return NAN; + return scr_date_new_ms(scr_date_local_to_utc(trunc(t))); +} + +/* new Date(year, monthIndex[, date[, hours[, minutes[, seconds[, ms]]]]]) + * over already-number arguments (the frontend completes omitted trailing + * arguments with date 1 and time parts 0): years 0–99 map to 1900+year, + * fields roll over through MakeDay/MakeTime, and the local time converts + * to UTC in the host zone. Never throws. */ +double scr_date_new_local(double y, double mo, double d, + double h, double mi, double s, double ms) { + if (!isnan(y)) { + double yi = trunc(y); + if (yi >= 0 && yi <= 99) y = 1900 + yi; + } + return scr_date_from_local(scr_date_v8_make_day(y, mo, d), scr_date_v8_make_time(h, mi, s, ms)); +} + +/* ── V8's date-string parser, the fallback ───────────────────────────── + * scr_date_parse answers the bounded grammars directly and asks + * this parser only when they produce NaN. It follows V8's DateParser + * (dateparser.cc, dateparser-inl.h) step for step: the ES5 date-time + * format first, then the Safari-compatible legacy grammar for whatever + * is left (RFC 1123/2822 HTTP dates, Date#toString output, "Jan 2, 2024", + * "2024/01/02 10:00", ...). Strings are read as UTF-16 code units, and a + * NUL unit ends the input, as in V8. A date without a zone is local time. */ + +enum { + SCR_DT_INVALID = -6, SCR_DT_UNKNOWN = -5, SCR_DT_SPACE = -4, + SCR_DT_NUMBER = -3, SCR_DT_SYMBOL = -2, SCR_DT_END = -1, + /* keyword tags are their keyword type (>= 0) */ + SCR_DK_WORD = 0, SCR_DK_MONTH = 1, SCR_DK_ZONE = 2, SCR_DK_T = 3, SCR_DK_AMPM = 4, +}; +#define SCR_DT_NONE INT_MAX + +typedef struct { + int tag, length, value; +} ScrDateTok; + +typedef struct { + const unsigned char *p, *end; + uint32_t ch, low; + ScrDateTok next; +} ScrDateScan; + +static void scr_date_scan_advance(ScrDateScan *in) { + if (in->low) { + in->ch = in->low; + in->low = 0; + return; + } + if (in->p >= in->end) { + in->ch = 0; + return; + } + const unsigned char *p = in->p; + size_t left = (size_t)(in->end - p); + uint32_t cp = p[0]; + size_t n = 1; + if (cp >= 0xF0 && left >= 4) { + cp = ((cp & 0x07u) << 18) | ((p[1] & 0x3Fu) << 12) | ((p[2] & 0x3Fu) << 6) | (p[3] & 0x3Fu); + n = 4; + } else if (cp >= 0xE0 && left >= 3) { + cp = ((cp & 0x0Fu) << 12) | ((p[1] & 0x3Fu) << 6) | (p[2] & 0x3Fu); + n = 3; + } else if (cp >= 0xC0 && left >= 2) { + cp = ((cp & 0x1Fu) << 6) | (p[1] & 0x3Fu); + n = 2; + } else if (cp >= 0x80) { + cp = 0xFFFD; + } + in->p += n; + if (cp > 0xFFFF) { + cp -= 0x10000; + in->ch = 0xD800 + (cp >> 10); + in->low = 0xDC00 + (cp & 0x3FF); + } else { + in->ch = cp; + } +} + +/* ECMAScript WhiteSpace, and WhiteSpace or LineTerminator. */ +static bool scr_date_is_space(uint32_t c) { + return c == 0x09 || c == 0x0B || c == 0x0C || c == 0x20 || c == 0xA0 || c == 0xFEFF || + c == 0x1680 || (c >= 0x2000 && c <= 0x200A) || c == 0x202F || c == 0x205F || + c == 0x3000; +} + +static bool scr_date_is_space_or_newline(uint32_t c) { + return scr_date_is_space(c) || c == 0x0A || c == 0x0D || c == 0x2028 || c == 0x2029; +} + +static const struct { + char prefix[3]; + signed char type, value; +} scr_date_keywords[] = { + {{'j', 'a', 'n'}, SCR_DK_MONTH, 1}, {{'f', 'e', 'b'}, SCR_DK_MONTH, 2}, + {{'m', 'a', 'r'}, SCR_DK_MONTH, 3}, {{'a', 'p', 'r'}, SCR_DK_MONTH, 4}, + {{'m', 'a', 'y'}, SCR_DK_MONTH, 5}, {{'j', 'u', 'n'}, SCR_DK_MONTH, 6}, + {{'j', 'u', 'l'}, SCR_DK_MONTH, 7}, {{'a', 'u', 'g'}, SCR_DK_MONTH, 8}, + {{'s', 'e', 'p'}, SCR_DK_MONTH, 9}, {{'o', 'c', 't'}, SCR_DK_MONTH, 10}, + {{'n', 'o', 'v'}, SCR_DK_MONTH, 11}, {{'d', 'e', 'c'}, SCR_DK_MONTH, 12}, + {{'a', 'm', 0}, SCR_DK_AMPM, 0}, {{'p', 'm', 0}, SCR_DK_AMPM, 12}, + {{'u', 't', 0}, SCR_DK_ZONE, 0}, {{'u', 't', 'c'}, SCR_DK_ZONE, 0}, + {{'z', 0, 0}, SCR_DK_ZONE, 0}, {{'g', 'm', 't'}, SCR_DK_ZONE, 0}, + {{'c', 'd', 't'}, SCR_DK_ZONE, -5}, {{'c', 's', 't'}, SCR_DK_ZONE, -6}, + {{'e', 'd', 't'}, SCR_DK_ZONE, -4}, {{'e', 's', 't'}, SCR_DK_ZONE, -5}, + {{'m', 'd', 't'}, SCR_DK_ZONE, -6}, {{'m', 's', 't'}, SCR_DK_ZONE, -7}, + {{'p', 'd', 't'}, SCR_DK_ZONE, -7}, {{'p', 's', 't'}, SCR_DK_ZONE, -8}, + {{'t', 0, 0}, SCR_DK_T, 0}, +}; + +static ScrDateTok scr_date_scan_token(ScrDateScan *in) { + ScrDateTok t; + t.tag = SCR_DT_UNKNOWN; + t.length = 1; + t.value = -1; + uint32_t c = in->ch; + if (c == 0) { + t.tag = SCR_DT_END; + return t; + } + if (c >= '0' && c <= '9') { + /* Leading zeros count toward the length; past nine significant + * digits the value stops growing but the digits are consumed. */ + int n = 0, digits = 0, length = 0; + while (in->ch == '0') { + scr_date_scan_advance(in); + length++; + } + while (in->ch >= '0' && in->ch <= '9') { + if (digits < 9) n = n * 10 + (int)(in->ch - '0'); + digits++; + length++; + scr_date_scan_advance(in); + } + t.tag = SCR_DT_NUMBER; + t.value = n; + t.length = length; + return t; + } + if (c == ':' || c == '-' || c == '+' || c == '.' || c == ')') { + scr_date_scan_advance(in); + t.tag = SCR_DT_SYMBOL; + t.value = (int)c; + return t; + } + if (c >= 'A' && !scr_date_is_space(c)) { + uint32_t prefix[3]; + prefix[0] = prefix[1] = prefix[2] = 0; + int length = 0; + while (in->ch >= 'A' && !scr_date_is_space(in->ch)) { + if (length < 3) prefix[length] = in->ch | 0x20; + length++; + scr_date_scan_advance(in); + } + t.tag = SCR_DK_WORD; + t.value = 0; + t.length = length; + for (size_t i = 0; i < sizeof scr_date_keywords / sizeof scr_date_keywords[0]; i++) { + const char *k = scr_date_keywords[i].prefix; + if (prefix[0] == (uint32_t)(unsigned char)k[0] && prefix[1] == (uint32_t)(unsigned char)k[1] && + prefix[2] == (uint32_t)(unsigned char)k[2] && + (length <= 3 || scr_date_keywords[i].type == SCR_DK_MONTH)) { + t.tag = scr_date_keywords[i].type; + t.value = scr_date_keywords[i].value; + break; + } + } + return t; + } + if (scr_date_is_space_or_newline(c)) { + scr_date_scan_advance(in); + t.tag = SCR_DT_SPACE; + return t; + } + if (c == '(') { + int balance = 0; + do { + if (in->ch == ')') balance--; + else if (in->ch == '(') balance++; + scr_date_scan_advance(in); + } while (balance > 0 && in->ch); + return t; + } + scr_date_scan_advance(in); + return t; +} + +static ScrDateTok scr_date_next(ScrDateScan *in) { + ScrDateTok t = in->next; + in->next = scr_date_scan_token(in); + return t; +} + +static bool scr_date_tok_symbol(ScrDateTok t, int c) { + return t.tag == SCR_DT_SYMBOL && t.value == c; +} + +static bool scr_date_skip(ScrDateScan *in, int c) { + if (!scr_date_tok_symbol(in->next, c)) return false; + in->next = scr_date_scan_token(in); + return true; +} + +static bool scr_date_tok_sign(ScrDateTok t) { + return t.tag == SCR_DT_SYMBOL && (t.value == '-' || t.value == '+'); +} + +static bool scr_date_tok_fixed(ScrDateTok t, int length) { + return t.tag == SCR_DT_NUMBER && t.length == length; +} + +static bool scr_date_tok_z(ScrDateTok t) { + return t.tag == SCR_DK_ZONE && t.length == 1 && t.value == 0; +} + +static bool scr_date_between(int x, int lo, int hi) { return x >= lo && x <= hi; } + +typedef struct { + int comp[3], index, named_month; + bool iso; +} ScrDateDay; + +typedef struct { + int comp[4], index, hour_offset; +} ScrDateTime; + +typedef struct { + int sign, hour, minute; +} ScrDateZone; + +static bool scr_date_day_add(ScrDateDay *d, int n) { + if (d->index >= 3) return false; + d->comp[d->index++] = n; + return true; +} + +static bool scr_date_time_add(ScrDateTime *t, int n) { + if (t->index >= 4) return false; + t->comp[t->index++] = n; + return true; +} + +static bool scr_date_time_add_final(ScrDateTime *t, int n) { + if (!scr_date_time_add(t, n)) return false; + while (t->index < 4) t->comp[t->index++] = 0; + return true; +} + +static bool scr_date_time_expecting(const ScrDateTime *t, int n) { + return (t->index == 1 && scr_date_between(n, 0, 59)) || + (t->index == 2 && scr_date_between(n, 0, 59)) || + (t->index == 3 && scr_date_between(n, 0, 999)); +} + +static void scr_date_zone_set(ScrDateZone *z, int hours) { + z->sign = hours < 0 ? -1 : 1; + z->hour = hours * z->sign; + z->minute = 0; +} + +static bool scr_date_zone_expecting(const ScrDateZone *z, int n) { + return z->hour != SCR_DT_NONE && z->minute == SCR_DT_NONE && scr_date_between(n, 0, 59); +} + +/* ReadMilliseconds: the first three significant digits of the numeral. */ +static int scr_date_millis(ScrDateTok t) { + int number = t.value, length = t.length; + if (length == 1) number *= 100; + else if (length == 2) number *= 10; + else if (length > 3) { + static const int factors[] = {10, 100, 1000, 10000, 100000, 1000000}; + if (length > 9) length = 9; + number /= factors[length - 4]; + } + return number; +} + +/* ParseES5DateTime: answers the first token it could not place (the + * legacy grammar resumes there), END after a complete ES5 string, or + * INVALID for a malformed ES5 time part. */ +static ScrDateTok scr_date_parse_es5(ScrDateScan *in, ScrDateDay *day, ScrDateTime *time, + ScrDateZone *tz) { + ScrDateTok invalid; + invalid.tag = SCR_DT_INVALID; + invalid.length = 0; + invalid.value = -1; + if (scr_date_tok_sign(in->next)) { + ScrDateTok sign = scr_date_next(in); + if (!scr_date_tok_fixed(in->next, 6)) return sign; + int s = 44 - sign.value; + int year = scr_date_next(in).value; + if (s < 0 && year == 0) return sign; + scr_date_day_add(day, s * year); + } else if (scr_date_tok_fixed(in->next, 4)) { + scr_date_day_add(day, scr_date_next(in).value); + } else { + return scr_date_next(in); + } + if (scr_date_skip(in, '-')) { + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 1, 12)) { + return scr_date_next(in); + } + scr_date_day_add(day, scr_date_next(in).value); + if (scr_date_skip(in, '-')) { + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 1, 31)) { + return scr_date_next(in); + } + scr_date_day_add(day, scr_date_next(in).value); + } + } + if (in->next.tag != SCR_DK_T) { + if (in->next.tag != SCR_DT_END) return scr_date_next(in); + } else { + scr_date_next(in); + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 24)) return invalid; + bool hour_is_24 = in->next.value == 24; + scr_date_time_add(time, scr_date_next(in).value); + if (!scr_date_skip(in, ':')) return invalid; + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 59) || + (hour_is_24 && in->next.value > 0)) { + return invalid; + } + scr_date_time_add(time, scr_date_next(in).value); + if (scr_date_skip(in, ':')) { + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 59) || + (hour_is_24 && in->next.value > 0)) { + return invalid; + } + scr_date_time_add(time, scr_date_next(in).value); + if (scr_date_skip(in, '.')) { + if (in->next.tag != SCR_DT_NUMBER || (hour_is_24 && in->next.value > 0)) return invalid; + scr_date_time_add(time, scr_date_millis(scr_date_next(in))); + } + } + if (scr_date_tok_z(in->next)) { + scr_date_next(in); + scr_date_zone_set(tz, 0); + } else if (scr_date_tok_sign(in->next)) { + tz->sign = scr_date_next(in).value == '+' ? 1 : -1; + if (scr_date_tok_fixed(in->next, 4)) { + int hourmin = scr_date_next(in).value; + int hour = hourmin / 100, minute = hourmin % 100; + if (!scr_date_between(hour, 0, 23) || !scr_date_between(minute, 0, 59)) return invalid; + tz->hour = hour; + tz->minute = minute; + } else { + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 23)) { + return invalid; + } + tz->hour = scr_date_next(in).value; + if (!scr_date_skip(in, ':')) return invalid; + if (!scr_date_tok_fixed(in->next, 2) || !scr_date_between(in->next.value, 0, 59)) { + return invalid; + } + tz->minute = scr_date_next(in).value; + } + } + if (in->next.tag != SCR_DT_END) return invalid; + } + /* Date-only forms without a zone are UTC; date-times are local. */ + if (tz->hour == SCR_DT_NONE && time->index == 0) scr_date_zone_set(tz, 0); + day->iso = true; + ScrDateTok end; + end.tag = SCR_DT_END; + end.length = 0; + end.value = -1; + return end; +} + +static bool scr_date_day_write(ScrDateDay *d, double *year_out, double *month_out, double *day_out) { + if (d->index < 1) return false; + while (d->index < 3) d->comp[d->index++] = 1; + int year = 0, month, day; + if (d->named_month == SCR_DT_NONE) { + if (d->iso || !scr_date_between(d->comp[0], 1, 31)) { + year = d->comp[0]; + month = d->comp[1]; + day = d->comp[2]; + } else { + month = d->comp[0]; + day = d->comp[1]; + year = d->comp[2]; + } + } else { + month = d->named_month; + /* V8 pads to three components before choosing an order, so a lone + * day after a month name reads the padding 1 as the year (2001). */ + if (!scr_date_between(d->comp[0], 1, 31)) { + year = d->comp[0]; + day = d->comp[1]; + } else { + day = d->comp[0]; + year = d->comp[1]; + } + } + if (!d->iso) { + if (scr_date_between(year, 0, 49)) year += 2000; + else if (scr_date_between(year, 50, 99)) year += 1900; + } + if (!scr_date_between(month, 1, 12) || !scr_date_between(day, 1, 31)) return false; + *year_out = year; + *month_out = month - 1; + *day_out = day; + return true; +} + +static bool scr_date_time_write(ScrDateTime *t, double out[4]) { + while (t->index < 4) t->comp[t->index++] = 0; + int hour = t->comp[0], minute = t->comp[1], second = t->comp[2], millisecond = t->comp[3]; + if (t->hour_offset != SCR_DT_NONE) { + if (!scr_date_between(hour, 0, 12)) return false; + hour %= 12; + hour += t->hour_offset; + } + if (!scr_date_between(hour, 0, 23) || !scr_date_between(minute, 0, 59) || + !scr_date_between(second, 0, 59) || !scr_date_between(millisecond, 0, 999)) { + if (hour != 24 || minute != 0 || second != 0 || millisecond != 0) return false; + } + out[0] = hour; + out[1] = minute; + out[2] = second; + out[3] = millisecond; + return true; +} + +static bool scr_date_zone_write(ScrDateZone *z, double *offset_ms) { + if (z->sign == SCR_DT_NONE) { + *offset_ms = NAN; + return true; + } + if (z->hour == SCR_DT_NONE) z->hour = 0; + if (z->minute == SCR_DT_NONE) z->minute = 0; + unsigned total = (unsigned)z->hour * 3600u + (unsigned)z->minute * 60u; + if (total > (unsigned)INT_MAX) return false; + long long ms = (long long)total * 1000; + *offset_ms = (double)(z->sign < 0 ? -ms : ms); + return true; +} + +static double scr_date_parse_v8(ScrStr *s) { + /* Field-by-field initialization keeps vector constants out of the + * unit's shared constant pool (see scr_date_local_limit). */ + ScrDateScan in; + in.p = (const unsigned char *)s->data; + in.end = in.p + s->len; + in.ch = 0; + in.low = 0; + scr_date_scan_advance(&in); + in.next = scr_date_scan_token(&in); + ScrDateDay day; + day.index = 0; + day.named_month = SCR_DT_NONE; + day.iso = false; + ScrDateTime time; + time.index = 0; + time.hour_offset = SCR_DT_NONE; + ScrDateZone tz; + tz.sign = SCR_DT_NONE; + tz.hour = SCR_DT_NONE; + tz.minute = SCR_DT_NONE; + ScrDateTok token = scr_date_parse_es5(&in, &day, &time, &tz); + if (token.tag == SCR_DT_INVALID) return NAN; + bool has_read_number = day.index != 0; + for (; token.tag != SCR_DT_END; token = scr_date_next(&in)) { + if (token.tag == SCR_DT_NUMBER) { + has_read_number = true; + int n = token.value; + if (scr_date_skip(&in, ':')) { + if (scr_date_skip(&in, ':')) { + /* n:: */ + if (time.index != 0) return NAN; + scr_date_time_add(&time, n); + scr_date_time_add(&time, 0); + } else { + if (!scr_date_time_add(&time, n)) return NAN; + if (scr_date_tok_symbol(in.next, '.')) scr_date_next(&in); + } + } else if (scr_date_skip(&in, '.') && scr_date_time_expecting(&time, n)) { + scr_date_time_add(&time, n); + if (in.next.tag != SCR_DT_NUMBER) return NAN; + int ms = scr_date_millis(scr_date_next(&in)); + if (ms < 0) return NAN; + scr_date_time_add_final(&time, ms); + } else if (scr_date_zone_expecting(&tz, n)) { + tz.minute = n; + } else if (scr_date_time_expecting(&time, n)) { + scr_date_time_add_final(&time, n); + ScrDateTok peek = in.next; + if (peek.tag != SCR_DT_END && peek.tag != SCR_DT_SPACE && !scr_date_tok_z(peek) && + !scr_date_tok_sign(peek)) { + return NAN; + } + } else { + if (!scr_date_day_add(&day, n)) return NAN; + scr_date_skip(&in, '-'); + } + } else if (token.tag >= 0) { + if (token.tag == SCR_DK_AMPM && time.index != 0) { + time.hour_offset = token.value; + } else if (token.tag == SCR_DK_MONTH) { + day.named_month = token.value; + scr_date_skip(&in, '-'); + } else if (token.tag == SCR_DK_ZONE && has_read_number) { + scr_date_zone_set(&tz, token.value); + } else { + /* Garbage words are illegal once a number has been read, and the + * first number must be separated from them. */ + if (has_read_number) return NAN; + if (in.next.tag == SCR_DT_NUMBER) return NAN; + } + } else if (scr_date_tok_sign(token) && + ((tz.hour == 0 && tz.minute == 0) || time.index != 0)) { + /* A UTC offset, only after UTC or a time. */ + tz.sign = token.value == '-' ? -1 : 1; + int n = 0, length = 0; + if (in.next.tag == SCR_DT_NUMBER) { + ScrDateTok number = scr_date_next(&in); + length = number.length; + n = number.value; + } + has_read_number = true; + if (scr_date_tok_symbol(in.next, ':')) { + tz.hour = n; + tz.minute = SCR_DT_NONE; + } else if (length == 2 || length == 1) { + tz.hour = n; + tz.minute = 0; + } else if (length == 4 || length == 3) { + tz.hour = n / 100; + tz.minute = n % 100; + } else { + return NAN; + } + } else if ((scr_date_tok_sign(token) || scr_date_tok_symbol(token, ')')) && has_read_number) { + return NAN; + } + /* Anything else (whitespace, unknown characters) is ignored. */ + } + double year, month, date, hms[4], offset; + if (!scr_date_day_write(&day, &year, &month, &date) || !scr_date_time_write(&time, hms) || + !scr_date_zone_write(&tz, &offset)) { + return NAN; + } + double dayv = scr_date_v8_make_day(year, month, date); + double timev = scr_date_v8_make_time(hms[0], hms[1], hms[2], hms[3]); + if (isnan(offset)) return scr_date_from_local(dayv, timev); + if (!isfinite(dayv) || !isfinite(timev)) return NAN; + double t = timev + dayv * 86400000.0 - offset; + if (!(fabs(t) <= 8640000000000000.0)) return NAN; + return scr_date_new_ms(t); +} + /* ── Date time zone names ─────────────────────────────────────────────── * Long time zone names for Date.prototype.toString and toTimeString. * @@ -11798,6 +12843,29 @@ ScrDyn *scr_dyn_native_date_new(const ScrDyn *arguments) { return scr_dyn_native_date_from_ms(ms); } +/* new Date(value) in JavaScript: a string parses with the full parser; + * every other value takes scr_dyn_native_date_new. */ +ScrDyn *scr_dyn_native_date_new_value(const ScrDyn *arguments) { + if (arguments->v.arr.len == 1 && arguments->v.arr.items[0]->kind == SCR_DYN_STR) + return scr_dyn_native_date_from_ms(scr_date_new_ms(scr_date_parse(arguments->v.arr.items[0]->v.str))); + return scr_dyn_native_date_new(arguments); +} + +/* new Date(year, monthIndex, ...) in JavaScript: ToNumber over the first + * seven arguments in order (a throwing conversion stops there), the + * spec's defaults for the rest, then the local-time field constructor. */ +ScrDyn *scr_dyn_native_date_new_fields(const ScrDyn *arguments) { + double fields[7]; + fields[0] = fields[1] = fields[3] = fields[4] = fields[5] = fields[6] = 0; + fields[2] = 1; + size_t n = arguments->v.arr.len < 7 ? arguments->v.arr.len : 7; + for (size_t i = 0; i < n; i++) { + if (!scr_dyn_number_coerce_js(arguments->v.arr.items[i], &fields[i])) return NULL; + } + return scr_dyn_native_date_from_ms( + scr_date_new_local(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6])); +} + /* The Date.prototype methods scr_native_date_invoke answers. */ static const char *const scr_native_date_methods[] = { "getTime", "valueOf", "toISOString", "toJSON", "toString", scr_date_to_date_string_name, diff --git a/packages/runtime/src/scr_lib.h b/packages/runtime/src/scr_lib.h index 4c973e3e5..bae678292 100644 --- a/packages/runtime/src/scr_lib.h +++ b/packages/runtime/src/scr_lib.h @@ -149,6 +149,11 @@ void scr_process_exit(double code); extern const unsigned char scr_detached_exit; void scr_process_set_exit_code(double code); double scr_process_exit_code_or_zero(void); +/* The writer and the presence test of programs that read process.exitCode: + * the code reads undefined until an assignment (or a nonzero exit status + * the runtime chose, such as an uncaught exception's 1). */ +void scr_process_set_exit_code_tracked(double code); +bool scr_process_has_exit_code(void); /* process._exiting: true once the exit sequence began (process.exit or * the exit-listener runner set the flag). Never throws. */ extern SCR_TL bool scr_process_in_exit; @@ -169,6 +174,21 @@ double scr_process_uptime(void); /* perf_hooks performance.now(): fractional ms since process start (the * uptime anchor — Node's timeOrigin for a compiled program). */ double scr_perf_now(void); +/* perf_hooks event-loop metrics (scr_perf_loop.c). loop_mark records the + * loop's start (the emitted main calls it before the loop runs, only in + * programs that read the metric); loop_start answers it (0 before) and + * idle_time the idle ms since. The eld_* family is monitorEventLoopDelay's + * IntervalHistogram over a handle; eld_percentile expects 0 < p <= 100; eld_stat which: 0 min, 1 max, 2 mean, 3 stddev, + * 4 count, 5 exceeds. */ +void scr_perf_loop_mark(void); +double scr_perf_loop_start(void); +double scr_perf_idle_time(void); +double scr_eld_new(double resolution); +bool scr_eld_enable(double handle); +bool scr_eld_disable(double handle); +void scr_eld_reset(double handle); +double scr_eld_percentile(double handle, double percentile); +double scr_eld_stat(double handle, double which); /* perf_hooks user timing (scr_perf.c): mark, measure, the getEntries * family and the clears take the call's dyn argument vector (borrowed) * and apply Node's validation and errors. mark and measure answer 1 and diff --git a/packages/runtime/src/scr_mailbox.c b/packages/runtime/src/scr_mailbox.c index f983bd289..39e36f4e5 100644 --- a/packages/runtime/src/scr_mailbox.c +++ b/packages/runtime/src/scr_mailbox.c @@ -349,7 +349,9 @@ void scr_channel_set_owner(ScrChannel *channel, int side, ScrMailbox *owner, uns } /* postMessage from `side`: the message (consumed) queues on the other end - * unless either end has closed. */ + * unless either end has closed. Only a message that finds the end's queue + * empty wakes its owner: the owner's loop counts an end with queued + * messages as ready, so it does not wait while the queue is non-empty. */ bool scr_channel_post(ScrChannel *channel, int side, ScrMessage *message, unsigned message_kind, unsigned wake_kind) { ScrMailEvent *event = calloc(1, sizeof(*event)); @@ -363,10 +365,11 @@ bool scr_channel_post(ScrChannel *channel, int side, ScrMessage *message, unsign scr_mail_event_free(event); return false; } + bool first = peer->count == 0; *peer->tail = event; peer->tail = &event->next; peer->count++; - ScrMailbox *wake = peer->owner ? scr_mailbox_retain(peer->owner) : NULL; + ScrMailbox *wake = first && peer->owner ? scr_mailbox_retain(peer->owner) : NULL; scr_channel_unlock(channel); scr_channel_wake(wake, wake_kind); return true; diff --git a/packages/runtime/src/scr_message.c b/packages/runtime/src/scr_message.c index 7c969a2e3..374b5469a 100644 --- a/packages/runtime/src/scr_message.c +++ b/packages/runtime/src/scr_message.c @@ -119,7 +119,7 @@ static void *scr_message_alloc(size_t count, size_t size) { static size_t scr_message_grow(size_t capacity) { if (capacity > SIZE_MAX / 2) scr_trap("scriptc: message too large\n"); - return capacity ? capacity * 2 : 16; + return capacity ? capacity * 2 : 4; } static unsigned char *scr_message_copy(const void *data, size_t length) { @@ -672,6 +672,15 @@ done: free(encoder.identities); free(encoder.stack); if (scr_exc_pending()) { scr_message_free(message); return NULL; } + /* A queued message keeps its nodes until it is received: drop the + * growth slack. */ + if (message->count && message->count < message->capacity) { + ScrMessageNode *nodes = realloc(message->nodes, message->count * sizeof(*nodes)); + if (nodes) { + message->nodes = nodes; + message->capacity = message->count; + } + } return message; } @@ -801,6 +810,12 @@ ScrDyn *scr_message_decode(const ScrMessage *message) { } if (!values[i]->v.arr.properties) values[i]->v.arr.properties = scr_dyn_new_obj(); scr_dyn_obj_set(values[i]->v.arr.properties, property->key, property->length, value); + } else if (node->kind == SCR_MESSAGE_ERROR && property->length == 5 && !memcmp(property->key, "stack", 5)) { + /* The new error's own `stack` accessor stores the value. */ + ScrStr *key = scr_str_new(property->key, property->length); + scr_dyn_key_set(values[i], key, value); + scr_str_release(key); + scr_dyn_release(value); } else scr_dyn_obj_set(values[i], property->key, property->length, value); } if (node->kind == SCR_MESSAGE_ERROR) { diff --git a/packages/runtime/src/scr_net.c b/packages/runtime/src/scr_net.c index f13906454..9902033f2 100644 --- a/packages/runtime/src/scr_net.c +++ b/packages/runtime/src/scr_net.c @@ -21,6 +21,7 @@ #include #include #include +#include /* AF_UNIX: listen(path) and http's socketPath */ #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(_AIX) || defined(__sun) #include #endif @@ -442,6 +443,7 @@ struct ScrNetServer { * '::' when bound_v6, else '0.0.0.0' — Node's own * address().address answers) */ ScrStr *pending_err; /* deferred listen failure */ + ScrStr *unix_path; /* listen(path): the bound socket file, removed at close */ size_t nconns; ScrNetLs conn_ls, err_ls, close_ls, listening_cbs, timeout_ls; /* server.maxConnections (unset = undefined) and its 'drop' listeners */ @@ -532,6 +534,14 @@ struct ScrNetSocket { /* the in-flight dial chain's LAST failure — surfaced (moved into * pending_err) only when the list exhausts; a later success drops it */ ScrStr *dial_err; + /* a dial that is not the TCP sockaddr path (the Unix-socket connect): + * scr_net_sock_dial_peer hands the dial to it; unix_path is its target */ + void (*dial_hook)(ScrNetSocket *s); + ScrStr *unix_path; + /* 'lookup' listeners and the resolutions waiting to reach them (the + * lookup unit's records; see scr_net_lookup_ops) */ + ScrNetLs lookup_ls; + struct ScrNetLookupEv *lookup_evs; ScrNetLs data_ls, end_ls, close_ls, err_ls, conn_ls, timeout_ls, readable_ls; /* Flow control (pause()/resume()): user_paused holds reads OFF even * with consumers (kernel/TCP backpressure is the buffer); flowing @@ -608,6 +618,21 @@ typedef struct ScrNetTunnel { size_t in_len; } ScrNetTunnel; +/* The 'lookup' event's half of the runtime, installed by the first + * 'lookup' registration: programs without a 'lookup' listener never link + * it, and the hook sites below are a NULL check. */ +typedef struct ScrNetLookupOps { + /* a host name resolved (ip/family) or failed (err_msg, ip NULL) */ + void (*record)(ScrNetSocket *s, const char *host, size_t host_len, const char *ip, int family, + const char *err_msg); + void (*flush)(ScrNetSocket *s); /* deliver the recorded resolutions */ + void (*free)(ScrNetSocket *s); +} ScrNetLookupOps; +/* Not static: the compiler must not resolve the calls through it to the + * one implementation, which would link that half into every program. */ +extern SCR_TL const ScrNetLookupOps *scr_net_lookup_ops; +SCR_TL const ScrNetLookupOps *scr_net_lookup_ops = NULL; + static void scr_net_tunnel_free(ScrNetTunnel *t) { if (t == NULL) return; free(t->out); @@ -648,6 +673,7 @@ void scr_net_server_release(ScrNetServer *s) { scr_dyn_release(s->drop_max_connection); scr_str_release(s->bound_host); scr_str_release(s->pending_err); + scr_str_release(s->unix_path); if (s->fd >= 0) scr_net_close_fd_raw(s->fd); #ifdef SCR_RC_AUDIT scr_net_live--; @@ -685,6 +711,9 @@ void scr_net_sock_release(ScrNetSocket *s) { free(s->dial_ips); scr_str_release(s->dial_err); scr_str_release(s->dial_host); + scr_str_release(s->unix_path); + scr_net_ls_drop(&s->lookup_ls); + if (s->lookup_evs != NULL) scr_net_lookup_ops->free(s); scr_net_tunnel_free(s->tunnel); free(s->wbuf); free(s->rbuf); @@ -1385,6 +1414,10 @@ static void scr_net_sock_connect_done(ScrNetSocket *s) { * one): the transport handshake starts, or 'connect' fires and buffered * writes flush. */ static void scr_net_sock_connected(ScrNetSocket *s) { + if (s->lookup_evs != NULL) { + scr_net_lookup_ops->flush(s); + if (scr_exc_pending()) return; + } scr_net_sock_update_write(s); /* keeps the filter iff bytes are buffered */ scr_net_sock_update_read(s); scr_net_sock_touch(s); @@ -1892,6 +1925,11 @@ void scr_net_server_close_direct(ScrNetServer *s, ScrClosure *cb /*moves, nullab scr_net_close_fd_raw(s->fd); /* stops accepting NOW (registrations dropped first) */ s->fd = -1; s->listening = false; +#ifndef _WIN32 + /* a Unix-socket server removes its socket file as it closes, like + * libuv's pipe close */ + if (s->unix_path != NULL) unlink(s->unix_path->data); +#endif } /* A close BEFORE the deferred 'listening' delivered cancels it — Node's * emitListeningNT checks the handle close() nulled, so the listen @@ -1905,6 +1943,9 @@ void scr_net_server_close_direct(ScrNetServer *s, ScrClosure *cb /*moves, nullab s->close_seq = ++scr_net_close_seq; } s->closing = true; /* the sweep fires 'close' once nconns drains */ + /* a server that never listened is not registered yet: Node still emits + * its 'close' (and runs close callbacks) */ + scr_net_server_register(s); /* Node's http server.close() first closes its idle keep-alive * connections; busy ones end after their response (scr_http.c). Only * HTTP connections carry an idle probe, so plain net servers keep @@ -2207,7 +2248,10 @@ ScrStr *scr_net_blocking_lookup(ScrStr *host /*borrowed*/) { * autoselect), and any other name is resolved when the dial starts. */ static void scr_net_sock_set_peer(ScrNetSocket *s, ScrStr *host /*borrowed, nullable*/) { const char *h = host && host->len > 0 ? host->data : "localhost"; - if (strcmp(h, "localhost") == 0) h = "127.0.0.1"; + if (strcmp(h, "localhost") == 0) { + h = "127.0.0.1"; + if (scr_net_lookup_ops != NULL) scr_net_lookup_ops->record(s, "localhost", 9, h, 4, NULL); + } snprintf(s->peer_ip, sizeof s->peer_ip, "%s", h); struct in_addr a4; struct in6_addr a6; @@ -2253,6 +2297,9 @@ static void scr_net_sock_dial_host(ScrNetSocket *s) { out = inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr, ip, sizeof ip); } if (out == NULL) continue; + if (scr_net_lookup_ops != NULL) + scr_net_lookup_ops->record(s, host->data, host->len, ip, ai->ai_family == AF_INET6 ? 6 : 4, + NULL); if (first == AF_UNSPEC) first = ai->ai_family; size_t f = ai->ai_family == first ? 0 : 1; if (fam[f] == NULL) { @@ -2283,6 +2330,7 @@ static void scr_net_sock_dial_host(ScrNetSocket *s) { char msg[320]; snprintf(msg, sizeof msg, "getaddrinfo %s %.*s", code, (int)(host->len < 255 ? host->len : 255), host->data); + if (scr_net_lookup_ops != NULL) scr_net_lookup_ops->record(s, host->data, host->len, NULL, 0, msg); free(ips); scr_str_release(host); s->pending_err = scr_str_new(msg, strlen(msg)); @@ -2304,6 +2352,10 @@ static void scr_net_sock_dial_host(ScrNetSocket *s) { * queue — the socket exists, buffers writes, and dials when a slot * frees). */ static void scr_net_sock_dial_peer(ScrNetSocket *s) { + if (s->dial_hook != NULL) { + s->dial_hook(s); + return; + } if (s->dial_host != NULL) { scr_net_sock_dial_host(s); return; @@ -2552,6 +2604,179 @@ void scr_net_sock_dial_start(ScrNetSocket *s) { scr_net_sock_dial_peer(s); } +/* ── Unix domain sockets ─────────────────────────────────────────────── + * + * listen(path) and the http client's socketPath option. Node (libuv) + * reports these failures with the path where a TCP error names + * host:port: "connect ENOENT /run/x.sock", "listen EADDRINUSE: address + * already in use /run/x.sock". Windows has no Unix-socket support here + * (Node maps paths to named pipes there), so both forms fail with ENOTSUP. */ + +static const char *scr_net_unix_errname(int err) { + switch (err) { + case ENOENT: return "ENOENT"; + case ENOTDIR: return "ENOTDIR"; + case ENAMETOOLONG: return "ENAMETOOLONG"; + case EAGAIN: return "EAGAIN"; + case EPROTOTYPE: return "EPROTOTYPE"; + default: return scr_net_errname(err); + } +} + +static const char *scr_net_unix_reason(int err) { + switch (err) { + case EADDRINUSE: return "address already in use"; + case EACCES: return "permission denied"; + case ENOENT: return "no such file or directory"; + case ENOTDIR: return "not a directory"; + case ENAMETOOLONG: return "name too long"; + case EINVAL: return "invalid argument"; + default: return "operation not supported"; + } +} + +/* Fills a sockaddr_un for `path`; false (with errno set) when it does not + * fit, which libuv reports as ENAMETOOLONG too. */ +#ifndef _WIN32 +static bool scr_net_unix_addr(const ScrStr *path, struct sockaddr_un *sa) { + memset(sa, 0, sizeof *sa); + sa->sun_family = AF_UNIX; + if (path->len >= sizeof sa->sun_path || memchr(path->data, '\0', path->len) != NULL) { + errno = ENAMETOOLONG; + return false; + } + memcpy(sa->sun_path, path->data, path->len); + return true; +} +#endif + +/* The dial_hook of a Unix-socket client: a nonblocking connect that + * completes through the same write-readiness path as TCP. A local + * connect usually settles at once; its failure is the deferred 'error'. */ +static void scr_net_sock_dial_unix(ScrNetSocket *s) { + int err = ENOTSUP; +#ifndef _WIN32 + struct sockaddr_un sa; + if (scr_net_unix_addr(s->unix_path, &sa)) { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + fputs("scriptc: socket() failed\n", stderr); + abort(); + } + scr_net_nonblock(fd); + s->fd = fd; + if (connect(fd, (struct sockaddr *)&sa, sizeof sa) == 0 || errno == EINPROGRESS) { + s->connecting = true; + scr_net_sock_update_write(s); + return; + } + } + err = errno; +#endif + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "connect "); + scr_jb_puts(&b, scr_net_unix_errname(err)); + scr_jb_putc(&b, ' '); + scr_jb_write(&b, s->unix_path->data, s->unix_path->len); + s->pending_err = scr_jb_finish(&b); + s->had_error = true; + s->connecting = false; + scr_net_sock_close_fd(s); +} + +/* A client socket for the Unix socket at `path` (borrowed). `deferred` + * creates it the way scr_net_connect_deferred does: registered and + * buffering, dialed by scr_net_sock_dial_start. +1. */ +ScrNetSocket *scr_net_connect_path(ScrStr *path, bool deferred) { + ScrNetSocket *s = scr_net_sock_new(); + s->unix_path = scr_str_retain(path); + s->dial_hook = &scr_net_sock_dial_unix; + if (!scr_net_poller_init()) { + fputs("scriptc: event poller init failed\n", stderr); + abort(); + } + if (deferred) { + s->connecting = true; + s->dial_deferred = true; + } else { + scr_net_sock_dial_unix(s); + } + scr_net_sock_register(s); + return s; +} + +/* server.address() in programs that listen on a path (the compiler links + * these only there): the Unix-socket path a listening server answers in + * place of the AddressInfo record (+1, "" for a TCP server), and the check + * a record-typed address() read makes first. */ +ScrStr *scr_net_server_unix_path(ScrNetServer *s) { + return s->listening && s->unix_path != NULL ? scr_str_retain(s->unix_path) : scr_str_new("", 0); +} + +void scr_net_server_address_check(ScrNetServer *s) { + if (!s->listening || s->unix_path == NULL) return; + static const char msg[] = + "server.address() of a server listening on a Unix socket is the socket path string; " + "type the result as string | AddressInfo | null (Node's declaration) to read it"; + scr_throw_error_msg(SCR_ERR_ERROR, msg, sizeof msg - 1); +} + +/* server.listen(path[, cb]): binds a Unix socket now; a failure is the + * deferred 'error', success the deferred 'listening', as for a port. */ +void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/, + ScrClosure *cb /*moves, nullable*/) { + /* Node's isPipeName: a string that reads as a non-negative number is a port */ + double port = scr_string_to_number(path); + if (port >= 0) { + scr_net_listen_any(s, port, false, cb); + return; + } + if (cb) scr_net_ls_add(&s->listening_cbs, cb, NULL, true); + if (s->listening || s->close_emitted) return; + if (!scr_net_poller_init()) { + fputs("scriptc: event poller init failed\n", stderr); + abort(); + } + int err = ENOTSUP; +#ifndef _WIN32 + struct sockaddr_un sa; + if (scr_net_unix_addr(path, &sa)) { + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + fputs("scriptc: socket() failed\n", stderr); + abort(); + } + scr_net_nonblock(fd); + if (bind(fd, (struct sockaddr *)&sa, sizeof sa) == 0 && listen(fd, 511) == 0) { + s->fd = fd; + s->listening = true; + s->emit_listening = true; + scr_str_release(s->unix_path); + s->unix_path = scr_str_retain(path); + scr_net_watch_read(fd, s, true); + scr_net_server_register(s); + return; + } + err = errno; + close(fd); + } else { + err = errno; + } +#endif + ScrJsonBuf b; + scr_jb_init(&b); + scr_jb_puts(&b, "listen "); + scr_jb_puts(&b, scr_net_unix_errname(err)); + scr_jb_puts(&b, ": "); + scr_jb_puts(&b, scr_net_unix_reason(err)); + scr_jb_putc(&b, ' '); + scr_jb_write(&b, path->data, path->len); + if (!s->pending_err) s->pending_err = scr_jb_finish(&b); + else scr_str_release(scr_jb_finish(&b)); + scr_net_server_register(s); /* the sweep delivers the failure */ +} + /* ── the caller-lookup dial (net.connect with a lookup option) ───────── * * portless's createLoopbackConnection: connect({ host, port, @@ -4094,6 +4319,136 @@ void scr_net_sock_on_close_dyn(ScrNetSocket *s, const ScrDyn *listener /*borrowe (void *)&scr_net_dyn_fire_arg, once); } +/* ── 'lookup' ─────────────────────────────────────────────────────────── */ + +typedef struct ScrNetLookupEv { + struct ScrNetLookupEv *next; + ScrStr *host; + ScrStr *ip; /* NULL with err_msg */ + int family; + ScrStr *err_msg; /* "getaddrinfo ENOTFOUND host" */ +} ScrNetLookupEv; + +static void scr_net_lookup_record(ScrNetSocket *s, const char *host, size_t host_len, + const char *ip, int family, const char *err_msg) { + ScrNetLookupEv *ev = calloc(1, sizeof *ev); + if (!ev) scr_net_oom(); + ev->host = scr_str_new(host, host_len); + ev->ip = ip != NULL ? scr_str_new(ip, strlen(ip)) : NULL; + ev->family = family; + ev->err_msg = err_msg != NULL ? scr_str_new(err_msg, strlen(err_msg)) : NULL; + ScrNetLookupEv **link = &s->lookup_evs; + while (*link) link = &(*link)->next; + *link = ev; +} + +static void scr_net_lookup_ev_free(ScrNetLookupEv *ev) { + scr_str_release(ev->host); + scr_str_release(ev->ip); + scr_str_release(ev->err_msg); + free(ev); +} + +static void scr_net_lookup_free(ScrNetSocket *s) { + while (s->lookup_evs) { + ScrNetLookupEv *ev = s->lookup_evs; + s->lookup_evs = ev->next; + scr_net_lookup_ev_free(ev); + } +} + +/* Node emits ('lookup', err, address, family, host) from the resolver's + * answer, before it dials: here the resolutions recorded at the dial reach + * the listeners just before 'connect' (or the lookup's 'error'). */ +static void scr_net_lookup_flush(ScrNetSocket *s) { + while (s->lookup_evs && !scr_exc_pending()) { + ScrNetLookupEv *ev = s->lookup_evs; + s->lookup_evs = ev->next; + if (s->lookup_ls.n > 0) { + ScrDyn *args[4]; + if (ev->err_msg != NULL) { + ScrError *e = scr_error_new(SCR_ERR_ERROR, ev->err_msg); + const char *code = strchr(ev->err_msg->data, ' '); + if (code != NULL) { + char buf[32]; + size_t n = strcspn(code + 1, " "); + if (n < sizeof buf) { + memcpy(buf, code + 1, n); + buf[n] = '\0'; + scr_error_set_code(e, buf); + } + } + args[0] = scr_dyn_from_error(e); + scr_error_release(e); + args[1] = scr_dyn_retain(scr_dyn_undefined()); + args[2] = scr_dyn_retain(scr_dyn_undefined()); + } else { + args[0] = scr_dyn_new_null(); + args[1] = scr_dyn_new_str(ev->ip); + args[2] = scr_dyn_new_num((double)ev->family); + } + args[3] = scr_dyn_new_str(ev->host); + ScrNetL *snap; + size_t n = scr_net_ls_snapshot(&s->lookup_ls, &snap); + scr_dyn_this_push(s, SCR_DYNH_NET_SOCKET); + for (size_t i = 0; i < n; i++) { + if (!scr_exc_pending()) { + ScrDyn *fn = scr_dyn_listener_fn(snap[i].cb); + scr_dyn_release(scr_dyn_call(fn, args, 4, "listener")); + scr_dyn_release(fn); + } + scr_closure_release(snap[i].cb); + } + scr_dyn_this_pop(); + free(snap); + for (size_t i = 0; i < 4; i++) scr_dyn_release(args[i]); + } + scr_net_lookup_ev_free(ev); + } +} + +static const ScrNetLookupOps scr_net_lookup_ops_impl = { + &scr_net_lookup_record, + &scr_net_lookup_flush, + &scr_net_lookup_free, +}; + +/* The C main of a program with a 'lookup' listener installs the lookup + * records before any socket dials. */ +void scr_net_lookup_install(void) { scr_net_lookup_ops = &scr_net_lookup_ops_impl; } + +/* socket.on/once('lookup', listener) with a dyn function listener. The + * socket resolved when net.connect ran, before the listener was added; it + * delivers its recorded answers at the connect, like Node's asynchronous + * lookup. */ +void scr_net_sock_on_lookup(ScrNetSocket *s, const ScrDyn *listener /*borrowed*/, bool once) { + if (s->close_emitted) return; + scr_net_ls_add(&s->lookup_ls, scr_dyn_listener_closure_fn(listener, (void *)&scr_net_dyn_fire_arg), + (void *)&scr_net_dyn_fire_arg, once); +} + +/* server.close((err) => ...): the callback runs at 'close' with undefined, + * or with Node's ERR_SERVER_NOT_RUNNING when the server was not listening. */ +static void scr_net_close_not_running_fire(ScrClosure *cb) { + ScrDyn *fn = scr_dyn_listener_fn(cb); + static const char msg[] = "Server is not running."; + ScrStr *text = scr_str_new(msg, sizeof msg - 1); + ScrError *e = scr_error_new(SCR_ERR_ERROR, text); + scr_str_release(text); + scr_error_set_code(e, "ERR_SERVER_NOT_RUNNING"); + ScrDyn *err = scr_dyn_from_error(e); + scr_error_release(e); + scr_dyn_release(scr_dyn_call(fn, &err, 1, "listener")); + scr_dyn_release(err); + scr_dyn_release(fn); +} + +void scr_net_server_close_dyn(ScrNetServer *s, const ScrDyn *listener /*borrowed*/) { + ScrClosure *cb = scr_dyn_listener_closure_fn( + listener, s->listening ? (void *)&scr_dyn_listener_fire0 : (void *)&scr_net_close_not_running_fire); + scr_net_server_close(s, cb); +} + void scr_net_sock_on_write_flush_dyn(ScrNetSocket *s, const ScrDyn *listener /*borrowed*/) { if (s->close_emitted) return; scr_net_ls_add(&s->wcb_ls, scr_dyn_listener_closure_fn(listener, (void *)&scr_net_dyn_fire_arg), @@ -4752,6 +5107,13 @@ static void scr_net_sweep(void) { return; } } + if (sock->lookup_evs != NULL && sock->pending_err) { + scr_net_lookup_ops->flush(sock); + if (scr_exc_pending()) { + scr_net_sock_release(sock); + return; + } + } if (sock->pending_err) { ScrStr *msg = sock->pending_err; sock->pending_err = NULL; @@ -4810,6 +5172,7 @@ static void scr_net_sweep(void) { scr_net_ls_drop(&sock->finish_ls); scr_net_ls_drop(&sock->wcb_ls); scr_net_ls_drop(&sock->ready_ls); + scr_net_ls_drop(&sock->lookup_ls); if (sock->pipe_dst) { scr_net_sock_release(sock->pipe_dst); sock->pipe_dst = NULL; @@ -5011,6 +5374,7 @@ static void scr_net_cleanup_atexit(void) { scr_net_ls_drop(&s->conn_ls); scr_net_ls_drop(&s->timeout_ls); scr_net_ls_drop(&s->readable_ls); + scr_net_ls_drop(&s->lookup_ls); /* Tear down the protocol layer's parser NOW: an in-flight http * exchange (a handler that threw before res.end()) holds req/res, * and the res holds this socket back — a cycle the lean untraced diff --git a/packages/runtime/src/scr_net.h b/packages/runtime/src/scr_net.h index f76613a0e..d78ae48f0 100644 --- a/packages/runtime/src/scr_net.h +++ b/packages/runtime/src/scr_net.h @@ -164,6 +164,11 @@ void scr_net_server_timeout_option_set(ScrNetServer *s, double field, const stru ScrStr *scr_net_server_addr_ip(ScrNetServer *s); /* +1 — address().address */ ScrStr *scr_net_server_addr_family(ScrNetServer *s); /* +1 — address().family */ void scr_net_server_close(ScrNetServer *s, ScrClosure *cb /*moves, nullable*/); +void scr_net_server_close_dyn(ScrNetServer *s, const ScrDyn *listener /*borrowed*/); +void scr_net_sock_on_lookup(ScrNetSocket *s, const ScrDyn *listener /*borrowed*/, bool once); +void scr_net_lookup_install(void); +ScrStr *scr_net_server_unix_path(ScrNetServer *s); /* +1, "" unless listening on a path */ +void scr_net_server_address_check(ScrNetServer *s); /* throws for a Unix-socket server */ void scr_net_server_close_all_connections(ScrNetServer *s); void scr_net_server_close_idle_connections(ScrNetServer *s); ScrDyn *scr_net_server_max_connections_get(ScrNetServer *s); /* +1 */ @@ -237,6 +242,11 @@ bool scr_net_sock_readable(ScrNetSocket *s); /* The deferred dial (the http agent's maxSockets queue): the socket * registers "connecting" and buffers writes; dial_start runs the dial. */ ScrNetSocket *scr_net_connect_deferred(double port, ScrStr *host /*borrowed, nullable*/); /* +1 */ +/* A client socket for the Unix socket at `path`, dialed now or (deferred) + * by scr_net_sock_dial_start. A failure is the deferred 'error'. */ +ScrNetSocket *scr_net_connect_path(ScrStr *path /*borrowed*/, bool deferred); /* +1 */ +/* server.listen(path[, cb]): a Unix-socket server, its file removed at close. */ +void scr_net_listen_path(ScrNetServer *s, ScrStr *path /*borrowed*/, ScrClosure *cb /*moves, nullable*/); void scr_net_sock_dial_start(ScrNetSocket *s); void scr_net_sock_set_timeout(ScrNetSocket *s, double ms); void scr_net_sock_on_timeout(ScrNetSocket *s, ScrClosure *cb /*moves*/, bool once); diff --git a/packages/runtime/src/scr_number.h b/packages/runtime/src/scr_number.h index f43195116..78d19614b 100644 --- a/packages/runtime/src/scr_number.h +++ b/packages/runtime/src/scr_number.h @@ -52,6 +52,8 @@ ScrStr *scr_bool_to_scrstr(bool b); /* interned "true"/"false" */ * adjacent surrogate pairs combine, other surrogates stay lone. Borrowed * args; the string result is +1; neither throws. */ ScrStr *scr_str_from_char_code(ScrArr *codes); +/* String.fromCharCode over n codes in a caller-owned array (no ScrArr). */ +ScrStr *scr_str_from_char_codes(const double *codes, size_t n); /* Single numeric argument; avoids argument packing. Defined in scr_string.c * to share its character cache. Same ToUint16 and lone-surrogate behavior. */ ScrStr *scr_str_from_char_code_one(double code); @@ -116,9 +118,18 @@ ScrStr *scr_date_to_iso(double ms); /* Node's Date.prototype.toUTCString ("Thu, 01 Jan 1970 00:00:00 GMT"; * "Invalid Date" for NaN). Never throws; +1. */ ScrStr *scr_date_to_utc_string(double ms); -/* new Date(dateString).getTime(): the BOUNDED parse (the X509 validity - * shape + ECMA's format with an explicit offset); NaN elsewhere. */ +/* The BOUNDED parse (the X509 validity shape + ECMA's format with an + * explicit offset); NaN elsewhere. */ double scr_date_parse_get_time(ScrStr *s); +/* Date.parse and new Date(dateString): V8's date parser (the ES5 format, + * then its legacy grammar: RFC 1123/2822, Date#toString, "Jan 2, 2024", + * ...). Zone-less dates are local time. Never throws. */ +double scr_date_parse(ScrStr *s); +/* new Date(year, monthIndex, date, hours, minutes, seconds, ms) over + * already-number arguments: V8's MakeDay/MakeTime over LOCAL fields, then + * UTC(t) in the host zone and TimeClip. Never throws. */ +double scr_date_new_local(double y, double mo, double d, + double h, double mi, double s, double ms); /* Date.UTC over already-number arguments (frontend completes the spec's * defaults): MakeDay/MakeTime/TimeClip exactly — 0–99 maps to 1900+year, * month/date rollover, NaN for non-finite parts, out-of-range results, diff --git a/packages/runtime/src/scr_perf_loop.c b/packages/runtime/src/scr_perf_loop.c new file mode 100644 index 000000000..68d3bf10f --- /dev/null +++ b/packages/runtime/src/scr_perf_loop.c @@ -0,0 +1,327 @@ +/* node:perf_hooks event-loop metrics: performance.eventLoopUtilization() + * and monitorEventLoopDelay()'s IntervalHistogram. + * + * Event-loop utilization. Node measures idle time as the time its loop + * spends blocked in the poll phase. A compiled program's main thread is + * busy exactly while it runs on the CPU (or blocks in a synchronous call), + * so the thread's CPU time stands in for Node's active time: idle is the + * wall time since the loop started minus that CPU time. The loop start is + * marked by the emitted main just before the loop runs, and only in + * programs that ask for the metric; before it, utilization is all zeros, + * like Node during the main module's synchronous evaluation. + * + * Event-loop delay. Each histogram samples on an unref'd interval at its + * resolution and records the time between samples in nanoseconds, like + * Node's ELDHistogram (Histogram::RecordDelta). Values land in an + * HdrHistogram-shaped counts array (lowest 1, three significant figures: + * 2048 sub-buckets per power of two), so min, max, mean, stddev and + * percentiles answer the bucket values Node's hdr_histogram answers. */ + +#include "scr_runtime.h" + +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +/* ── event-loop utilization ─────────────────────────────────────────── */ + +static SCR_TL double scr_perf_loop_start_ms = 0; /* performance.now() at loop start */ +static SCR_TL double scr_perf_loop_cpu0_ms = 0; /* thread CPU time at loop start */ + +/* The calling thread's CPU time in milliseconds; 0 where unavailable. */ +static double scr_perf_thread_cpu_ms(void) { +#if defined(_WIN32) + FILETIME created, exited, kernel, user; + if (!GetThreadTimes(GetCurrentThread(), &created, &exited, &kernel, &user)) return 0; + ULARGE_INTEGER k, u; + k.LowPart = kernel.dwLowDateTime; + k.HighPart = kernel.dwHighDateTime; + u.LowPart = user.dwLowDateTime; + u.HighPart = user.dwHighDateTime; + return (double)(k.QuadPart + u.QuadPart) / 1e4; /* 100ns units */ +#elif defined(CLOCK_THREAD_CPUTIME_ID) && !defined(__wasi__) + struct timespec ts; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) != 0) return 0; + return (double)ts.tv_sec * 1000.0 + (double)ts.tv_nsec / 1e6; +#else + return 0; +#endif +} + +void scr_perf_loop_mark(void) { + if (scr_perf_loop_start_ms > 0) return; + scr_perf_loop_cpu0_ms = scr_perf_thread_cpu_ms(); + double now = scr_perf_now(); + /* Node's loopStart is positive once set; a mark at the very first + * microsecond still counts as started. */ + scr_perf_loop_start_ms = now > 0 ? now : 1e-6; +} + +double scr_perf_loop_start(void) { return scr_perf_loop_start_ms; } + +double scr_perf_idle_time(void) { + if (scr_perf_loop_start_ms <= 0) return 0; + double wall = scr_perf_now() - scr_perf_loop_start_ms; + double busy = scr_perf_thread_cpu_ms() - scr_perf_loop_cpu0_ms; + double idle = wall - busy; + if (idle < 0) idle = 0; + if (idle > wall) idle = wall; + return idle; +} + +/* ── HdrHistogram-shaped counts (lowest 1, 3 significant figures) ───── */ + +#define SCR_HDR_SUB_BUCKET_HALF_MAG 10 +#define SCR_HDR_SUB_BUCKET_HALF (1 << SCR_HDR_SUB_BUCKET_HALF_MAG) /* 1024 */ +#define SCR_HDR_SUB_BUCKET_COUNT (SCR_HDR_SUB_BUCKET_HALF * 2) /* 2048 */ +#define SCR_HDR_SUB_BUCKET_MASK ((int64_t)SCR_HDR_SUB_BUCKET_COUNT - 1) + +typedef struct { + int64_t *counts; /* owned, grown on demand */ + size_t counts_len; + int64_t total; + int64_t min_value; /* INT64_MAX while empty */ + int64_t max_value; /* 0 while empty */ + double count; /* Node's count_ (successful records) */ + uint64_t prev_ns; /* RecordDelta's previous sample; 0 = none */ + double resolution; /* ms */ + double timer; /* the sampling interval's handle while enabled, else 0 */ + ScrClosure *tick; /* owned: the sampling callback, identifies this histogram */ + bool enabled; +} ScrEld; + +static int scr_hdr_bucket_index(int64_t value) { + int pow2ceiling = 64 - __builtin_clzll((uint64_t)(value | SCR_HDR_SUB_BUCKET_MASK)); + return pow2ceiling - (SCR_HDR_SUB_BUCKET_HALF_MAG + 1); +} + +static size_t scr_hdr_counts_index(int64_t value) { + int bucket = scr_hdr_bucket_index(value); + int64_t sub = value >> bucket; + return (size_t)(((int64_t)(bucket + 1) << SCR_HDR_SUB_BUCKET_HALF_MAG) + (sub - SCR_HDR_SUB_BUCKET_HALF)); +} + +static int64_t scr_hdr_value_at_index(size_t index) { + int bucket = (int)(index >> SCR_HDR_SUB_BUCKET_HALF_MAG) - 1; + int64_t sub = (int64_t)(index & (SCR_HDR_SUB_BUCKET_HALF - 1)) + SCR_HDR_SUB_BUCKET_HALF; + if (bucket < 0) { + sub -= SCR_HDR_SUB_BUCKET_HALF; + bucket = 0; + } + return sub << bucket; +} + +static int64_t scr_hdr_range_size(int64_t value) { + int bucket = scr_hdr_bucket_index(value); + int64_t sub = value >> bucket; + int adjusted = sub >= SCR_HDR_SUB_BUCKET_COUNT ? bucket + 1 : bucket; + return (int64_t)1 << adjusted; +} + +static int64_t scr_hdr_lowest_equivalent(int64_t value) { + int bucket = scr_hdr_bucket_index(value); + int64_t sub = value >> bucket; + return sub << bucket; +} + +static int64_t scr_hdr_highest_equivalent(int64_t value) { + return scr_hdr_lowest_equivalent(value) + scr_hdr_range_size(value) - 1; +} + +static int64_t scr_hdr_median_equivalent(int64_t value) { + return scr_hdr_lowest_equivalent(value) + (scr_hdr_range_size(value) >> 1); +} + +/* Newton's square root: keeps this unit free of a libm reference. */ +static double scr_eld_sqrt(double x) { + if (!(x > 0)) return x == 0 ? 0 : NAN; + double r = x >= 1 ? x : 1; + for (int i = 0; i < 64; i++) { + double next = 0.5 * (r + x / r); + if (next == r) break; + r = next; + } + return r; +} + +static void scr_eld_record(ScrEld *h, int64_t value) { + if (value < 0) return; + size_t index = scr_hdr_counts_index(value); + if (index >= h->counts_len) { + size_t len = h->counts_len ? h->counts_len : SCR_HDR_SUB_BUCKET_COUNT; + while (len <= index) len *= 2; + int64_t *grown = realloc(h->counts, len * sizeof(int64_t)); + if (grown == NULL) return; + for (size_t i = h->counts_len; i < len; i++) grown[i] = 0; + h->counts = grown; + h->counts_len = len; + } + h->counts[index]++; + h->total++; + if (value != 0 && value < h->min_value) h->min_value = value; + if (value > h->max_value) h->max_value = value; + h->count += 1; +} + +static void scr_eld_clear(ScrEld *h) { + for (size_t i = 0; i < h->counts_len; i++) h->counts[i] = 0; + h->total = 0; + h->min_value = INT64_MAX; + h->max_value = 0; + h->count = 0; + h->prev_ns = 0; +} + +/* ── monitorEventLoopDelay ──────────────────────────────────────────── */ + +static SCR_TL ScrEld **scr_elds; +static SCR_TL size_t scr_elds_len; + +static ScrEld *scr_eld_of(double handle) { + if (!(handle >= 1) || handle > (double)scr_elds_len) return NULL; + return scr_elds[(size_t)handle - 1]; +} + +static void scr_eld_tick(ScrClosure *self) { + for (size_t i = 0; i < scr_elds_len; i++) { + ScrEld *h = scr_elds[i]; + if (h->tick != self) continue; + uint64_t now = scr_hrtime_ns(); + if (h->prev_ns > 0 && now > h->prev_ns) scr_eld_record(h, (int64_t)(now - h->prev_ns)); + h->prev_ns = now; + return; + } +} + +/* Histograms live for the thread's lifetime (Node frees them with their + * JS object); release them when the script ends so their callbacks and + * counts do not outlive it. */ +static void scr_eld_cleanup(void) { + for (size_t i = 0; i < scr_elds_len; i++) { + ScrEld *h = scr_elds[i]; + scr_closure_release(h->tick); + free(h->counts); + free(h); + } + free(scr_elds); + scr_elds = NULL; + scr_elds_len = 0; +} + +/* Node's validateInteger(resolution, 'options.resolution', 1): a catchable + * RangeError (ERR_OUT_OF_RANGE) for a non-integer or a value out of range, + * with the value as util.inspect renders it ("-0" included). */ +static bool scr_eld_resolution_ok(double resolution) { + bool integer = isfinite(resolution) && trunc(resolution) == resolution; + if (integer && resolution >= 1 && resolution <= 9007199254740991.0) return true; + char received[48]; + if (resolution == 0 && signbit(resolution)) + memcpy(received, "-0", 3); + else + scr_num_received(resolution, received); + char msg[192]; + int len = snprintf(msg, sizeof msg, + "The value of \"options.resolution\" is out of range. It must be %s. Received %s", + integer ? ">= 1 && <= 9007199254740991" : "an integer", received); + scr_throw_error_msg_code(SCR_ERR_RANGE, msg, (size_t)len, "ERR_OUT_OF_RANGE"); + return false; +} + +double scr_eld_new(double resolution) { + if (!scr_eld_resolution_ok(resolution)) return 0; + if (scr_elds_len == 0 && scr_elds == NULL) scr_atexit(scr_eld_cleanup); + ScrEld *h = calloc(1, sizeof(ScrEld)); + ScrEld **grown = realloc(scr_elds, (scr_elds_len + 1) * sizeof(ScrEld *)); + if (h == NULL || grown == NULL) { + free(h); + if (grown != NULL) scr_elds = grown; + return 0; + } + scr_elds = grown; + h->min_value = INT64_MAX; + h->resolution = resolution; + h->tick = scr_closure_new((void *)scr_eld_tick, 0); + scr_elds[scr_elds_len++] = h; + return (double)scr_elds_len; +} + +bool scr_eld_enable(double handle) { + ScrEld *h = scr_eld_of(handle); + if (h == NULL || h->enabled) return false; + h->enabled = true; + h->timer = scr_set_interval(scr_closure_retain(h->tick), h->resolution); + scr_timer_unref(h->timer); + return true; +} + +bool scr_eld_disable(double handle) { + ScrEld *h = scr_eld_of(handle); + if (h == NULL || !h->enabled) return false; + h->enabled = false; + scr_clear_interval(h->timer); + h->timer = 0; + return true; +} + +void scr_eld_reset(double handle) { + ScrEld *h = scr_eld_of(handle); + if (h != NULL) scr_eld_clear(h); +} + +/* hdr_value_at_percentile; the compiler checked Node's 0 < p <= 100 range. */ +double scr_eld_percentile(double handle, double percentile) { + ScrEld *h = scr_eld_of(handle); + if (h == NULL || h->total == 0) return 0; + double requested = percentile < 100.0 ? percentile : 100.0; + int64_t wanted = (int64_t)((requested / 100.0) * (double)h->total + 0.5); + if (wanted < 1) wanted = 1; + int64_t seen = 0; + for (size_t i = 0; i < h->counts_len; i++) { + seen += h->counts[i]; + if (seen >= wanted) return (double)scr_hdr_highest_equivalent(scr_hdr_value_at_index(i)); + } + return 0; +} + +/* which: 0 min, 1 max, 2 mean, 3 stddev, 4 count, 5 exceeds. */ +double scr_eld_stat(double handle, double which) { + ScrEld *h = scr_eld_of(handle); + int w = (int)which; + if (h == NULL) return w == 0 ? (double)INT64_MAX : w >= 2 && w <= 3 ? NAN : 0; + switch (w) { + case 0: + if (h->counts_len > 0 && h->counts[0] > 0) return 0; + return h->min_value == INT64_MAX ? (double)INT64_MAX + : (double)scr_hdr_lowest_equivalent(h->min_value); + case 1: + return h->max_value == 0 ? 0 : (double)scr_hdr_highest_equivalent(h->max_value); + case 2: + case 3: { + if (h->total == 0) return NAN; + double sum = 0; + for (size_t i = 0; i < h->counts_len; i++) { + if (h->counts[i] == 0) continue; + sum += (double)h->counts[i] * (double)scr_hdr_median_equivalent(scr_hdr_value_at_index(i)); + } + double mean = sum / (double)h->total; + if (w == 2) return mean; + double geo = 0; + for (size_t i = 0; i < h->counts_len; i++) { + if (h->counts[i] == 0) continue; + double dev = (double)scr_hdr_median_equivalent(scr_hdr_value_at_index(i)) - mean; + geo += dev * dev * (double)h->counts[i]; + } + return scr_eld_sqrt(geo / (double)h->total); + } + case 4: + return h->count; + default: + return 0; + } +} diff --git a/packages/runtime/src/scr_rc.h b/packages/runtime/src/scr_rc.h index eb28fb2e7..8611741da 100644 --- a/packages/runtime/src/scr_rc.h +++ b/packages/runtime/src/scr_rc.h @@ -352,6 +352,10 @@ void scr_sa_thread_starting(void); * pointer, or a process-wide cache keeps an immortal object. Its memory * then stays when it exits (scr_alloc.c). */ void scr_sa_thread_shares(void); +/* The event loop of the calling thread is about to wait with nothing ready + * (true), or is done waiting (false); worker threads are ignored. The + * memory of parked workers goes back while the loop waits (scr_alloc.c). */ +void scr_sa_loop_waiting(bool waiting); #define SCR_SA_FREE_FOREIGN(p) scr_sa_free_foreign(p) #else #define SCR_SA_FREE_FOREIGN(p) free(p) diff --git a/packages/runtime/src/scr_readline.c b/packages/runtime/src/scr_readline.c index e031a4e34..1bc48bbe7 100644 --- a/packages/runtime/src/scr_readline.c +++ b/packages/runtime/src/scr_readline.c @@ -185,11 +185,28 @@ void scr_rl_do_resume(ScrRl *rl) { } } +bool scr_rl_prompt_writable(ScrRl *rl) { + if (!rl->prompt_value) return true; + scr_dyn_arg_type_fail("stringToWrite", "of type string", rl->prompt_value); + return false; +} + static void scr_rl_prompt_now(ScrRl *rl) { if (rl->paused) scr_rl_do_resume(rl); if (scr_exc_pending()) return; if (rl->tty) scr_rl_tty_ops->prompt(rl, false); - else scr_rl_write_out(rl, rl->prompt); + else if (scr_rl_prompt_writable(rl)) scr_rl_write_out(rl, rl->prompt); +} + +/* setPrompt(this[kOldPrompt]) when a question is answered or cancelled */ +static void scr_rl_restore_prompt(ScrRl *rl) { + if (!rl->old_prompt) return; + scr_str_release(rl->prompt); + rl->prompt = rl->old_prompt; + rl->old_prompt = NULL; + if (rl->prompt_value) scr_dyn_release(rl->prompt_value); + rl->prompt_value = rl->old_prompt_value; + rl->old_prompt_value = NULL; } /* ── close ─────────────────────────────────────────────────────────── */ @@ -251,11 +268,7 @@ void scr_rl_on_line(ScrRl *rl, ScrStr *line) { ScrClosure *cb = rl->q_cb; ScrRlLineFn fn = rl->q_fn; rl->q_cb = NULL; - if (rl->old_prompt) { - scr_str_release(rl->prompt); - rl->prompt = rl->old_prompt; - rl->old_prompt = NULL; - } + scr_rl_restore_prompt(rl); fn(cb, line); scr_closure_release(cb); return; @@ -619,6 +632,8 @@ static void scr_rl_cleanup_atexit(void) { if (rl->out) rl->io->release(rl->out); scr_str_release(rl->prompt); if (rl->old_prompt) scr_str_release(rl->old_prompt); + if (rl->prompt_value) scr_dyn_release(rl->prompt_value); + if (rl->old_prompt_value) scr_dyn_release(rl->old_prompt_value); free(rl->line_buf); free(rl->parked); free(rl); @@ -748,7 +763,58 @@ void scr_rl_question(double id, ScrStr *query, ScrClosure *cb /*moves*/, ScrRlLi return; } rl->old_prompt = rl->prompt; + rl->old_prompt_value = rl->prompt_value; rl->prompt = scr_str_retain(query); + rl->prompt_value = NULL; + rl->q_cb = cb; + rl->q_fn = fn; + scr_rl_prompt_now(rl); +} + +/* A prompt that is not a string: String(value) for a primitive, which is + * what the terminal's line refresh and getCursorPos concatenate. An + * object's valueOf/toString would run on every refresh, and a symbol + * throws there, so a terminal interface refuses them (false, the refusal + * pending); another interface only writes the prompt, which throws for + * any value that is not a string, so it keeps ''. +1. */ +static bool scr_rl_prompt_text(ScrRl *rl, ScrDyn *value, ScrStr **out) { + if (scr_rl_primitive_prompt(value)) { + *out = scr_dyn_string_coerce(value); + return true; + } + if (!rl->tty) { + *out = scr_str_new("", 0); + return true; + } + static const char m[] = + "a terminal readline prompt that is an object or a symbol has no lowering"; + scr_throw_error_msg_code(SCR_ERR_ERROR, m, sizeof m - 1, "SC2020"); + return false; +} + +/* question(query, cb) with a checked-dynamic query: a string goes through + * scr_rl_question. Another value becomes the prompt, the callback is + * kept, and the prompt write throws ERR_INVALID_ARG_TYPE on an interface + * that is not a terminal (the question stays pending, as in Node). */ +void scr_rl_question_dyn(double id, ScrDyn *query, ScrClosure *cb /*moves*/, ScrRlLineFn fn) { + if (query->kind == SCR_DYN_STR) { + scr_rl_question(id, query->v.str, cb, fn); + return; + } + ScrRl *rl = scr_rl_find(id); + if (!rl || rl->closed || rl->q_cb) { + scr_rl_question(id, NULL, cb, fn); /* the closed throw, or the re-prompt */ + return; + } + ScrStr *text; + if (!scr_rl_prompt_text(rl, query, &text)) { + scr_closure_release(cb); + return; + } + rl->old_prompt = rl->prompt; + rl->old_prompt_value = rl->prompt_value; + rl->prompt = text; + rl->prompt_value = scr_dyn_retain(query); rl->q_cb = cb; rl->q_fn = fn; scr_rl_prompt_now(rl); @@ -857,11 +923,7 @@ static ScrDyn *scr_rl_ask_abort_thunk(ScrClosure *clo, ScrDyn *const *args, size if (rl && rl->q_cb) { scr_closure_release(rl->q_cb); rl->q_cb = NULL; - if (rl->old_prompt) { - scr_str_release(rl->prompt); - rl->prompt = rl->old_prompt; - rl->old_prompt = NULL; - } + scr_rl_restore_prompt(rl); if (rl->tty) { scr_rl_tty_ops->clear_line(rl); } else { @@ -877,8 +939,11 @@ static ScrDyn *scr_rl_ask_abort_thunk(ScrClosure *clo, ScrDyn *const *args, size /* readline/promises question(query, { signal }): rejects with * ERR_USE_AFTER_CLOSE on a closed interface and with an AbortError when - * the signal is (or becomes) aborted. */ -ScrPromise *scr_rl_question_promise(double id, ScrStr *query, ScrDyn *signal, ScrAbortAddFn add) { + * the signal is (or becomes) aborted. A checked-dynamic query (query_dyn, + * query NULL) that is not a string rejects with what question() throws, + * as Node's promise executor does. */ +static ScrPromise *scr_rl_ask(double id, ScrStr *query, ScrDyn *query_dyn, ScrDyn *signal, + ScrAbortAddFn add) { RlAsk *a = calloc(1, sizeof *a); if (!a) scr_rl_oom(); a->rc = 1; @@ -928,10 +993,29 @@ ScrPromise *scr_rl_question_promise(double id, ScrStr *query, ScrDyn *signal, Sc scr_box_set_ref(cb->caps[0], a); /* the creation reference moves here */ cb->caps[1] = scr_box_new_obj(&scr_rl_dyn_retain_v, &scr_rl_dyn_release_v, NULL); scr_box_set_ref(cb->caps[1], dispose); /* moves (NULL without a signal) */ - scr_rl_question(id, query, cb, &scr_rl_ask_answer); + if (!query_dyn) { + scr_rl_question(id, query, cb, &scr_rl_ask_answer); + return result; + } + scr_rl_ask_retain_v(a); + scr_rl_question_dyn(id, query_dyn, cb, &scr_rl_ask_answer); + if (scr_exc_pending() && !a->settled) { + a->settled = true; + scr_promise_reject_pending(a->p); + } + scr_rl_ask_release_v(a); return result; } +ScrPromise *scr_rl_question_promise(double id, ScrStr *query, ScrDyn *signal, ScrAbortAddFn add) { + return scr_rl_ask(id, query, NULL, signal, add); +} + +ScrPromise *scr_rl_question_promise_dyn(double id, ScrDyn *query, ScrDyn *signal, + ScrAbortAddFn add) { + return scr_rl_ask(id, NULL, query, signal, add); +} + void scr_rl_close(double id) { ScrRl *rl = scr_rl_find(id); if (rl) scr_rl_settle_close(rl); /* twice is a no-op, like Node */ @@ -985,13 +1069,48 @@ void scr_rl_set_prompt(double id, ScrStr *prompt) { if (!rl) return; scr_str_release(rl->prompt); rl->prompt = scr_str_retain(prompt); + if (rl->prompt_value) scr_dyn_release(rl->prompt_value); + rl->prompt_value = NULL; +} + +/* setPrompt(value) with a checked-dynamic value; borrows. */ +void scr_rl_set_prompt_dyn(double id, ScrDyn *prompt) { + if (prompt->kind == SCR_DYN_STR) { + scr_rl_set_prompt(id, prompt->v.str); + return; + } + ScrRl *rl = scr_rl_find(id); + if (!rl) return; + ScrStr *text; + if (!scr_rl_prompt_text(rl, prompt, &text)) return; + scr_str_release(rl->prompt); + rl->prompt = text; + if (rl->prompt_value) scr_dyn_release(rl->prompt_value); + rl->prompt_value = scr_dyn_retain(prompt); } ScrStr *scr_rl_get_prompt(double id) { ScrRl *rl = scr_rl_find(id); + if (rl && rl->prompt_value) { + static const char m[] = "rl.getPrompt() of a prompt that is not a string has no lowering"; + scr_throw_error_msg_code(SCR_ERR_ERROR, m, sizeof m - 1, "SC2020"); + return scr_str_new("", 0); + } return rl ? scr_str_retain(rl->prompt) : scr_str_new("", 0); } +/* Called after a node:readline/promises interface opens: its Interface + * class is not node:readline's, which instanceof tells apart. */ +void scr_rl_mark_promises(double id) { + ScrRl *rl = scr_rl_find(id); + if (rl) rl->promises = true; +} + +bool scr_rl_is_promises(double id) { + ScrRl *rl = scr_rl_find(id); + return rl && rl->promises; +} + bool scr_rl_is_terminal(double id) { ScrRl *rl = scr_rl_find(id); return rl && rl->tty; diff --git a/packages/runtime/src/scr_readline.h b/packages/runtime/src/scr_readline.h index 9385f6dd7..4557dcb4d 100644 --- a/packages/runtime/src/scr_readline.h +++ b/packages/runtime/src/scr_readline.h @@ -47,13 +47,22 @@ void scr_rl_input_error(double id, ScrError *err); void scr_rl_question(double id, ScrStr *query, ScrClosure *cb /*moves*/, void (*fn)(ScrClosure *, ScrStr *)); ScrPromise *scr_rl_question_promise(double id, ScrStr *query, ScrDyn *signal, ScrAbortAddFn add); +/* The same with a checked-dynamic query (borrowed). A query that is not a + * string makes the prompt write throw ERR_INVALID_ARG_TYPE (the promise + * form rejects with it) unless the interface is a terminal; the question + * stays pending, as in Node. */ +void scr_rl_question_dyn(double id, ScrDyn *query, ScrClosure *cb /*moves*/, + void (*fn)(ScrClosure *, ScrStr *)); +ScrPromise *scr_rl_question_promise_dyn(double id, ScrDyn *query, ScrDyn *signal, + ScrAbortAddFn add); void scr_rl_close(double id); void scr_rl_pause(double id); /* may throw ERR_USE_AFTER_CLOSE */ void scr_rl_resume(double id); /* may throw ERR_USE_AFTER_CLOSE */ void scr_rl_prompt(double id); /* may throw ERR_USE_AFTER_CLOSE */ void scr_rl_prompt_with(double id, bool preserve); /* prompt(preserveCursor); may throw */ void scr_rl_set_prompt(double id, ScrStr *prompt); -ScrStr *scr_rl_get_prompt(double id); /* +1 */ +void scr_rl_set_prompt_dyn(double id, ScrDyn *prompt); /* borrows; may throw */ +ScrStr *scr_rl_get_prompt(double id); /* +1; may throw (a prompt that is not a string) */ void scr_rl_write(double id, ScrStr *data); /* may throw ERR_USE_AFTER_CLOSE */ void scr_rl_on(double id, double ev, ScrClosure *cb /*moves*/, void *fn, bool once); /* createInterface's history and editing options, in Node's order: the @@ -83,6 +92,8 @@ double scr_rl_num_prop(double id, double which); * when there is a completer (the inspect unit links only then). */ void scr_rl_terminal(double id, double mode, bool on, ScrDyn *completer, bool promises, ScrStr *(*inspect)(ScrDyn *, double, double)); +void scr_rl_mark_promises(double id); +bool scr_rl_is_promises(double id); bool scr_rl_is_terminal(double id); /* readline.cursorTo / moveCursor / clearLine / clearScreenDown: the data * written (+1; kind 0-3), after the argument checks (may throw), and @@ -135,6 +146,9 @@ void scr_rl_error_thunk_dyn(ScrClosure *cb, ScrError *err); * the input stream or NULL for process.stdin; out the output stream or * NULL (out_stdout picks process.stdout). Both borrowed, like prompt * (NULL: the default "> "). */ +/* A checked-dynamic input or output as its stream (+1; NULL for an output + * of null or undefined); refuses other values (may throw). */ +ScrStream *scr_stream_rl_from_dyn(const ScrDyn *value, bool output); double scr_stream_rl_open(ScrStream *in, ScrStream *out, bool out_stdout, double crlf_delay, ScrStr *prompt, ScrDyn *signal, ScrAbortAddFn add); void scr_stdin_data_thunk0(ScrClosure *cb, ScrBytes *chunk); diff --git a/packages/runtime/src/scr_readline_internal.h b/packages/runtime/src/scr_readline_internal.h index 0fe2e2cb2..49ef42f39 100644 --- a/packages/runtime/src/scr_readline_internal.h +++ b/packages/runtime/src/scr_readline_internal.h @@ -56,11 +56,17 @@ typedef struct ScrRl { bool stdin_in; /* reads process.stdin through the shared consumer */ bool dead; /* stdin only: created after stdin ended */ bool paused; + bool promises; /* created by node:readline/promises */ const ScrRlIo *io; /* the stream side, for a stream input or output */ void *in; /* the input stream (+1), or NULL */ void *out; /* the output stream (+1), or NULL */ bool out_stdout; ScrStr *prompt, *old_prompt; + /* A prompt that is not a string (setPrompt or question with another + * value), +1, or NULL; prompt then holds String(value) for a terminal + * interface. Writing it the way Node's kWriteToOutput does throws + * ERR_INVALID_ARG_TYPE. old_prompt_value is old_prompt's. */ + ScrDyn *prompt_value, *old_prompt_value; ScrClosure *q_cb; /* the pending question's callback (owned) */ ScrRlLineFn q_fn; /* the last promise question (kQuestionReject), which the terminal's @@ -114,6 +120,14 @@ ScrArr *scr_rl_history_of(ScrRl *rl); /* borrowed; created on first use */ void scr_rl_emit(ScrRl *rl, int ev, ScrStr *line, ScrError *err); void scr_rl_emit_history(ScrRl *rl); void scr_rl_write_out(ScrRl *rl, ScrStr *text); +/* kWriteToOutput(prompt)'s validateString: false with ERR_INVALID_ARG_TYPE + * pending when the prompt is not a string. */ +bool scr_rl_prompt_writable(ScrRl *rl); +/* A prompt value whose String() the interface keeps in prompt. */ +static inline bool scr_rl_primitive_prompt(const ScrDyn *v) { + return v->kind == SCR_DYN_NULL || v->kind == SCR_DYN_UNDEF || v->kind == SCR_DYN_BOOL || + v->kind == SCR_DYN_NUM || v->kind == SCR_DYN_BIGINT; +} /* kOnLine: a pending question takes the line, else 'line'. Moves line. */ void scr_rl_on_line(ScrRl *rl, ScrStr *line); void scr_rl_do_pause(ScrRl *rl); diff --git a/packages/runtime/src/scr_readline_tty.c b/packages/runtime/src/scr_readline_tty.c index 38271411f..1c5fcd5f6 100644 --- a/packages/runtime/src/scr_readline_tty.c +++ b/packages/runtime/src/scr_readline_tty.c @@ -2255,7 +2255,7 @@ static void tty_prompt(ScrRl *rl, bool preserve) { bool dumb = term && term->len == 4 && memcmp(term->data, "dumb", 4) == 0; if (term) scr_str_release(term); if (dumb) { - scr_rl_write_out(rl, rl->prompt); + if (scr_rl_prompt_writable(rl)) scr_rl_write_out(rl, rl->prompt); return; } if (!preserve) t->cursor = 0; @@ -2333,6 +2333,12 @@ double scr_rl_cursor_pos(double id, double which) { if (rl->tty) { pos = tty_cursor_pos(rl); } else { + if (rl->prompt_value && !scr_rl_primitive_prompt(rl->prompt_value)) { + static const char m[] = + "rl.getCursorPos() with a prompt that is an object or a symbol has no lowering"; + scr_throw_error_msg_code(SCR_ERR_ERROR, m, sizeof m - 1, "SC2020"); + return 0; + } ScrRlTty probe = {0}; probe.columns = INFINITY; rl->tty = &probe; diff --git a/packages/runtime/src/scr_runtime.h b/packages/runtime/src/scr_runtime.h index 59cafe208..bf01a4a9b 100644 --- a/packages/runtime/src/scr_runtime.h +++ b/packages/runtime/src/scr_runtime.h @@ -75,5 +75,7 @@ #include "scr_assert.h" #include "scr_console.h" #include "scr_fs_cp.h" +#include "scr_wasm_abi.h" +#include "scr_wasm.h" #endif /* SCR_RUNTIME_H */ diff --git a/packages/runtime/src/scr_stream.c b/packages/runtime/src/scr_stream.c index 244e2ec5d..7c501f20c 100644 --- a/packages/runtime/src/scr_stream.c +++ b/packages/runtime/src/scr_stream.c @@ -2254,6 +2254,8 @@ void scr_stream_set_flush(ScrStream *s, ScrClosure *cb /*moves*/, ScrStreamPlain void scr_stream_set_flush_on_prefinish(ScrStream *s) { s->st->flush_on_prefinish = true; } +void scr_stream_set_destroyed(ScrStream *s, bool destroyed) { s->st->destroyed = destroyed; } + void scr_stream_set_native(ScrStream *s, void *state /*moves*/, void (*release)(void *)) { ScrStreamState *st = s->st; if (st->native) st->native_release(st->native); @@ -4728,6 +4730,31 @@ static void scr_stream_rl_hooks_free(void) { } } +/* createInterface's input or output given as a checked-dynamic value (a + * JavaScript function's result, an object's member): the stream (+1). + * An output of null or undefined is NULL (no output). A value that is + * not a stream with a readable (input) or writable (output) side is + * refused. */ +ScrStream *scr_stream_rl_from_dyn(const ScrDyn *value, bool output) { + if (output && (value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_UNDEF)) return NULL; + if (value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 && + !memcmp(value->v.typed_ref.type_key, "object:", 7)) { + ScrStream *stream = scr_dyn_typed_ref_unbox(value); + const ScrVt *vt = stream->vt; + bool duplex = vt == &scr_duplex_vt || vt == &scr_transform_vt || vt == &scr_passthrough_vt || + scr_vt_within(vt, &scr_duplex_vt); + bool ok = duplex || (output ? vt == &scr_writable_vt || scr_vt_within(vt, &scr_writable_vt) + : vt == &scr_readable_vt || scr_vt_within(vt, &scr_readable_vt)); + if (ok) return stream; + value->v.typed_ref.release(stream); + } + static const char in_m[] = "createInterface with an input that is not a readable stream has no lowering"; + static const char out_m[] = "createInterface with an output that is not a writable stream has no lowering"; + if (output) scr_throw_error_msg_code(SCR_ERR_ERROR, out_m, sizeof out_m - 1, "SC2020"); + else scr_throw_error_msg_code(SCR_ERR_ERROR, in_m, sizeof in_m - 1, "SC2020"); + return NULL; +} + double scr_stream_rl_open(ScrStream *in, ScrStream *out, bool out_stdout, double crlf_delay, ScrStr *prompt, ScrDyn *signal, ScrAbortAddFn add) { double id = scr_rl_open(&scr_stream_rl_io, in ? scr_stream_retain(in) : NULL, @@ -6350,15 +6377,19 @@ void scr_stream_install(void) { static void scr_stream_child_read(ScrClosure *cb, ScrStream *s, double size) { (void)cb; (void)s; (void)size; } -static ScrStream *scr_stream_child_readable(void) { - return scr_stream_new_readable(-1, true, true, +static ScrStream *scr_stream_child_readable(const char *cls) { + ScrStream *s = scr_stream_new_readable(-1, true, true, scr_closure_new((void *)scr_stream_child_read, 0), scr_stream_child_read, NULL, NULL); + s->cls = cls; + return s; } -static ScrStream *scr_stream_child_writable(ScrClosure *write, ScrStreamChunkInv write_inv, - ScrClosure *final_cb, ScrStreamPlainInv final_inv) { - return scr_stream_new_writable(-1, true, true, write, write_inv, +static ScrStream *scr_stream_child_writable(const char *cls, ScrClosure *write, + ScrStreamChunkInv write_inv, ScrClosure *final_cb, ScrStreamPlainInv final_inv) { + ScrStream *s = scr_stream_new_writable(-1, true, true, write, write_inv, final_cb, final_inv, NULL, NULL); + s->cls = cls; + return s; } const ScrChildStreamOps scr_stream_child_ops = { scr_stream_child_readable, scr_stream_child_writable, @@ -6422,8 +6453,10 @@ static ScrDyn *scr_stream_view_call(ScrClosure *cb, ScrDyn *const *args, size_t end = option->kind == SCR_DYN_UNDEF || scr_dyn_truthy(option); scr_dyn_release(option); } + /* scr_stream_pipe_general: a JavaScript caller can pipe into the + * converted process.stdout / process.stderr, which a pipe never ends. */ ScrStream *returned = !strcmp(method, "pipe") - ? scr_stream_pipe(stream, destination, end) : scr_stream_unpipe(stream, destination); + ? scr_stream_pipe_general(stream, destination, end) : scr_stream_unpipe(stream, destination); scr_stream_release(returned); if (!strcmp(method, "pipe") && !scr_exc_pending()) result = scr_dyn_retain(first); } @@ -6578,6 +6611,7 @@ ScrStream *scr_crypto_hash_stream(ScrCryptoHash *hash) { scr_hash_stream_step(box), scr_hash_stream_transform, scr_hash_stream_step(box), scr_hash_stream_flush, scr_hash_stream_step(box), scr_hash_stream_destroy); + s->cls = scr_crypto_hash_is_hmac(hash) ? "Hmac" : "Hash"; /* The steps keep the box alive as long as the stream. */ scr_crypto_hash_stream_set(hash, scr_stream_retain(s), scr_stream_release_v, box); scr_box_release(box); @@ -6589,3 +6623,104 @@ ScrStream *scr_crypto_hash_stream_piped(ScrCryptoHash *hash) { if (s) scr_crypto_hash_stream_hold(hash); return s; } + +/* ── the compiler's general stream representation ───────────────────── + * NodeJS.ReadableStream / NodeJS.WritableStream slots that also hold other + * streams compile as this unit's classes; the stdio handles convert into + * them where they are read or flow. Both entry points sit last in the unit + * and reference only global symbols and no new constant data, so programs + * that never convert link the same bytes as before. */ + +/* destroy() on a bridged child stdin closes the pipe, as Node's does. */ +static void scr_stream_child_stdin_destroy(ScrClosure *cb, ScrStream *s, ScrError *err) { + ScrChildWriter *writer = scr_box_get_ref(cb->caps[0]); + scr_child_writer_destroy(writer); + scr_child_writer_release(writer); + scr_stream_destroy_done(s, err ? scr_error_retain(err) : NULL); +} + +/* A child stdio read (the child.stdioStream libCall): the bridged stream + * the checked-dynamic ChildProcess view answers for the same key ("stdin", + * "stdout" or "stderr", passed by the compiled program), so both views + * share one stream per slot. The stdin stream (the only five-byte key) + * gains the destroy hook that closes the pipe. +1, or NULL for an unpiped + * slot or a throw. */ +ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len) { + ScrDyn *handle = scr_dyn_new_handle(child, SCR_DYNH_CHILD); + ScrDyn *box = scr_dyn_handle_ops_of(handle)->get(child, key, key_len); + scr_dyn_release(handle); + if (box == NULL) return NULL; + ScrStream *stream = box->kind == SCR_DYN_TYPED_REF ? scr_dyn_typed_ref_unbox(box) : NULL; + scr_dyn_release(box); + if (stream != NULL && key_len == 5 && stream->st->destroy_cb == NULL) { + ScrChildWriter *writer = scr_child_stdin(child); + if (writer != NULL) { + ScrClosure *destroy = scr_closure_new((void *)scr_stream_child_stdin_destroy, 1); + destroy->caps[0] = scr_box_new_obj(scr_child_writer_retain_v, scr_child_writer_release_v, NULL); + scr_box_set_ref(destroy->caps[0], writer); + stream->st->destroy_cb = destroy; + stream->st->destroy_inv = scr_stream_child_stdin_destroy; + } + } + return stream; +} + +/* process.stdout / process.stderr flowing into a Writable slot (the + * stream.stdioWritable libCall): a Writable whose _write hands each chunk + * to the descriptor through process.stdout.write's synchronous path and + * completes at once, so ordering against console output and direct writes + * is unchanged. The closure keeps the fd as given (1 or 2). Each thread + * keeps one per descriptor, so every conversion of process.stdout answers + * the same stream, as Node's process.stdout is one object: listeners, + * writable state and identity are shared. The cache is released at exit. */ +static SCR_TL ScrStream *scr_stream_stdio_writables[2]; +static void scr_stream_stdio_write(ScrClosure *cb, ScrStream *s, ScrBytes *chunk) { + int fd = (int)scr_box_get_f64(cb->caps[0]); + (void)scr_stdio_write(fd == 2 ? 2 : 1, chunk->data, chunk->len * scr_bytes_elem_size(chunk->elem)); + scr_stream_write_done(s, NULL); +} +static void scr_stream_stdio_writables_free(void) { + for (int i = 0; i < 2; i++) { + ScrStream *s = scr_stream_stdio_writables[i]; + scr_stream_stdio_writables[i] = NULL; + if (s) scr_stream_release(s); + } +} +ScrStream *scr_stream_stdio_writable(double fd) { + int i = fd == 2 ? 1 : 0; + if (scr_stream_stdio_writables[i] == NULL) { + if (scr_stream_stdio_writables[1 - i] == NULL) scr_atexit(scr_stream_stdio_writables_free); + ScrClosure *write = scr_closure_new((void *)scr_stream_stdio_write, 1); + write->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(write->caps[0], i ? 2 : 1); + scr_stream_stdio_writables[i] = scr_stream_new_writable(-1, true, true, write, + scr_stream_stdio_write, NULL, NULL, NULL, NULL); + } + return scr_stream_retain(scr_stream_stdio_writables[i]); +} + +/* readable.pipe() into a Writable slot under the general representation + * (the readable.pipeGeneral libCall), and pipe() called from JavaScript on + * a native stream: Node never ends process.stdout or process.stderr from a + * pipe, so their converted Writable keeps `end` off. */ +ScrStream *scr_stream_pipe_general(ScrStream *src, ScrStream *dst, bool end) { + if (dst == scr_stream_stdio_writables[0] || dst == scr_stream_stdio_writables[1]) end = false; + return scr_stream_pipe(src, dst, end); +} + +/* A pipeline() stage that arrives as a checked-dynamic value (the + * stream.unboxDyn libCall): a boxed native stream unwraps by identity + * (+1); any other value throws the compiled program's not-supported + * message (Node also takes iterables and functions there). */ +ScrStream *scr_stream_unbox_dyn(const ScrDyn *value, const ScrStr *message) { + if (value->kind == SCR_DYN_TYPED_REF && value->v.typed_ref.type_key_len > 7 && + !memcmp(value->v.typed_ref.type_key, "object:", 7)) { + ScrStream *stream = scr_dyn_typed_ref_unbox(value); + const ScrVt *vt = stream->vt; + if ((vt->pre >= scr_readable_vt.pre && vt->pre <= scr_readable_vt.post) || + (vt->pre >= scr_writable_vt.pre && vt->pre <= scr_writable_vt.post)) return stream; + value->v.typed_ref.release(stream); + } + scr_throw_error_msg_code(SCR_ERR_ERROR, message->data, message->len, "SC2020"); + return NULL; +} diff --git a/packages/runtime/src/scr_stream.h b/packages/runtime/src/scr_stream.h index 82727c1dc..78290d8c4 100644 --- a/packages/runtime/src/scr_stream.h +++ b/packages/runtime/src/scr_stream.h @@ -125,6 +125,24 @@ void scr_stream_init_passthrough(ScrStream *s, double rhwm, double whwm, bool au ScrStream *scr_stream_new_readable_dyn(struct ScrDyn *opts); void scr_stream_worker_stdio(void); ScrStream *scr_stream_new_writable_dyn(struct ScrDyn *opts); +/* The general stream representation's conversions (+1): a child's bridged + * stdio stream by key (NULL when unpiped), and process.stdout/stderr as a + * Writable (fd 1 or 2). */ +ScrStream *scr_stream_child_stdio(void *child, const char *key, size_t key_len); +ScrStream *scr_stream_stdio_writable(double fd); +/* readable.pipe() under the general representation: a converted + * process.stdout / process.stderr destination is never ended (dst +1). */ +ScrStream *scr_stream_pipe_general(ScrStream *src, ScrStream *dst, bool end); +/* A boxed native stream out of a checked-dynamic pipeline stage (+1), or + * NULL with `message` thrown as the not-supported Error. */ +ScrStream *scr_stream_unbox_dyn(const struct ScrDyn *value, const ScrStr *message); +/* Checked-dynamic stream slots (scr_stream_dyn.c): the native stream a + * value boxes with the requested side (1 readable, 2 writable; +1, or + * NULL), and readable.pipe()/unpipe() for a destination that is not a + * native stream. */ +ScrStream *scr_stream_dyn_native(const struct ScrDyn *value, double side); +void scr_stream_pipe_dyn(ScrStream *src, struct ScrDyn *src_box, struct ScrDyn *dest, bool end); +void scr_stream_unpipe_dyn(ScrStream *src, struct ScrDyn *dest); ScrStream *scr_stream_new_duplex_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_transform_dyn(struct ScrDyn *opts); ScrStream *scr_stream_new_passthrough_dyn(struct ScrDyn *opts); @@ -431,6 +449,9 @@ void scr_stream_set_flush(ScrStream *s, ScrClosure *cb, ScrStreamPlainInv inv); * released when the stream is freed; scr_stream_native answers it * (borrowed) when it was set with `release`, else NULL. */ void scr_stream_set_flush_on_prefinish(ScrStream *s); +/* `stream.destroyed = value` (scr_dyn_builtin_stream.c): Node's setter + * stores the states' flag without destroying the stream. */ +void scr_stream_set_destroyed(ScrStream *s, bool destroyed); void scr_stream_set_native(ScrStream *s, void *state, void (*release)(void *)); void *scr_stream_native(ScrStream *s, void (*release)(void *)); diff --git a/packages/runtime/src/scr_stream_dyn.c b/packages/runtime/src/scr_stream_dyn.c new file mode 100644 index 000000000..0859c4e07 --- /dev/null +++ b/packages/runtime/src/scr_stream_dyn.c @@ -0,0 +1,389 @@ +/* Checked-dynamic stream slots (the compiler's dyn-streams.ts). + * + * A program whose NodeJS.ReadableStream / NodeJS.WritableStream slots also + * hold JavaScript stream objects compiles them as checked-dynamic values: a + * native stream rides its typed capsule, any other value is the JavaScript + * object itself. Member calls test the value here and take the native + * stream path or the value's own members. Linked only by such programs + * (the stream and dyn-invoke units together). */ +#include "scr_runtime.h" + +#include +#include + +static bool scr_stream_dyn_within(const ScrVt *vt, const ScrVt *cls) { + return vt == cls || (cls->pre <= vt->pre && vt->pre <= cls->post); +} + +/* The native stream a checked-dynamic value boxes (+1), when it has the + * requested side (1 readable, 2 writable; Duplex, Transform and PassThrough + * have both), or NULL for any other value. Never throws. */ +ScrStream *scr_stream_dyn_native(const ScrDyn *value, double side) { + if (value->kind != SCR_DYN_TYPED_REF || value->v.typed_ref.type_key_len <= 7 || + memcmp(value->v.typed_ref.type_key, "object:", 7) != 0) + return NULL; + ScrStream *stream = scr_dyn_typed_ref_unbox(value); + const ScrVt *vt = stream->vt; + bool duplex = scr_stream_dyn_within(vt, &scr_duplex_vt) || + scr_stream_dyn_within(vt, &scr_transform_vt) || + scr_stream_dyn_within(vt, &scr_passthrough_vt); + bool ok = duplex || (side == 1 ? scr_stream_dyn_within(vt, &scr_readable_vt) + : scr_stream_dyn_within(vt, &scr_writable_vt)); + if (ok) return stream; + value->v.typed_ref.release(stream); + return NULL; +} + +/* ── readable.pipe(destination) into a JavaScript stream object ───────── + * Node's Readable.prototype.pipe (lib/internal/streams/readable.js) over a + * native source and a destination that is not a native stream: the same + * listeners in the same order, with the destination's members called + * through the dyn-invoke unit. The context holds the source, its boxed + * value (what 'pipe' and 'unpipe' pass), the destination and the boxed + * listeners until cleanup; the live list breaks the listener cycles at + * exit without calling JavaScript. */ + +enum { + SCR_DYN_PIPE_DATA, + SCR_DYN_PIPE_END, + SCR_DYN_PIPE_UNPIPE, + SCR_DYN_PIPE_DRAIN, + SCR_DYN_PIPE_ERROR, + SCR_DYN_PIPE_CLOSE, + SCR_DYN_PIPE_FINISH, + SCR_DYN_PIPE_ON_UNPIPE, + SCR_DYN_PIPE_COUNT +}; + +typedef struct ScrDynPipe { + size_t rc; + ScrStream *src; /* +1 until dropped */ + ScrDyn *src_box; /* +1 until dropped */ + ScrDyn *dest; /* +1 until dropped */ + ScrDyn *l[SCR_DYN_PIPE_COUNT]; /* +1 boxed listeners, or NULL */ + bool end, cleaned, awaiting_drain, unpiped; + struct ScrDynPipe *live_prev, *live_next; +} ScrDynPipe; + +static SCR_TL ScrDynPipe *scr_dyn_pipe_live = NULL; + +/* A string value for an event name (+1). */ +static ScrDyn *scr_dyn_pipe_str(const char *text, size_t len) { + ScrStr *s = scr_str_new(text, len); + ScrDyn *value = scr_dyn_new_str(s); + scr_str_release(s); + return value; +} +static SCR_TL bool scr_dyn_pipe_atexit_set = false; + +static void *scr_dyn_pipe_retain_v(void *p) { + ((ScrDynPipe *)p)->rc++; + return p; +} + +static void scr_dyn_pipe_drop(ScrDynPipe *p) { + if (p->live_prev) p->live_prev->live_next = p->live_next; + else if (scr_dyn_pipe_live == p) scr_dyn_pipe_live = p->live_next; + if (p->live_next) p->live_next->live_prev = p->live_prev; + p->live_prev = p->live_next = NULL; + for (size_t i = 0; i < SCR_DYN_PIPE_COUNT; i++) { + ScrDyn *l = p->l[i]; + p->l[i] = NULL; + if (l) scr_dyn_release(l); + } + if (p->src) { + ScrStream *src = p->src; + p->src = NULL; + scr_stream_release(src); + } + if (p->src_box) { + ScrDyn *box = p->src_box; + p->src_box = NULL; + scr_dyn_release(box); + } + if (p->dest) { + ScrDyn *dest = p->dest; + p->dest = NULL; + scr_dyn_release(dest); + } +} + +static void scr_dyn_pipe_release_v(void *v) { + ScrDynPipe *p = v; + if (--p->rc > 0) return; + scr_dyn_pipe_drop(p); + free(p); +} + +static void scr_dyn_pipe_atexit(void) { + while (scr_dyn_pipe_live) { + ScrDynPipe *p = scr_dyn_pipe_retain_v(scr_dyn_pipe_live); + scr_dyn_pipe_drop(p); + scr_dyn_pipe_release_v(p); + } +} + +/* destination.method(args...), the result discarded; false on a throw. */ +static bool scr_dyn_pipe_call(ScrDynPipe *p, const char *method, ScrDyn *const *args, size_t argc) { + ScrDyn *result = scr_dyn_invoke(p->dest, method, args, argc, method); + if (result) scr_dyn_release(result); + return !scr_exc_pending(); +} + +static bool scr_dyn_pipe_dest_off(ScrDynPipe *p, const char *event, size_t which) { + if (!p->l[which]) return true; + ScrDyn *args[2] = {scr_dyn_pipe_str(event, strlen(event)), p->l[which]}; + bool ok = scr_dyn_pipe_call(p, "removeListener", args, 2); + scr_dyn_release(args[0]); + return ok; +} + +static void scr_dyn_pipe_src_off(ScrDynPipe *p, const char *event, size_t which) { + if (!p->l[which] || !p->src) return; + ScrStr *name = scr_str_new(event, strlen(event)); + scr_emitter_release(scr_emitter_off_dyn((ScrEmitter *)p->src, name, p->l[which])); + scr_str_release(name); +} + +static void scr_dyn_pipe_ondrain(ScrDynPipe *p) { + p->awaiting_drain = false; + if (!p->src) return; + ScrStr *data = scr_str_new("data", 4); + double listeners = scr_emitter_listener_count((ScrEmitter *)p->src, data); + scr_str_release(data); + if (listeners > 0) scr_stream_release(scr_stream_resume(p->src)); +} + +/* pipe's cleanup(): every listener off, then a pending drain resumes. */ +static void scr_dyn_pipe_cleanup(ScrDynPipe *p) { + if (p->cleaned) return; + if (!scr_dyn_pipe_dest_off(p, "close", SCR_DYN_PIPE_CLOSE) || + !scr_dyn_pipe_dest_off(p, "finish", SCR_DYN_PIPE_FINISH) || + !scr_dyn_pipe_dest_off(p, "drain", SCR_DYN_PIPE_DRAIN) || + !scr_dyn_pipe_dest_off(p, "error", SCR_DYN_PIPE_ERROR) || + !scr_dyn_pipe_dest_off(p, "unpipe", SCR_DYN_PIPE_ON_UNPIPE)) + return; + scr_dyn_pipe_src_off(p, "end", SCR_DYN_PIPE_END); + scr_dyn_pipe_src_off(p, "end", SCR_DYN_PIPE_UNPIPE); + scr_dyn_pipe_src_off(p, "data", SCR_DYN_PIPE_DATA); + p->cleaned = true; + if (p->l[SCR_DYN_PIPE_DRAIN] && p->awaiting_drain) scr_dyn_pipe_ondrain(p); + ScrDynPipe *held = scr_dyn_pipe_retain_v(p); + scr_dyn_pipe_drop(held); + scr_dyn_pipe_release_v(held); +} + +/* src.unpipe(dest): the source pauses (its only pipe goes) and the + * destination hears 'unpipe' with { hasUnpiped: false }. */ +static void scr_dyn_pipe_unpipe(ScrDynPipe *p) { + if (p->unpiped || !p->src) return; + p->unpiped = true; + scr_stream_release(scr_stream_pause(p->src)); + ScrDyn *info = scr_dyn_new_obj(); + scr_dyn_obj_set(info, "hasUnpiped", 10, scr_dyn_new_bool(false)); + ScrDyn *args[3] = {scr_dyn_pipe_str("unpipe", 6), scr_dyn_retain(p->src_box), info}; + (void)scr_dyn_pipe_call(p, "emit", args, 3); + for (size_t i = 0; i < 3; i++) scr_dyn_release(args[i]); +} + +static ScrDyn *scr_dyn_pipe_listener(ScrDynPipe *p, int kind); + +/* pipe's pause(): the source stops until the destination drains. */ +static void scr_dyn_pipe_pause(ScrDynPipe *p) { + if (!p->cleaned) { + p->awaiting_drain = true; + scr_stream_release(scr_stream_pause(p->src)); + } + if (!p->l[SCR_DYN_PIPE_DRAIN] && p->dest) { + p->l[SCR_DYN_PIPE_DRAIN] = scr_dyn_pipe_listener(p, SCR_DYN_PIPE_DRAIN); + ScrDyn *args[2] = {scr_dyn_pipe_str("drain", 5), p->l[SCR_DYN_PIPE_DRAIN]}; + (void)scr_dyn_pipe_call(p, "on", args, 2); + scr_dyn_release(args[0]); + } +} + +static ScrDyn *scr_dyn_pipe_thunk(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrDynPipe *p = scr_box_get_ref(clo->caps[0]); /* +1 */ + int kind = (int)scr_box_get_f64(clo->caps[1]); + ScrDyn *first = argc ? args[0] : scr_dyn_undefined(); + switch (kind) { + case SCR_DYN_PIPE_DATA: { + if (!p->dest) break; + ScrDyn *ret = scr_dyn_invoke(p->dest, "write", &first, 1, "write"); + if (ret) { + if (ret->kind == SCR_DYN_BOOL && !scr_dyn_truthy(ret)) scr_dyn_pipe_pause(p); + scr_dyn_release(ret); + } + break; + } + case SCR_DYN_PIPE_END: + if (p->dest) (void)scr_dyn_pipe_call(p, "end", NULL, 0); + break; + case SCR_DYN_PIPE_UNPIPE: + scr_dyn_pipe_unpipe(p); + break; + case SCR_DYN_PIPE_DRAIN: + scr_dyn_pipe_ondrain(p); + break; + case SCR_DYN_PIPE_ERROR: { + if (!p->dest) break; + ScrDyn *dest = scr_dyn_retain(p->dest); + ScrDyn *self = scr_dyn_retain(p->l[SCR_DYN_PIPE_ERROR]); + scr_dyn_pipe_unpipe(p); + if (!scr_exc_pending()) { + ScrDyn *off[2] = {scr_dyn_pipe_str("error", 5), self}; + ScrDyn *r = scr_dyn_invoke(dest, "removeListener", off, 2, "removeListener"); + if (r) scr_dyn_release(r); + ScrDyn *count = scr_exc_pending() ? NULL : scr_dyn_invoke(dest, "listenerCount", off, 1, "listenerCount"); + if (count && count->kind == SCR_DYN_NUM && !scr_dyn_truthy(count)) { + ScrDyn *emit[2] = {off[0], first}; + r = scr_dyn_invoke(dest, "emit", emit, 2, "emit"); + if (r) scr_dyn_release(r); + } + if (count) scr_dyn_release(count); + scr_dyn_release(off[0]); + } + scr_dyn_release(self); + scr_dyn_release(dest); + break; + } + case SCR_DYN_PIPE_CLOSE: + if (scr_dyn_pipe_dest_off(p, "finish", SCR_DYN_PIPE_FINISH)) scr_dyn_pipe_unpipe(p); + break; + case SCR_DYN_PIPE_FINISH: + if (scr_dyn_pipe_dest_off(p, "close", SCR_DYN_PIPE_CLOSE)) scr_dyn_pipe_unpipe(p); + break; + case SCR_DYN_PIPE_ON_UNPIPE: { + ScrDyn *info = argc > 1 ? args[1] : NULL; + if (p->src_box && scr_dyn_strict_eq(first, p->src_box) && info && info->kind == SCR_DYN_OBJ) { + ScrDyn *has = scr_dyn_obj_read(info, "hasUnpiped", 10); + if (!has || scr_exc_pending()) { /* a throwing getter */ + if (has) scr_dyn_release(has); + break; + } + bool unpiped = has->kind == SCR_DYN_BOOL && !scr_dyn_truthy(has); + scr_dyn_release(has); + if (unpiped) { + scr_dyn_obj_set(info, "hasUnpiped", 10, scr_dyn_new_bool(true)); + scr_dyn_pipe_cleanup(p); + } + } + break; + } + } + scr_dyn_pipe_release_v(p); + return scr_exc_pending() ? NULL : scr_dyn_retain(scr_dyn_undefined()); +} + +static ScrDyn *scr_dyn_pipe_listener(ScrDynPipe *p, int kind) { + ScrClosure *clo = scr_closure_new((void *)scr_dyn_pipe_thunk, 2); + clo->caps[0] = scr_box_new_obj(scr_dyn_pipe_retain_v, scr_dyn_pipe_release_v, NULL); + scr_box_set_ref(clo->caps[0], scr_dyn_pipe_retain_v(p)); + clo->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(clo->caps[1], (double)kind); + return scr_dyn_new_func(clo, scr_dyn_pipe_thunk, 1, "native pipe listener", NULL); +} + +/* destination.on / once / prependListener(event, listener) */ +static bool scr_dyn_pipe_listen(ScrDynPipe *p, const char *method, const char *event, int kind) { + p->l[kind] = scr_dyn_pipe_listener(p, kind); + ScrDyn *args[2] = {scr_dyn_pipe_str(event, strlen(event)), p->l[kind]}; + bool ok = scr_dyn_pipe_call(p, method, args, 2); + scr_dyn_release(args[0]); + return ok; +} + +static void scr_dyn_pipe_src_on(ScrDynPipe *p, const char *event, int kind, bool once) { + p->l[kind] = scr_dyn_pipe_listener(p, kind); + ScrStr *name = scr_str_new(event, strlen(event)); + scr_stream_release(scr_stream_on_dyn(p->src, name, p->l[kind], once, false)); + scr_str_release(name); +} + +/* readable.pipe(destination, { end }) for a destination that is not a + * native stream. src and src_box (the source's boxed value) and dest are + * borrowed. Throws what the destination's members throw. */ +void scr_stream_pipe_dyn(ScrStream *src, ScrDyn *src_box, ScrDyn *dest, bool end) { + ScrDynPipe *p = calloc(1, sizeof *p); + if (!p) abort(); + p->rc = 1; + p->src = scr_stream_retain(src); + p->src_box = scr_dyn_retain(src_box); + p->dest = scr_dyn_retain(dest); + p->end = end; + p->live_next = scr_dyn_pipe_live; + if (scr_dyn_pipe_live) scr_dyn_pipe_live->live_prev = p; + scr_dyn_pipe_live = p; + if (!scr_dyn_pipe_atexit_set) { + scr_dyn_pipe_atexit_set = true; + scr_atexit(scr_dyn_pipe_atexit); + } + /* The source's end ends the destination (end: true) or unpipes it. */ + scr_dyn_pipe_src_on(p, "end", end ? SCR_DYN_PIPE_END : SCR_DYN_PIPE_UNPIPE, true); + if (scr_exc_pending() || !scr_dyn_pipe_listen(p, "on", "unpipe", SCR_DYN_PIPE_ON_UNPIPE)) goto out; + scr_dyn_pipe_src_on(p, "data", SCR_DYN_PIPE_DATA, false); + if (scr_exc_pending()) goto out; + { + /* A throwing getter's exception propagates out of pipe(), as Node's + * prependListener helper reads the member the same way. */ + ScrDyn *prepend = scr_dyn_obj_read(dest, "prependListener", 15); + if (!prepend || scr_exc_pending()) { + if (prepend) scr_dyn_release(prepend); + goto out; + } + bool has_prepend = prepend->kind == SCR_DYN_FUNC; + scr_dyn_release(prepend); + if (!scr_dyn_pipe_listen(p, has_prepend ? "prependListener" : "on", "error", SCR_DYN_PIPE_ERROR)) + goto out; + } + if (!scr_dyn_pipe_listen(p, "once", "close", SCR_DYN_PIPE_CLOSE) || + !scr_dyn_pipe_listen(p, "once", "finish", SCR_DYN_PIPE_FINISH)) + goto out; + { + ScrDyn *args[2] = {scr_dyn_pipe_str("pipe", 4), scr_dyn_retain(src_box)}; + bool ok = scr_dyn_pipe_call(p, "emit", args, 2); + scr_dyn_release(args[0]); + scr_dyn_release(args[1]); + if (!ok) goto out; + } + { + ScrDyn *need = scr_dyn_obj_read(dest, "writableNeedDrain", 17); + if (!need || scr_exc_pending()) { + if (need) scr_dyn_release(need); + goto out; + } + bool pause = need->kind == SCR_DYN_BOOL && scr_dyn_truthy(need); + scr_dyn_release(need); + if (pause) scr_dyn_pipe_pause(p); + else if (scr_stream_is_paused(src)) + scr_stream_release(scr_stream_resume(src)); + } +out: + scr_dyn_pipe_release_v(p); +} + +/* readable.unpipe(destination) for a destination that is not a native + * stream: the pipe scr_stream_pipe_dyn registered from src into it + * unpipes, as Node's unpipe does for a piped destination; nothing happens + * when there is none. An undefined destination (unpipe() without one) + * unpipes every JavaScript destination of src, oldest pipe first. + * Borrowed. */ +void scr_stream_unpipe_dyn(ScrStream *src, ScrDyn *dest) { + bool all = dest == NULL || dest->kind == SCR_DYN_UNDEF; + for (;;) { + /* The live list is newest first; the oldest matching pipe goes next. + * Unpiping runs JavaScript ('unpipe' listeners), so each round walks + * the list again. */ + ScrDynPipe *match = NULL; + for (ScrDynPipe *p = scr_dyn_pipe_live; p; p = p->live_next) { + if (p->src != src || p->unpiped || p->cleaned || !p->dest) continue; + if (!all && !scr_dyn_strict_eq(p->dest, dest)) continue; + match = p; + } + if (!match) return; + ScrDynPipe *held = scr_dyn_pipe_retain_v(match); + scr_dyn_pipe_unpipe(held); + scr_dyn_pipe_release_v(held); + if (!all || scr_exc_pending()) return; + } +} diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 8358658b5..77c020ee0 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -115,6 +115,15 @@ static SCR_TL bool scr_sidx_cleanup_registered; * moves one active entry; only clearing or initializing changes the count. */ static SCR_TL size_t scr_sidx_active; +/* UTF-16 length table (scr_str_utf16_len below). */ +#define SCR_U16LEN_N 1024 +typedef struct { + const ScrStr *s; + size_t units; +} ScrU16Len; +/* Allocated by the first length it records; freed with the index. */ +static SCR_TL ScrU16Len *scr_u16len_tab; + static void scr_sidx_clear(ScrSidx *e, unsigned *live) { if (e->s) { scr_sidx_active--; @@ -137,6 +146,8 @@ static void scr_sidx_reset_all(void) { } scr_sidx_sparse_clock = 0; scr_sidx_cursor_clock = 0; + free(scr_u16len_tab); + scr_u16len_tab = NULL; } static void scr_sidx_register_cleanup(void) { @@ -190,7 +201,27 @@ static __attribute__((noinline)) void scr_sidx_purge_active(const ScrStr *s) { } } +/* UTF-16 lengths of non-ASCII strings longer than 64 bytes, direct-mapped + * by address. The four index entries above evict each other when a + * program reads the lengths of more long texts than that in turn (every + * source file of a program), and each miss walks the whole string. A slot + * is dropped wherever the index drops its facts: when the string is freed + * or reallocated, and when an append changes it in place. */ + +/* The slot for s, or NULL before any length is recorded. */ +static inline ScrU16Len *scr_u16len_slot(const ScrStr *s) { + if (!scr_u16len_tab) return NULL; + uint64_t h = ((uint64_t)(uintptr_t)s >> 4) * UINT64_C(0x9E3779B97F4A7C15); + return &scr_u16len_tab[h >> 54]; +} + +static inline void scr_u16len_forget(const ScrStr *s) { + ScrU16Len *slot = scr_u16len_slot(s); + if (slot && slot->s == s) slot->s = NULL; +} + static void scr_sidx_purge(const ScrStr *s) { + scr_u16len_forget(s); if (scr_sidx_active) scr_sidx_purge_active(s); } @@ -288,6 +319,7 @@ static ScrSidx *scr_sidx_for(const ScrStr *s) { * receiver may move from the cursor tier to the sparse tier on its next * lookup, so invalidate either possible entry. */ static void scr_sidx_concat_append(const ScrStr *s, size_t oldlen) { + scr_u16len_forget(s); if (scr_sidx_active == 0) return; for (int i = 0; i < SCR_SIDX_N; i++) { ScrSidx *entries[] = {&scr_sidx_sparse_tab[i], &scr_sidx_cursor_tab[i]}; @@ -1400,6 +1432,8 @@ static void scr_sidx_materialize_identity_prefix(const ScrStr *s, ScrSidx *e) { static size_t scr_sidx_len(const ScrStr *s, ScrSidx *e) { if (s->ascii) return s->len; if (e->u16len != SCR_U16_UNKNOWN) return e->u16len; + ScrU16Len *slot = scr_u16len_slot(s); + if (slot && slot->s == s) return e->u16len = slot->units; while (e->indexed_cb < s->len) scr_sidx_extend_one(s, e); scr_sidx_finish(s, e); return s->ascii ? s->len : e->u16len; @@ -1899,7 +1933,20 @@ double scr_str_utf16_len(ScrStr *s) { ScrSidx *last = scr_sidx_last; /* a scan's receiver, length known */ if (last && last->s == s && last->u16len != SCR_U16_UNKNOWN) return (double)(int64_t)last->u16len; /* lengths fit int64_t */ - return (double)scr_sidx_len(s, scr_sidx(s)); + ScrU16Len *slot = scr_u16len_slot(s); + if (slot && slot->s == s) return (double)(int64_t)slot->units; + size_t units = scr_sidx_len(s, scr_sidx(s)); + /* The walk may prove the string ASCII, which needs no slot. */ + if (s->ascii) return (double)(int64_t)units; + if (!scr_u16len_tab) { + /* Without memory the lengths are counted on each read, as before. */ + scr_u16len_tab = calloc(SCR_U16LEN_N, sizeof *scr_u16len_tab); + if (!scr_u16len_tab) return (double)(int64_t)units; + scr_sidx_register_cleanup(); + slot = scr_u16len_slot(s); + } + *slot = (ScrU16Len){s, units}; + return (double)(int64_t)units; } double scr_str_char_code_at(ScrStr *s, double i) { diff --git a/packages/runtime/src/scr_test.c b/packages/runtime/src/scr_test.c index 7c5c243d8..762e25c61 100644 --- a/packages/runtime/src/scr_test.c +++ b/packages/runtime/src/scr_test.c @@ -983,9 +983,23 @@ static void scr_test_print_failure(ScrTestCtx *t) { } fputc('\n', stdout); if (t->err) { - /* Node prints the failure inspected: a thrown string is quoted. */ - ScrStr *msg = t->err->kind == SCR_EXC_STR ? scr_insp_str(t->err->payload) - : scr_caught_to_string(t->err); + /* Node prints the failure inspected: a thrown string is quoted, and + * numbers, booleans and checked-dynamic values (thrown records, arrays, + * null and undefined) print as util.inspect renders them. Errors and + * other payloads print their string form. */ + const ScrCaught *err = t->err; + ScrStr *msg; + if (err->kind == SCR_EXC_STR) { + msg = scr_insp_str(err->payload); + } else if (err->kind == SCR_EXC_F64 || err->kind == SCR_EXC_BOOL || + ((err->kind == SCR_EXC_REF || err->kind == SCR_EXC_PRIMITIVE_REF) && + err->retain_fn == scr_dyn_retain_v)) { + ScrDyn *value = scr_caught_to_dyn(err); + msg = scr_insp_dyn(value, 0, 2); + scr_dyn_release(value); + } else { + msg = scr_caught_to_string(err); + } if (msg) { const char *p = msg->data; const char *end = msg->data + msg->len; diff --git a/packages/runtime/src/scr_tls.c b/packages/runtime/src/scr_tls.c index 464b19736..804e51d69 100644 --- a/packages/runtime/src/scr_tls.c +++ b/packages/runtime/src/scr_tls.c @@ -1512,6 +1512,74 @@ static ScrBytes *scr_tls_pem_dyn(const ScrDyn *v, const char *what, bool concat) return NULL; } +/* https.request/get with a runtime options record: the http walk, then + * the TLS options the literal https path lowers (rejectUnauthorized, ca, + * servername). Other tls.connect options fence when they carry a value. */ +static const char *const SCR_HTTPS_REQ_FENCED_OPTIONS[] = { + "ALPNProtocols", "cert", "checkServerIdentity", "ciphers", "crl", "dhparam", "ecdhCurve", + "honorCipherOrder", "key", "maxVersion", "minVersion", "passphrase", "pfx", "secureContext", + "secureOptions", "secureProtocol", "session", "sessionIdContext", "sigalgs", NULL, +}; + +ScrHttpClientReq *scr_https_request_dyn(const ScrDyn *opts /*borrowed*/, bool auto_end, + ScrClosure *cb /*moves, nullable*/, ScrHttpRespFn fn, + ScrDyn *(*add_abort)(ScrDyn *signal, ScrDyn *listener)) { + ScrHttpReqOpts o; + if (!scr_http_req_opts_read(opts, true, "https.request", &o)) { + if (cb) scr_closure_release(cb); + return NULL; + } + const ScrDyn *rec = o.view != NULL ? o.view : opts; + bool reject = true; + ScrBytes *ca = NULL; + ScrStr *servername = NULL; + bool ok = true; + if (rec != NULL && rec->kind == SCR_DYN_OBJ) { + for (size_t i = 0; ok && SCR_HTTPS_REQ_FENCED_OPTIONS[i] != NULL; i++) { + const char *key = SCR_HTTPS_REQ_FENCED_OPTIONS[i]; + const ScrDyn *v = scr_dyn_obj_get(rec, key, strlen(key)); + if (v != NULL && scr_tls_dyn_truthy(v)) { + scr_http_req_opts_fence("https.request", key, + "rejectUnauthorized, ca, and servername are the supported TLS options"); + ok = false; + } + } + const ScrDyn *r = ok ? scr_dyn_obj_get(rec, "rejectUnauthorized", 18) : NULL; + if (r != NULL && r->kind == SCR_DYN_BOOL && !r->v.b) reject = false; + const ScrDyn *c = ok ? scr_dyn_obj_get(rec, "ca", 2) : NULL; + if (c != NULL && c->kind != SCR_DYN_UNDEF && c->kind != SCR_DYN_NULL) { + ca = scr_tls_pem_dyn(c, "an https.request 'ca' option", true); + if (ca == NULL) ok = false; + } + const ScrDyn *sn = ok ? scr_dyn_obj_get(rec, "servername", 10) : NULL; + if (sn != NULL && sn->kind != SCR_DYN_UNDEF && sn->kind != SCR_DYN_NULL) { + if (sn->kind != SCR_DYN_STR) { + scr_dyn_prop_type_fail("options.servername", "of type string", sn); + ok = false; + } else { + servername = scr_str_retain(sn->v.str); + } + } + } + if (!ok) { + if (ca) scr_bytes_release(ca); + scr_http_req_opts_free(&o); + if (cb) scr_closure_release(cb); + return NULL; + } + const char *ca_data = ca ? (const char *)ca->data : NULL; + size_t ca_len = ca ? ca->len : 0; + ScrTlsCli *cli = scr_tls_cli_for(o.host, reject, ca_data, ca_len, servername); + ScrStr *key = scr_tls_pool_key(o.host, reject, ca_data, ca_len, servername); + scr_str_release(servername); + ScrHttpClientReq *req = scr_http_req_opts_send(&o, auto_end, cb, fn, add_abort, + &scr_tls_cli_wrap, cli, key); + scr_str_release(key); + if (ca) scr_bytes_release(ca); + scr_http_req_opts_free(&o); + return req; +} + /* The literal path's runtime-valued cert/key extraction (the compiler's * tls.pemDyn libCall): `what` arrives precomposed. undefined throws — the * literal walk required the member's presence. */ diff --git a/packages/runtime/src/scr_wasm.c b/packages/runtime/src/scr_wasm.c new file mode 100644 index 000000000..121acacbb --- /dev/null +++ b/packages/runtime/src/scr_wasm.c @@ -0,0 +1,2553 @@ +/* The JavaScript WebAssembly API over modules translated to native code + * when the program was built (scr_wasm_abi.h is the contract with the + * translator). + * + * WebAssembly.compile, instantiate, validate and new Module hash the bytes + * they are given and look the module up by SHA-256 and length in + * scr_wasm_registry, which the compiler emits; bytes that were not + * translated at build time reject with a CompileError that says so. + * Nothing is interpreted or compiled at run time, and a translated + * module's bytes were validated when the program was built. + * + * Values: Module, Instance, Memory, Table and Global are native handles in + * the checked-dynamic representation (the SCR_DYNH_WASM_* tags, installed + * per thread on first use). An instance's exports object is a frozen + * null-prototype object whose functions call the translated code through + * its thunks under a trap guard; imports are checked-dynamic callables + * called with `this` undefined, ToJSValue arguments and ToWebAssemblyValue + * results. + * + * Traps. Translated code calls scr_wasm_trap, which siglongjmps to the + * innermost export entry (scr_wasm_enter); the entry throws Node's + * WebAssembly.RuntimeError. With guard pages, an out-of-bounds access + * faults inside the memory's reservation and stack exhaustion faults near + * the stack pointer: the SIGSEGV/SIGBUS handler (installed when the first + * guarded instance is created, on a per-thread alternate stack, chaining + * to the previous handler for every other fault) moves the interrupted + * context to a trampoline that traps the same way, so the handler itself + * returns normally. Async fibers' stacks have no guard page of their own, + * so the first export call on one protects the lowest page of its mapping + * (scr_wasm_guard_fiber). An exception thrown by an import unwinds the + * translated frames the same way and propagates unchanged. Translated + * frames own nothing, so abandoning them leaks nothing; the module's own + * shadow stack pointer is not restored, exactly as in V8. + * + * The error classes are Errors whose inherited name is CompileError, + * LinkError or RuntimeError (not an own property, as in Node), which is + * the brand instanceof tests. + * + * The Memory, Table and Global constructors build the same objects an + * instance exports, so either kind can be imported; WebAssembly.Module. + * imports, exports and customSections read the descriptor. + * + * Gaps (results/opt-wasm-design/DESIGN.md, stages W6-W9): Memory.grow + * does not detach the previous ArrayBuffer (the memory never moves with + * guard pages, and explicit-bounds memories keep their previous blocks, so + * old views stay valid with their old length); shared memories are not + * constructed; instances, their imports and exports are never freed (the + * cycle collector does not trace them yet). */ +#if !defined(_GNU_SOURCE) && defined(__linux__) +#define _GNU_SOURCE /* REG_RIP and friends in */ +#endif +#include "scr_runtime.h" +#include "scr_wasm_abi.h" + +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#endif + +#if !defined(_WIN32) +#define SCR_WASM_SIGNALS 1 +#include +#include +#include +#include +#if defined(__APPLE__) +#include +#else +#include +#endif +#endif + +#if defined(__has_feature) +#if __has_feature(address_sanitizer) +#define SCR_WASM_ASAN 1 +#endif +#endif +#if !defined(SCR_WASM_ASAN) && defined(__SANITIZE_ADDRESS__) +#define SCR_WASM_ASAN 1 +#endif + +/* Entry points of translated code. */ +/* Sanitized builds do not declare it noreturn: AddressSanitizer would + * call __asan_handle_no_return before every call, which warns on the + * runtime's async fibers. */ +#ifdef SCR_WASM_ASAN +void scr_wasm_trap(int32_t code); +#else +_Noreturn void scr_wasm_trap(int32_t code); +#endif +void scr_wasm_import_call(void *host, int32_t index, const uint64_t *args, uint64_t *results); +int32_t scr_wasm_memory_grow(void *inst, int32_t delta); +void scr_wasm_memory_fill(void *inst, int32_t d, int32_t value, int32_t n); +void scr_wasm_memory_copy(void *inst, int32_t d, int32_t s, int32_t n); +void scr_wasm_memory_init(void *inst, int32_t seg, int32_t d, int32_t s, int32_t n); +void scr_wasm_data_drop(void *inst, int32_t seg); +void *scr_wasm_table_get(void *inst, int32_t t, int32_t i); +void scr_wasm_table_set(void *inst, int32_t t, int32_t i, void *value); +int32_t scr_wasm_table_grow(void *inst, int32_t t, void *init, int32_t n); +void scr_wasm_table_fill(void *inst, int32_t t, int32_t i, void *value, int32_t n); +void scr_wasm_table_copy(void *inst, int32_t dt, int32_t st, int32_t d, int32_t s, int32_t n); +void scr_wasm_table_init(void *inst, int32_t t, int32_t seg, int32_t d, int32_t s, int32_t n); +void scr_wasm_elem_drop(void *inst, int32_t seg); +void scr_wasm_global_set_ref(void *cell, void *value); + +#define SCR_WASM_PAGE 65536u +#define SCR_WASM_MAX_PAGES 65536u +#define SCR_WASM_MAX_TABLE 10000000u +/* Every `base + zext(i32) + offset(u32) + size` the translated code can + * form stays inside this reservation, so an access past the memory faults + * on a PROT_NONE page instead of reaching other mappings. */ +#define SCR_WASM_GUARD_RESERVATION ((uint64_t)8 << 30 | (uint64_t)SCR_WASM_PAGE) + +/* ── instance state ──────────────────────────────────────────────────── */ + +typedef struct ScrWasmInst { + size_t rc; + const ScrWasmModuleDesc *desc; + void **inst; /* the translated code's instance (layout in scr_wasm_abi.h) */ + ScrDyn **funcs; /* imported function values, by function index */ + ScrWasmMemory *memory; + ScrWasmTable **tables; + ScrWasmGlobal **globals; + ScrWasmFuncRef *funcrefs; + ScrDyn **export_fns; /* exported function values, by function index (lazy) */ + char **export_names; + bool *data_dropped; + bool *elem_dropped; + ScrDyn *exports; + /* The Memory, Table and Global objects of the instance, by index: the + * imported object itself, or the one created when the module exports it, + * so every export of the same index is the same object, as in V8. */ + ScrDyn *memory_obj; + ScrDyn **table_objs; + ScrDyn **global_objs; +} ScrWasmInst; + +static ScrWasmInst *scr_wasm_host_of(void *inst) { return ((ScrWasmInst **)inst)[0]; } + +#ifdef SCR_RC_AUDIT +/* Instances are never freed yet (stage W6), so the sanitized lane's RC + * audit would report everything they hold. Audited builds tear every + * instance down at exit instead. */ +static SCR_TL ScrWasmInst **scr_wasm_hosts; +static SCR_TL size_t scr_wasm_host_count, scr_wasm_host_cap; +static void scr_wasm_teardown(void); + +static void scr_wasm_note_host(ScrWasmInst *host) { + if (scr_wasm_host_count == 0 && scr_wasm_host_cap == 0) scr_atexit(scr_wasm_teardown); + if (scr_wasm_host_count == scr_wasm_host_cap) { + size_t cap = scr_wasm_host_cap ? scr_wasm_host_cap * 2 : 8; + ScrWasmInst **grown = realloc(scr_wasm_hosts, cap * sizeof *grown); + if (!grown) scr_trap("scriptc: out of memory\n"); + scr_wasm_hosts = grown; + scr_wasm_host_cap = cap; + } + scr_wasm_hosts[scr_wasm_host_count++] = host; +} +#endif + +/* ── SHA-256 (FIPS 180-4) ────────────────────────────────────────────── */ + +typedef struct { + uint32_t h[8]; +} ScrWasmSha; + +static const uint32_t scr_wasm_sha_k[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +static uint32_t scr_wasm_rotr(uint32_t x, unsigned n) { return (x >> n) | (x << (32 - n)); } + +static void scr_wasm_sha_block(ScrWasmSha *s, const uint8_t *p) { + uint32_t w[64]; + for (int i = 0; i < 16; i++) + w[i] = (uint32_t)p[4 * i] << 24 | (uint32_t)p[4 * i + 1] << 16 | (uint32_t)p[4 * i + 2] << 8 | p[4 * i + 3]; + for (int i = 16; i < 64; i++) { + uint32_t s0 = scr_wasm_rotr(w[i - 15], 7) ^ scr_wasm_rotr(w[i - 15], 18) ^ (w[i - 15] >> 3); + uint32_t s1 = scr_wasm_rotr(w[i - 2], 17) ^ scr_wasm_rotr(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + uint32_t a = s->h[0], b = s->h[1], c = s->h[2], d = s->h[3], e = s->h[4], f = s->h[5], g = s->h[6], h = s->h[7]; + for (int i = 0; i < 64; i++) { + uint32_t t1 = h + (scr_wasm_rotr(e, 6) ^ scr_wasm_rotr(e, 11) ^ scr_wasm_rotr(e, 25)) + ((e & f) ^ (~e & g)) + + scr_wasm_sha_k[i] + w[i]; + uint32_t t2 = (scr_wasm_rotr(a, 2) ^ scr_wasm_rotr(a, 13) ^ scr_wasm_rotr(a, 22)) + ((a & b) ^ (a & c) ^ (b & c)); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + s->h[0] += a; + s->h[1] += b; + s->h[2] += c; + s->h[3] += d; + s->h[4] += e; + s->h[5] += f; + s->h[6] += g; + s->h[7] += h; +} + +static void scr_wasm_sha256(const uint8_t *data, size_t len, uint8_t out[32]) { + ScrWasmSha s = {{0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}}; + size_t i = 0; + for (; i + 64 <= len; i += 64) scr_wasm_sha_block(&s, data + i); + uint8_t tail[128] = {0}; + size_t rest = len - i; + if (rest) memcpy(tail, data + i, rest); + tail[rest] = 0x80; + size_t total = rest + 1 + 8 <= 64 ? 64 : 128; + uint64_t bits = (uint64_t)len * 8; + for (int k = 0; k < 8; k++) tail[total - 1 - k] = (uint8_t)(bits >> (8 * k)); + scr_wasm_sha_block(&s, tail); + if (total == 128) scr_wasm_sha_block(&s, tail + 64); + for (int k = 0; k < 8; k++) { + out[4 * k] = (uint8_t)(s.h[k] >> 24); + out[4 * k + 1] = (uint8_t)(s.h[k] >> 16); + out[4 * k + 2] = (uint8_t)(s.h[k] >> 8); + out[4 * k + 3] = (uint8_t)s.h[k]; + } +} + +/* ── errors ──────────────────────────────────────────────────────────── */ + +enum { SCR_WASM_COMPILE_ERROR, SCR_WASM_LINK_ERROR, SCR_WASM_RUNTIME_ERROR }; +static const char *const scr_wasm_error_names[] = {"CompileError", "LinkError", "RuntimeError"}; + +/* A WebAssembly error: an Error whose inherited (not own) name is the + * class's. Borrows message. +1. */ +static ScrError *scr_wasm_error_make(int which, ScrStr *message) { + ScrError *e = scr_error_new(SCR_ERR_ERROR, message); + scr_str_release(e->name); + const char *name = scr_wasm_error_names[which]; + e->name = scr_str_new(name, strlen(name)); + e->name_present = false; + return e; +} + +static void scr_wasm_throw_kind(int which, const char *msg) { + ScrStr *s = scr_str_new(msg, strlen(msg)); + ScrError *e = scr_wasm_error_make(which, s); + scr_str_release(s); + scr_throw_obj(e, &scr_error_retain_v, &scr_error_release_v, scr_error_trace_arg()); +} + +static int scr_wasm_error_brand(const ScrError *e) { + if (!e || e->vt != &scr_error_vts[SCR_ERR_ERROR] || e->name_present || !e->name) return -1; + for (int i = 0; i < 3; i++) { + size_t n = strlen(scr_wasm_error_names[i]); + if (e->name->len == n && !memcmp(e->name->data, scr_wasm_error_names[i], n)) return i; + } + return -1; +} + +static void scr_wasm_type_error(const char *msg) { scr_throw_error_msg(SCR_ERR_TYPE, msg, strlen(msg)); } +static void scr_wasm_range_error(const char *msg) { scr_throw_error_msg(SCR_ERR_RANGE, msg, strlen(msg)); } + +static const char *scr_wasm_trap_message(int code) { + switch (code) { + case SCR_WASM_TRAP_MEMORY_OOB: return "memory access out of bounds"; + case SCR_WASM_TRAP_DIV_BY_ZERO: return "divide by zero"; + case SCR_WASM_TRAP_DIV_UNREPRESENTABLE: return "divide result unrepresentable"; + case SCR_WASM_TRAP_REM_BY_ZERO: return "remainder by zero"; + case SCR_WASM_TRAP_FLOAT_UNREPRESENTABLE: return "float unrepresentable in integer range"; + case SCR_WASM_TRAP_TABLE_OOB: return "table index is out of bounds"; + case SCR_WASM_TRAP_SIGNATURE: return "null function or function signature mismatch"; + case SCR_WASM_TRAP_NULL: return "dereferencing a null pointer"; + case SCR_WASM_TRAP_ELEM_SEGMENT: return "element segment out of bounds"; + default: return "unreachable"; + } +} + +/* Throws the JavaScript error for a trap code (nothing for HOST: its + * exception is already pending). */ +static void scr_wasm_throw_trap(int code) { + if (code == SCR_WASM_TRAP_HOST) return; + if (code == SCR_WASM_TRAP_EXHAUSTION) { + scr_wasm_range_error("Maximum call stack size exceeded"); + return; + } + scr_wasm_throw_kind(SCR_WASM_RUNTIME_ERROR, scr_wasm_trap_message(code)); +} + +/* ── the trap guard ──────────────────────────────────────────────────── */ + +/* AddressSanitizer intercepts siglongjmp and warns when it cannot tell the + * extent of the running stack, as on the runtime's async fibers. Sanitized + * builds therefore jump with a pair no runtime intercepts and unpoison the + * abandoned frames themselves: the compiler's builtin pair, or on AArch64, + * where the compiler has no builtin setjmp lowering, the pair below. */ +#if defined(SCR_WASM_ASAN) && defined(__aarch64__) +void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +/* x19-x28, the frame and link registers, sp and d8-d15: the registers the + * AArch64 procedure call standard has a callee preserve. */ +typedef uint64_t ScrWasmJmp[21]; +__attribute__((naked, noinline, returns_twice)) static int scr_wasm_setjmp(ScrWasmJmp buf) { + __asm__ volatile("stp x19, x20, [x0, #0]\n" + "stp x21, x22, [x0, #16]\n" + "stp x23, x24, [x0, #32]\n" + "stp x25, x26, [x0, #48]\n" + "stp x27, x28, [x0, #64]\n" + "stp x29, x30, [x0, #80]\n" + "mov x16, sp\n" + "str x16, [x0, #96]\n" + "stp d8, d9, [x0, #104]\n" + "stp d10, d11, [x0, #120]\n" + "stp d12, d13, [x0, #136]\n" + "stp d14, d15, [x0, #152]\n" + "mov w0, #0\n" + "ret\n"); +} +/* Returns 1 from the scr_wasm_setjmp call that filled buf. */ +__attribute__((naked, noinline, noreturn)) static void scr_wasm_longjmp(ScrWasmJmp buf) { + __asm__ volatile("ldp x19, x20, [x0, #0]\n" + "ldp x21, x22, [x0, #16]\n" + "ldp x23, x24, [x0, #32]\n" + "ldp x25, x26, [x0, #48]\n" + "ldp x27, x28, [x0, #64]\n" + "ldp x29, x30, [x0, #80]\n" + "ldr x16, [x0, #96]\n" + "mov sp, x16\n" + "ldp d8, d9, [x0, #104]\n" + "ldp d10, d11, [x0, #120]\n" + "ldp d12, d13, [x0, #136]\n" + "ldp d14, d15, [x0, #152]\n" + "mov w0, #1\n" + "ret\n"); +} +#define SCR_WASM_SETJMP(buf) scr_wasm_setjmp(buf) +#define SCR_WASM_LONGJMP(buf) scr_wasm_longjmp(buf) +#elif defined(SCR_WASM_ASAN) +void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +typedef void *ScrWasmJmp[5]; +#define SCR_WASM_SETJMP(buf) __builtin_setjmp(buf) +#define SCR_WASM_LONGJMP(buf) __builtin_longjmp(buf, 1) +#elif defined(_WIN32) +typedef jmp_buf ScrWasmJmp; +#define SCR_WASM_SETJMP(buf) setjmp(buf) +#define SCR_WASM_LONGJMP(buf) longjmp(buf, 1) +#else +typedef sigjmp_buf ScrWasmJmp; +#define SCR_WASM_SETJMP(buf) sigsetjmp(buf, 0) +#define SCR_WASM_LONGJMP(buf) siglongjmp(buf, 1) +#endif + +typedef struct ScrWasmEntry { + ScrWasmJmp jmp; + struct ScrWasmEntry *prev; + uintptr_t frame; /* the entry's frame address: exhaustion resumes below it */ +} ScrWasmEntry; + +/* Thread-local in every build: each thread runs its own instances, and + * the fault handler reads them. */ +static _Thread_local ScrWasmEntry *scr_wasm_top; +static _Thread_local bool scr_wasm_running; /* translated code (not an import) is on top */ +static _Thread_local int scr_wasm_trap_code; + +/* Externref values an import returned: translated code holds them without + * a reference, so they stay alive until the outermost export call + * returns. */ +static _Thread_local ScrDyn **scr_wasm_kept; +static _Thread_local size_t scr_wasm_kept_count, scr_wasm_kept_cap; + +static void scr_wasm_keep(ScrDyn *d) { + if (scr_wasm_kept_count == scr_wasm_kept_cap) { + size_t cap = scr_wasm_kept_cap ? scr_wasm_kept_cap * 2 : 16; + ScrDyn **grown = realloc(scr_wasm_kept, cap * sizeof *grown); + if (!grown) scr_trap("scriptc: out of memory\n"); + scr_wasm_kept = grown; + scr_wasm_kept_cap = cap; + } + scr_wasm_kept[scr_wasm_kept_count++] = scr_dyn_retain(d); +} + +static void scr_wasm_release_kept(void) { + while (scr_wasm_kept_count) scr_dyn_release(scr_wasm_kept[--scr_wasm_kept_count]); +} + +#ifdef SCR_WASM_ASAN +/* Uninstrumented, so the sanitizer inserts no __asan_handle_no_return + * before the jump; the abandoned frames are unpoisoned below. */ +__attribute__((no_sanitize("address"))) +#else +_Noreturn +#endif +void scr_wasm_trap(int32_t code) { + ScrWasmEntry *top = scr_wasm_top; + if (!top) scr_trap("scriptc: WebAssembly trap outside an export call\n"); + scr_wasm_running = false; + scr_wasm_trap_code = code; +#ifdef SCR_WASM_ASAN + /* The real frame address: a local's address may be on the sanitizer's + * fake stack. */ + uintptr_t low = ((uintptr_t)__builtin_frame_address(0) & ~(uintptr_t)15) - 256; + if (top->frame > low && top->frame - low < ((uintptr_t)256 << 20)) + __asan_unpoison_memory_region((void *)low, top->frame - low); +#endif + SCR_WASM_LONGJMP(top->jmp); +} + +#if SCR_WASM_SIGNALS +static void scr_wasm_thread_init(void); +static void scr_wasm_guard_fiber(uintptr_t frame); +#endif + +/* Code with explicit bounds checks: the lowest stack address translated + * code may use before it traps with stack exhaustion (translate.ts checks + * it in the entry of every function through which the stack can grow + * without bound): the runtime's own stack guard for the running stack + * (thread or async fiber), read at each outermost export call. */ +_Thread_local uint64_t scr_wasm_stack_limit; + +static void scr_wasm_set_stack_limit(void) { +#if !defined(_WIN32) && !defined(__wasi__) + if (!scr_stack_guard) scr_stack_guard_init(); + scr_wasm_stack_limit = scr_stack_guard; +#endif +} + +/* Calls a thunk (or `call` on `code`, a function reference's code) with + * traps caught; answers a SCR_WASM_TRAP_* code. */ +static __attribute__((noinline)) int scr_wasm_enter_ref(ScrWasmThunk fn, ScrWasmCallThunk call, void *ref_code, + void *inst, const uint64_t *args, uint64_t *results) { +#if SCR_WASM_SIGNALS + scr_wasm_thread_init(); +#endif + ScrWasmEntry entry; + entry.prev = scr_wasm_top; + entry.frame = (uintptr_t)__builtin_frame_address(0); + if (!entry.prev) { + scr_wasm_set_stack_limit(); +#if SCR_WASM_SIGNALS + scr_wasm_guard_fiber(entry.frame); +#endif + } + bool was_running = scr_wasm_running; + scr_wasm_top = &entry; + int code = 0; + if (SCR_WASM_SETJMP(entry.jmp) == 0) { + scr_wasm_running = true; + if (call) call(ref_code, inst, args, results); + else fn(inst, args, results); + } else { + code = scr_wasm_trap_code; + } + scr_wasm_running = was_running; + scr_wasm_top = entry.prev; + return code; +} + +static int scr_wasm_enter(ScrWasmThunk fn, void *inst, const uint64_t *args, uint64_t *results) { + return scr_wasm_enter_ref(fn, NULL, NULL, inst, args, results); +} + +/* Called by each caller of scr_wasm_enter once it has converted the + * call's results: after the outermost call, the references kept for it + * (import results, and references a table or global replaced while the + * guest could still hold them) are released. The results' externrefs are + * borrowed from that list until then. */ +static void scr_wasm_leave(void) { + if (!scr_wasm_top && scr_wasm_kept_count) scr_wasm_release_kept(); +} + +static void scr_wasm_init_thunk(void *inst, const uint64_t *args, uint64_t *results) { + (void)args; + (void)results; + scr_wasm_host_of(inst)->desc->init(inst); +} + +static void scr_wasm_start_thunk(void *inst, const uint64_t *args, uint64_t *results) { + (void)args; + (void)results; + scr_wasm_host_of(inst)->desc->start(inst); +} + +/* ── memories and the fault handler ──────────────────────────────────── */ + +#if SCR_WASM_SIGNALS +/* Reservations of this thread's guarded memories, read by the fault + * handler (faults are synchronous, so nothing changes under it). */ +typedef struct { + uintptr_t start, end; +} ScrWasmRange; +static _Thread_local ScrWasmRange *scr_wasm_ranges; +static _Thread_local size_t scr_wasm_range_count, scr_wasm_range_cap; +static _Thread_local bool scr_wasm_altstack_ready; + +static struct sigaction scr_wasm_prev_segv, scr_wasm_prev_bus; +static pthread_once_t scr_wasm_handler_once = PTHREAD_ONCE_INIT; +static bool scr_wasm_handler_installed; + +static void scr_wasm_range_add(uint8_t *base, uint64_t len) { + if (scr_wasm_range_count == scr_wasm_range_cap) { + size_t cap = scr_wasm_range_cap ? scr_wasm_range_cap * 2 : 8; + ScrWasmRange *grown = realloc(scr_wasm_ranges, cap * sizeof *grown); + if (!grown) scr_trap("scriptc: out of memory\n"); + scr_wasm_ranges = grown; + scr_wasm_range_cap = cap; + } + scr_wasm_ranges[scr_wasm_range_count++] = (ScrWasmRange){(uintptr_t)base, (uintptr_t)base + len}; +} + +static void scr_wasm_range_remove(uint8_t *base) { + for (size_t i = 0; i < scr_wasm_range_count; i++) { + if (scr_wasm_ranges[i].start == (uintptr_t)base) { + scr_wasm_ranges[i] = scr_wasm_ranges[--scr_wasm_range_count]; + return; + } + } +} + +static _Noreturn __attribute__((noinline, used)) void scr_wasm_fault_trampoline(int32_t code) { + scr_wasm_trap(code); + __builtin_unreachable(); +} + +/* Moves the interrupted context to scr_wasm_fault_trampoline(code). With + * `stack` nonzero the trampoline runs at that stack address (exhaustion); + * otherwise below the interrupted stack pointer. */ +static bool scr_wasm_redirect(void *context, int32_t code, uintptr_t stack) { + ucontext_t *uc = context; + uintptr_t target = (uintptr_t)&scr_wasm_fault_trampoline; +#if defined(__APPLE__) && defined(__x86_64__) + uintptr_t sp = stack ? stack : (uintptr_t)uc->uc_mcontext->__ss.__rsp - 256; + sp = (sp & ~(uintptr_t)15) - 8; /* as after a call */ + uc->uc_mcontext->__ss.__rsp = sp; + uc->uc_mcontext->__ss.__rdi = (uint64_t)(uint32_t)code; + uc->uc_mcontext->__ss.__rip = target; + return true; +#elif defined(__APPLE__) && defined(__aarch64__) + uintptr_t sp = stack ? stack : (uintptr_t)__darwin_arm_thread_state64_get_sp(uc->uc_mcontext->__ss) - 256; + sp &= ~(uintptr_t)15; + __darwin_arm_thread_state64_set_sp(uc->uc_mcontext->__ss, sp); + uc->uc_mcontext->__ss.__x[0] = (uint64_t)(uint32_t)code; + __darwin_arm_thread_state64_set_lr_fptr(uc->uc_mcontext->__ss, (void *)target); + __darwin_arm_thread_state64_set_pc_fptr(uc->uc_mcontext->__ss, (void *)target); + return true; +#elif defined(__linux__) && defined(__x86_64__) + uintptr_t sp = stack ? stack : (uintptr_t)uc->uc_mcontext.gregs[REG_RSP] - 256; + sp = (sp & ~(uintptr_t)15) - 8; + uc->uc_mcontext.gregs[REG_RSP] = (greg_t)sp; + uc->uc_mcontext.gregs[REG_RDI] = (greg_t)(uint32_t)code; + uc->uc_mcontext.gregs[REG_RIP] = (greg_t)target; + return true; +#elif defined(__linux__) && defined(__aarch64__) + uintptr_t sp = stack ? stack : (uintptr_t)uc->uc_mcontext.sp - 256; + sp &= ~(uintptr_t)15; + uc->uc_mcontext.sp = sp; + uc->uc_mcontext.regs[0] = (uint64_t)(uint32_t)code; + uc->uc_mcontext.regs[30] = target; + uc->uc_mcontext.pc = target; + return true; +#else + (void)uc; + (void)target; + (void)code; + (void)stack; + return false; +#endif +} + +static uintptr_t scr_wasm_context_sp(void *context) { + ucontext_t *uc = context; +#if defined(__APPLE__) && defined(__x86_64__) + return (uintptr_t)uc->uc_mcontext->__ss.__rsp; +#elif defined(__APPLE__) && defined(__aarch64__) + return (uintptr_t)__darwin_arm_thread_state64_get_sp(uc->uc_mcontext->__ss); +#elif defined(__linux__) && defined(__x86_64__) + return (uintptr_t)uc->uc_mcontext.gregs[REG_RSP]; +#elif defined(__linux__) && defined(__aarch64__) + return (uintptr_t)uc->uc_mcontext.sp; +#else + (void)uc; + return 0; +#endif +} + +static void scr_wasm_chain(int sig, siginfo_t *info, void *context) { + struct sigaction *prev = sig == SIGBUS ? &scr_wasm_prev_bus : &scr_wasm_prev_segv; + if (prev->sa_flags & SA_SIGINFO) { + if (prev->sa_sigaction) { + prev->sa_sigaction(sig, info, context); + return; + } + } else if (prev->sa_handler != SIG_DFL && prev->sa_handler != SIG_IGN) { + prev->sa_handler(sig); + return; + } + /* The default action: restore it and let the faulting instruction run + * again, so the process dies of the original signal. */ + sigaction(sig, prev, NULL); +} + +static void scr_wasm_fault(int sig, siginfo_t *info, void *context) { + if (scr_wasm_running && scr_wasm_top) { + uintptr_t addr = (uintptr_t)info->si_addr; + for (size_t i = 0; i < scr_wasm_range_count; i++) { + if (addr >= scr_wasm_ranges[i].start && addr < scr_wasm_ranges[i].end) { + if (scr_wasm_redirect(context, SCR_WASM_TRAP_MEMORY_OOB, 0)) return; + } + } + uintptr_t sp = scr_wasm_context_sp(context); + if (sp && addr + 65536 >= sp && addr <= sp + 65536) { + /* Stack exhaustion: resume below the export entry's frame, over the + * abandoned translated frames. */ + uintptr_t below = scr_wasm_top->frame - 4096; + if (scr_wasm_redirect(context, SCR_WASM_TRAP_EXHAUSTION, below)) return; + } + } + scr_wasm_chain(sig, info, context); +} + +static void scr_wasm_install_handler(void) { + struct sigaction sa; + memset(&sa, 0, sizeof sa); + sa.sa_sigaction = scr_wasm_fault; + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + if (sigaction(SIGSEGV, &sa, &scr_wasm_prev_segv) == 0 && sigaction(SIGBUS, &sa, &scr_wasm_prev_bus) == 0) + scr_wasm_handler_installed = true; +} + +/* The thread's own stack, which the system ends with a guard page. */ +static _Thread_local uintptr_t scr_wasm_thread_low, scr_wasm_thread_high; +/* The stack guard of the last stack an outermost export call ran on. */ +static _Thread_local uintptr_t scr_wasm_guarded_stack; + +/* Async fibers run on SCR_FIBER_STACK-byte mappings without a guard page + * (compiled code checks scr_stack_guard instead), so recursion in + * translated code there would run past the mapping. The first export call + * on a fiber therefore protects the lowest page of its mapping, which the + * fault handler then reports as stack exhaustion; scr_stack_guard, which + * sits scr_stack_guard_for's reserve above that page, locates it. The + * page stays protected while the runtime reuses the stack. A stack that + * does not match the fiber layout is left alone. */ +static void scr_wasm_guard_fiber(uintptr_t frame) { + if (!scr_wasm_handler_installed) return; + uintptr_t guard = scr_stack_guard; + if (!guard || guard == scr_wasm_guarded_stack) return; + scr_wasm_guarded_stack = guard; + if (!scr_wasm_thread_high) { +#if defined(__APPLE__) + pthread_t self = pthread_self(); + scr_wasm_thread_high = (uintptr_t)pthread_get_stackaddr_np(self); + scr_wasm_thread_low = scr_wasm_thread_high - pthread_get_stacksize_np(self); +#else + pthread_attr_t attr; + if (pthread_getattr_np(pthread_self(), &attr) == 0) { + void *addr; + size_t size; + if (pthread_attr_getstack(&attr, &addr, &size) == 0) { + scr_wasm_thread_low = (uintptr_t)addr; + scr_wasm_thread_high = (uintptr_t)addr + size; + } + pthread_attr_destroy(&attr); + } +#endif + } + if (frame >= scr_wasm_thread_low && frame < scr_wasm_thread_high) return; + uintptr_t low = guard - (scr_stack_guard_for(0, SCR_FIBER_STACK)); + long page = sysconf(_SC_PAGESIZE); + if (page <= 0 || (low & ((uintptr_t)page - 1)) != 0) return; + if (frame <= low + (uintptr_t)page || frame >= low + SCR_FIBER_STACK) return; + (void)mprotect((void *)low, (size_t)page, PROT_NONE); +} + +/* Per thread: an alternate signal stack, so exhaustion faults can be + * handled. A thread that already has one keeps it. */ +static void scr_wasm_thread_init(void) { + if (scr_wasm_altstack_ready || !scr_wasm_handler_installed) return; + scr_wasm_altstack_ready = true; + stack_t current; + if (sigaltstack(NULL, ¤t) == 0 && !(current.ss_flags & SS_DISABLE) && current.ss_size >= 32768) return; + size_t size = 65536; + void *mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (mem == MAP_FAILED) return; + stack_t ss = {.ss_sp = mem, .ss_size = size, .ss_flags = 0}; + sigaltstack(&ss, NULL); +} +#endif + +/* Checked memories (explicit bounds checks, no guard pages) keep one base + * address for their whole life: the ArrayBuffer of `memory.buffer` and + * every view of it alias the storage, so growing must not move it. The + * address space for the memory's largest size is reserved once and pages + * are committed as the memory grows. Hosts without address-space + * reservation (WASI) move the storage only while no buffer has been + * handed out; after that, growth that would move it fails (-1, a + * RangeError for Memory.prototype.grow). */ +#if defined(_WIN32) +#define SCR_WASM_STABLE_RESERVE 1 +static uint8_t *scr_wasm_reserve(uint64_t bytes) { + return VirtualAlloc(NULL, (SIZE_T)bytes, MEM_RESERVE, PAGE_NOACCESS); +} +static bool scr_wasm_commit(uint8_t *base, uint64_t from, uint64_t to) { + return to == from || VirtualAlloc(base + from, (SIZE_T)(to - from), MEM_COMMIT, PAGE_READWRITE) != NULL; +} +static void scr_wasm_unreserve(ScrWasmMemory *m) { + if (m->base) VirtualFree(m->base, 0, MEM_RELEASE); +} +#elif !defined(__wasi__) +#define SCR_WASM_STABLE_RESERVE 1 +static uint8_t *scr_wasm_reserve(uint64_t bytes) { +#if !defined(MAP_NORESERVE) +#define MAP_NORESERVE 0 +#endif + void *base = mmap(NULL, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); + return base == MAP_FAILED ? NULL : base; +} +static bool scr_wasm_commit(uint8_t *base, uint64_t from, uint64_t to) { + return to == from || mprotect(base + from, to - from, PROT_READ | PROT_WRITE) == 0; +} +static void scr_wasm_unreserve(ScrWasmMemory *m) { + if (m->base) munmap(m->base, m->reserved); +} +#else +static void scr_wasm_unreserve(ScrWasmMemory *m) { free(m->base); } +#endif + +static ScrWasmMemory *scr_wasm_memory_retain(ScrWasmMemory *m) { + m->rc++; + return m; +} + +/* True when JavaScript holds none of the memory's ArrayBuffers or views + * over them: each byte store's only reference is the memory's own. */ +static bool scr_wasm_memory_unexposed(const ScrWasmMemory *m) { + for (size_t i = 0; i < m->exposed_count; i++) + if (m->exposed[i]->rc != 1) return false; + return true; +} + +static void scr_wasm_memory_free(ScrWasmMemory *m) { + for (size_t i = 0; i < m->exposed_count; i++) scr_bytes_release(m->exposed[i]); + free(m->exposed); +#if SCR_WASM_SIGNALS + if (m->guarded) { + scr_wasm_range_remove(m->base); + munmap(m->base, m->reserved); + } else +#endif + scr_wasm_unreserve(m); + free(m); +} + +/* Released memories whose storage JavaScript still reaches through a + * buffer; freed once it no longer does (checked whenever a memory is + * created or released). */ +static _Thread_local ScrWasmMemory *scr_wasm_parked; + +static void scr_wasm_sweep_parked(void) { + for (ScrWasmMemory **p = &scr_wasm_parked; *p;) { + ScrWasmMemory *m = *p; + if (scr_wasm_memory_unexposed(m)) { + *p = m->next_parked; + scr_wasm_memory_free(m); + } else { + p = &m->next_parked; + } + } +} + +static void scr_wasm_memory_release(ScrWasmMemory *m) { + if (!m || --m->rc) return; + scr_dyn_release(m->buffer); + m->buffer = NULL; + scr_wasm_sweep_parked(); + if (scr_wasm_memory_unexposed(m)) { + scr_wasm_memory_free(m); + } else { + m->next_parked = scr_wasm_parked; + scr_wasm_parked = m; + } +} + +/* A memory of `min` pages growing to at most `max` pages (UINT64_MAX: + * no declared maximum); NULL with Node's RangeError pending when the + * address space or memory is short. */ +static ScrWasmMemory *scr_wasm_memory_new(uint64_t min, uint64_t max, bool guarded, const char *what) { + if (scr_wasm_parked) scr_wasm_sweep_parked(); + ScrWasmMemory *m = calloc(1, sizeof *m); + if (!m) scr_trap("scriptc: out of memory\n"); + m->rc = 1; + m->declared_max = max; + m->max_pages = max > SCR_WASM_MAX_PAGES ? SCR_WASM_MAX_PAGES : max; + m->size = min * SCR_WASM_PAGE; +#if SCR_WASM_SIGNALS + if (guarded) { +#if !defined(MAP_NORESERVE) +#define MAP_NORESERVE 0 +#endif + void *base = mmap(NULL, SCR_WASM_GUARD_RESERVATION, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); + if (base == MAP_FAILED || + (m->size && mprotect(base, m->size, PROT_READ | PROT_WRITE) != 0)) { + if (base != MAP_FAILED) munmap(base, SCR_WASM_GUARD_RESERVATION); + free(m); + char msg[200]; + snprintf(msg, sizeof msg, "%s: Out of memory: Cannot allocate Wasm memory for new instance", what); + scr_wasm_range_error(msg); + return NULL; + } + m->base = base; + m->reserved = SCR_WASM_GUARD_RESERVATION; + m->guarded = true; + scr_wasm_range_add(m->base, m->reserved); + return m; + } +#else + (void)guarded; +#endif +#if SCR_WASM_STABLE_RESERVE + uint64_t reserve = m->max_pages * SCR_WASM_PAGE; + m->base = reserve ? scr_wasm_reserve(reserve) : NULL; + if (reserve && (!m->base || !scr_wasm_commit(m->base, 0, m->size))) { + if (m->base) { + m->reserved = reserve; + scr_wasm_unreserve(m); + } +#else + m->base = m->size ? calloc(1, m->size) : NULL; + if (m->size && !m->base) { +#endif + free(m); + char msg[200]; + snprintf(msg, sizeof msg, "%s: Out of memory: Cannot allocate Wasm memory for new instance", what); + scr_wasm_range_error(msg); + return NULL; + } +#if SCR_WASM_STABLE_RESERVE + m->reserved = reserve; +#else + m->reserved = m->size; +#endif + return m; +} + +/* Grows by `delta` pages; answers the previous page count or -1. */ +static int64_t scr_wasm_memory_grow_by(ScrWasmMemory *m, uint64_t delta) { + uint64_t old = m->size / SCR_WASM_PAGE; + if (delta > m->max_pages || old + delta > m->max_pages) return -1; + if (delta == 0) return (int64_t)old; + uint64_t size = (old + delta) * SCR_WASM_PAGE; +#if SCR_WASM_SIGNALS + if (m->guarded) { + if (mprotect(m->base + m->size, size - m->size, PROT_READ | PROT_WRITE) != 0) return -1; + m->size = size; + return (int64_t)old; + } +#endif +#if SCR_WASM_STABLE_RESERVE + /* Committed pages read as zero; the base never moves. */ + if (size > m->reserved || !scr_wasm_commit(m->base, m->size, size)) return -1; + m->size = size; +#else + /* A buffer handed out aliases the storage: moving it would leave the + * buffer's views reading freed memory. */ + if (m->buffer_exposed) return -1; + uint8_t *grown = realloc(m->base, size); + if (!grown) return -1; + memset(grown + m->size, 0, size - m->size); + m->base = grown; + m->reserved = size; + m->size = size; +#endif + return (int64_t)old; +} + +static ScrWasmMemory *scr_wasm_inst_memory(void *inst) { return ((ScrWasmMemory **)inst)[2]; } + +int32_t scr_wasm_memory_grow(void *inst, int32_t delta) { + ScrWasmMemory *m = scr_wasm_inst_memory(inst); + int64_t old = scr_wasm_memory_grow_by(m, (uint32_t)delta); + ((void **)inst)[1] = m->base; + return (int32_t)old; +} + +static void scr_wasm_check_memory(ScrWasmMemory *m, uint32_t at, uint32_t n) { + if ((uint64_t)at + n > m->size) scr_wasm_trap(SCR_WASM_TRAP_MEMORY_OOB); +} + +void scr_wasm_memory_fill(void *inst, int32_t d, int32_t value, int32_t n) { + ScrWasmMemory *m = scr_wasm_inst_memory(inst); + scr_wasm_check_memory(m, (uint32_t)d, (uint32_t)n); + if (n) memset(m->base + (uint32_t)d, value & 0xff, (uint32_t)n); +} + +void scr_wasm_memory_copy(void *inst, int32_t d, int32_t s, int32_t n) { + ScrWasmMemory *m = scr_wasm_inst_memory(inst); + scr_wasm_check_memory(m, (uint32_t)d, (uint32_t)n); + scr_wasm_check_memory(m, (uint32_t)s, (uint32_t)n); + if (n) memmove(m->base + (uint32_t)d, m->base + (uint32_t)s, (uint32_t)n); +} + +/* Instantiation reports an active data segment's failure with its + * details (V8's "data segment N is out of bounds (offset ...)"). */ +static _Thread_local bool scr_wasm_instantiating; +static _Thread_local struct { + uint32_t seg, offset, length; + uint64_t size; +} scr_wasm_segment_failure; + +void scr_wasm_memory_init(void *inst, int32_t seg, int32_t d, int32_t s, int32_t n) { + ScrWasmInst *host = scr_wasm_host_of(inst); + const ScrWasmDataDesc *data = &host->desc->datas[seg]; + uint64_t len = host->data_dropped[seg] ? 0 : data->len; + ScrWasmMemory *m = scr_wasm_inst_memory(inst); + if ((uint64_t)(uint32_t)s + (uint32_t)n > len || (uint64_t)(uint32_t)d + (uint32_t)n > m->size) { + if (scr_wasm_instantiating) { + scr_wasm_segment_failure.seg = (uint32_t)seg; + scr_wasm_segment_failure.offset = (uint32_t)d; + scr_wasm_segment_failure.length = (uint32_t)n; + scr_wasm_segment_failure.size = m->size; + scr_wasm_trap(SCR_WASM_TRAP_DATA_SEGMENT); + } + scr_wasm_trap(SCR_WASM_TRAP_MEMORY_OOB); + } + if (n) memcpy(m->base + (uint32_t)d, data->data + (uint32_t)s, (uint32_t)n); +} + +void scr_wasm_data_drop(void *inst, int32_t seg) { scr_wasm_host_of(inst)->data_dropped[seg] = true; } + +/* ── tables ──────────────────────────────────────────────────────────── */ + +static ScrWasmTable *scr_wasm_table_new(char elem, uint64_t min, uint64_t max) { + ScrWasmTable *t = calloc(1, sizeof *t); + if (!t) scr_trap("scriptc: out of memory\n"); + t->rc = 1; + t->elem = elem; + t->size = (uint32_t)min; + t->declared_max = max; + t->max = max > SCR_WASM_MAX_TABLE ? SCR_WASM_MAX_TABLE : (uint32_t)max; + t->elems = calloc(min ? min : 1, sizeof *t->elems); + if (!t->elems) scr_trap("scriptc: out of memory\n"); + return t; +} + +static ScrWasmTable *scr_wasm_table_retain(ScrWasmTable *t) { + t->rc++; + return t; +} + +static void scr_wasm_table_release(ScrWasmTable *t) { + if (!t || --t->rc) return; + if (t->elem == 'x') + for (uint32_t i = 0; i < t->size; i++) scr_dyn_release(t->elems[i]); + free(t->elems); + free(t); +} + +static void *scr_wasm_ref_retain(char elem, void *v) { + if (elem == 'x' && v) scr_dyn_retain(v); + return v; +} + +/* The reference a table or global slot drops: while a call runs, the guest + * may still hold it in a local or on its operand stack (it read the slot + * before the store), so it stays alive until the outermost call returns. */ +static void scr_wasm_drop_ref(ScrDyn *old) { + if (!old) return; + if (scr_wasm_top) scr_wasm_keep(old); + scr_dyn_release(old); +} + +static void scr_wasm_table_store(ScrWasmTable *t, uint32_t i, void *v) { + void *old = t->elems[i]; + t->elems[i] = scr_wasm_ref_retain(t->elem, v); + if (t->elem == 'x') scr_wasm_drop_ref(old); +} + +static ScrWasmTable *scr_wasm_inst_table(void *inst, int32_t t) { return ((ScrWasmTable **)inst)[4 + t]; } + +void *scr_wasm_table_get(void *inst, int32_t ti, int32_t i) { + ScrWasmTable *t = scr_wasm_inst_table(inst, ti); + if ((uint32_t)i >= t->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + return t->elems[(uint32_t)i]; +} + +void scr_wasm_table_set(void *inst, int32_t ti, int32_t i, void *value) { + ScrWasmTable *t = scr_wasm_inst_table(inst, ti); + if ((uint32_t)i >= t->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + scr_wasm_table_store(t, (uint32_t)i, value); +} + +static int64_t scr_wasm_table_grow_by(ScrWasmTable *t, uint32_t n, void *init) { + uint32_t old = t->size; + if ((uint64_t)old + n > t->max) return -1; + if (n == 0) return old; + void **grown = realloc(t->elems, ((size_t)old + n) * sizeof *grown); + if (!grown) return -1; + t->elems = grown; + for (uint32_t i = 0; i < n; i++) grown[old + i] = scr_wasm_ref_retain(t->elem, init); + t->size = old + n; + return old; +} + +int32_t scr_wasm_table_grow(void *inst, int32_t ti, void *init, int32_t n) { + return (int32_t)scr_wasm_table_grow_by(scr_wasm_inst_table(inst, ti), (uint32_t)n, init); +} + +void scr_wasm_table_fill(void *inst, int32_t ti, int32_t i, void *value, int32_t n) { + ScrWasmTable *t = scr_wasm_inst_table(inst, ti); + if ((uint64_t)(uint32_t)i + (uint32_t)n > t->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + for (uint32_t k = 0; k < (uint32_t)n; k++) scr_wasm_table_store(t, (uint32_t)i + k, value); +} + +void scr_wasm_table_copy(void *inst, int32_t dti, int32_t sti, int32_t d, int32_t s, int32_t n) { + ScrWasmTable *dt = scr_wasm_inst_table(inst, dti), *st = scr_wasm_inst_table(inst, sti); + uint32_t ud = (uint32_t)d, us = (uint32_t)s, un = (uint32_t)n; + if ((uint64_t)ud + un > dt->size || (uint64_t)us + un > st->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + if (ud <= us) { + for (uint32_t k = 0; k < un; k++) scr_wasm_table_store(dt, ud + k, st->elems[us + k]); + } else { + for (uint32_t k = un; k-- > 0;) scr_wasm_table_store(dt, ud + k, st->elems[us + k]); + } +} + +static void *scr_wasm_elem_item(ScrWasmInst *host, int64_t item) { + if (item >= 0) return &host->funcrefs[item]; + if (item == -1) return NULL; + return (void *)(uintptr_t)host->globals[-(item + 2)]->value; +} + +void scr_wasm_table_init(void *inst, int32_t ti, int32_t seg, int32_t d, int32_t s, int32_t n) { + ScrWasmInst *host = scr_wasm_host_of(inst); + ScrWasmTable *t = scr_wasm_inst_table(inst, ti); + const ScrWasmElemDesc *e = &host->desc->elems[seg]; + uint64_t len = host->elem_dropped[seg] ? 0 : e->count; + uint32_t ud = (uint32_t)d, us = (uint32_t)s, un = (uint32_t)n; + if ((uint64_t)ud + un > t->size) scr_wasm_trap(SCR_WASM_TRAP_TABLE_OOB); + if ((uint64_t)us + un > len) scr_wasm_trap(SCR_WASM_TRAP_ELEM_SEGMENT); + for (uint32_t k = 0; k < un; k++) scr_wasm_table_store(t, ud + k, scr_wasm_elem_item(host, e->items[us + k])); +} + +void scr_wasm_elem_drop(void *inst, int32_t seg) { scr_wasm_host_of(inst)->elem_dropped[seg] = true; } + +/* ── globals ─────────────────────────────────────────────────────────── */ + +static ScrWasmGlobal *scr_wasm_global_new(char type, bool mutable_) { + ScrWasmGlobal *g = calloc(1, sizeof *g); + if (!g) scr_trap("scriptc: out of memory\n"); + g->rc = 1; + g->type = type; + g->mutable_ = mutable_; + return g; +} + +static ScrWasmGlobal *scr_wasm_global_retain(ScrWasmGlobal *g) { + g->rc++; + return g; +} + +static void scr_wasm_global_release(ScrWasmGlobal *g) { + if (!g || --g->rc) return; + if (g->type == 'x') scr_dyn_release((ScrDyn *)(uintptr_t)g->value); + free(g); +} + +void scr_wasm_global_set_ref(void *cell, void *value) { + ScrWasmGlobal *g = cell; + ScrDyn *old = (ScrDyn *)(uintptr_t)g->value; + if (value) scr_dyn_retain(value); + g->value = (uint64_t)(uintptr_t)value; + scr_wasm_drop_ref(old); +} + +/* ── handles ─────────────────────────────────────────────────────────── */ + +enum { SCR_WASM_K_MODULE, SCR_WASM_K_INSTANCE, SCR_WASM_K_MEMORY, SCR_WASM_K_TABLE, SCR_WASM_K_GLOBAL }; + +typedef struct ScrWasmObj { + size_t rc; + int kind; + const ScrWasmModuleDesc *desc; /* Module */ + ScrWasmInst *inst; /* Instance; the owner of exported objects */ + ScrWasmMemory *memory; + ScrWasmTable *table; + ScrWasmGlobal *global; +} ScrWasmObj; + +/* Instances are never freed yet (stage W6 traces them for the cycle + * collector), so the references their handles and exported functions hold + * are not counted. */ +static void *scr_wasm_inst_retain(void *p) { return p; } + +static void scr_wasm_inst_release(void *p) { (void)p; } + +static void *scr_wasm_obj_retain(void *p) { + ((ScrWasmObj *)p)->rc++; + return p; +} + +static void scr_wasm_obj_release(void *p) { + ScrWasmObj *o = p; + if (--o->rc) return; + if (o->inst) scr_wasm_inst_release(o->inst); + scr_wasm_memory_release(o->memory); + scr_wasm_table_release(o->table); + scr_wasm_global_release(o->global); + free(o); +} + +static ScrWasmObj *scr_wasm_obj_new(int kind) { + ScrWasmObj *o = calloc(1, sizeof *o); + if (!o) scr_trap("scriptc: out of memory\n"); + o->rc = 1; + o->kind = kind; + return o; +} + +static ScrDyn *scr_wasm_empty_view(void *h) { + (void)h; + return scr_dyn_new_obj(); +} + +static const ScrDynHandleTag scr_wasm_tags[] = {SCR_DYNH_WASM_MODULE, SCR_DYNH_WASM_INSTANCE, SCR_DYNH_WASM_MEMORY, + SCR_DYNH_WASM_TABLE, SCR_DYNH_WASM_GLOBAL}; + +static void scr_wasm_install(void); + +static ScrDyn *scr_wasm_box(ScrWasmObj *o) { + scr_wasm_install(); + ScrDyn *d = scr_dyn_new_handle(o, scr_wasm_tags[o->kind]); + scr_wasm_obj_release(o); + return d; +} + +static ScrWasmObj *scr_wasm_unbox(const ScrDyn *v, int kind) { + if (!v || v->kind != SCR_DYN_HANDLE || v->v.handle.tag != scr_wasm_tags[kind]) return NULL; + return v->v.handle.ptr; +} + +/* ── values ──────────────────────────────────────────────────────────── */ + +static ScrDyn *scr_wasm_function_value(ScrWasmInst *host, uint32_t func); + +/* ToJSValue (+1; NULL with an exception pending). */ +static ScrDyn *scr_wasm_to_js(char t, uint64_t v) { + switch (t) { + case 'i': return scr_dyn_new_num((double)(int32_t)(uint32_t)v); + case 'I': { + ScrBigInt *b = scr_bigint_from_i64((int64_t)v); + ScrDyn *d = scr_dyn_new_bigint(b); + scr_bigint_release(b); + return d; + } + case 'f': { + uint32_t bits = (uint32_t)v; + float f; + memcpy(&f, &bits, 4); + return scr_dyn_new_num((double)f); + } + case 'F': { + double d; + memcpy(&d, &v, 8); + return scr_dyn_new_num(d); + } + case 'r': { + ScrWasmFuncRef *ref = (ScrWasmFuncRef *)(uintptr_t)v; + if (!ref) return scr_dyn_new_null(); + ScrWasmInst *host = scr_wasm_host_of(ref->inst); + return scr_wasm_function_value(host, (uint32_t)(ref - host->funcrefs)); + } + default: { + ScrDyn *d = (ScrDyn *)(uintptr_t)v; + return d ? scr_dyn_retain(d) : scr_dyn_new_null(); + } + } +} + +static uint32_t scr_wasm_to_int32(double n) { + if (!isfinite(n)) return 0; + double t = trunc(n); + double m = fmod(t, 4294967296.0); + if (m < 0) m += 4294967296.0; + return (uint32_t)m; +} + +static ScrWasmFuncRef *scr_wasm_funcref_of(const ScrDyn *v); + +/* V8's message for a value that is neither null nor an exported + * WebAssembly function where a funcref is expected (behind a prefix that + * names the operation). */ +#define SCR_WASM_FUNCREF_EXPECTED "function-typed object must be null (if nullable) or a Wasm function object" + +/* ToWebAssemblyValue: false with an exception pending. References are + * borrowed from `v`. A funcref mismatch throws a TypeError with + * `funcref_error` (the export-call message when NULL). */ +static bool scr_wasm_from_js_as(char t, const ScrDyn *v, uint64_t *out, const char *funcref_error) { + if (!v) v = scr_dyn_undefined(); + switch (t) { + case 'i': { + double n; + if (!scr_dyn_number_coerce_js(v, &n)) return false; + *out = scr_wasm_to_int32(n); + return true; + } + case 'f': { + double n; + if (!scr_dyn_number_coerce_js(v, &n)) return false; + float f = (float)n; + uint32_t b; + memcpy(&b, &f, 4); + *out = b; + return true; + } + case 'F': { + double n; + if (!scr_dyn_number_coerce_js(v, &n)) return false; + memcpy(out, &n, 8); + return true; + } + case 'I': { + ScrBigInt *b = scr_dyn_bigint_coerce(v); + if (!b) return false; + *out = (uint64_t)scr_bigint_to_i64(b); + scr_bigint_release(b); + return true; + } + case 'r': { + if (v->kind == SCR_DYN_NULL) { + *out = 0; + return true; + } + ScrWasmFuncRef *ref = scr_wasm_funcref_of(v); + if (!ref) { + scr_wasm_type_error(funcref_error ? funcref_error : "type incompatibility when transforming from/to JS"); + return false; + } + *out = (uint64_t)(uintptr_t)ref; + return true; + } + default: + if (v->kind == SCR_DYN_NULL) { + *out = 0; + return true; + } + *out = (uint64_t)(uintptr_t)v; + return true; + } +} + +static bool scr_wasm_from_js(char t, const ScrDyn *v, uint64_t *out) { return scr_wasm_from_js_as(t, v, out, NULL); } + +/* DefaultValue(type): what a missing initial value of a constructed + * global or table, or a missing table.grow/set value, stands for. */ +static uint64_t scr_wasm_default_value(char t) { + return t == 'x' ? (uint64_t)(uintptr_t)scr_dyn_undefined() : 0; +} + +static size_t scr_wasm_params(const char *sig) { + size_t n = 0; + while (sig[n] && sig[n] != ':') n++; + return n; +} + +/* ── exported functions ──────────────────────────────────────────────── */ + +typedef struct { + ScrWasmInst *host; + uint32_t func; + ScrWasmThunk thunk; +} ScrWasmExportCtx; + +static ScrDyn *scr_wasm_export_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + ScrWasmInst *host = scr_box_get_ref(clo->caps[0]); + uint32_t func = (uint32_t)scr_box_get_f64(clo->caps[1]); + const char *sig = host->desc->func_sigs[func]; + ScrWasmThunk thunk = NULL; + for (uint64_t i = 0; i < host->desc->export_count; i++) { + const ScrWasmExportDesc *ex = &host->desc->exports[i]; + if (ex->kind == SCR_WASM_FUNC && ex->index == func) { + thunk = ex->call; + break; + } + } + size_t np = scr_wasm_params(sig); + const char *res = sig[np] == ':' ? sig + np + 1 : ""; + size_t nr = strlen(res); + uint64_t stack_args[16], stack_res[8] = {0}; + uint64_t *a = np <= 16 ? stack_args : malloc(np * sizeof *a); + uint64_t *r = nr <= 8 ? stack_res : calloc(nr, sizeof *r); + if (!a || !r) scr_trap("scriptc: out of memory\n"); + ScrDyn *result = NULL; + bool ok = true; + for (size_t i = 0; i < np && ok; i++) ok = scr_wasm_from_js(sig[i], i < argc ? args[i] : NULL, &a[i]); + if (ok) { + /* A function the module does not export runs through its signature's + * call thunk, on the code its reference record names. */ + int trap = thunk ? scr_wasm_enter(thunk, host->inst, a, r) + : scr_wasm_enter_ref(NULL, host->desc->func_call_thunks[func], host->funcrefs[func].code, + host->inst, a, r); + if (trap) { + scr_wasm_throw_trap(trap); + } else if (nr == 0) { + result = scr_dyn_retain(scr_dyn_undefined()); + } else if (nr == 1) { + result = scr_wasm_to_js(res[0], r[0]); + } else { + result = scr_dyn_new_arr(); + for (size_t i = 0; i < nr; i++) scr_dyn_arr_push(result, scr_wasm_to_js(res[i], r[i])); + } + scr_wasm_leave(); + } + if (a != stack_args) free(a); + if (r != stack_res) free(r); + scr_wasm_inst_release(host); + return result; +} + +/* The function value of function `func` (+1), created once per instance + * and function: exported functions call through their export thunk, other + * referenced functions through their signature's call thunk. */ +static ScrDyn *scr_wasm_function_value(ScrWasmInst *host, uint32_t func) { + if (host->export_fns[func]) return scr_dyn_retain(host->export_fns[func]); + bool callable = host->desc->func_call_thunks[func] != NULL && host->funcrefs[func].code != NULL; + for (uint64_t i = 0; i < host->desc->export_count && !callable; i++) { + const ScrWasmExportDesc *ex = &host->desc->exports[i]; + if (ex->kind == SCR_WASM_FUNC && ex->index == func) callable = true; + } + if (!callable) { + /* Unreachable for valid modules: every function a reference can name + * is exported or has a call thunk. */ + scr_wasm_type_error("type incompatibility when transforming from/to JS"); + return NULL; + } + char name[24]; + snprintf(name, sizeof name, "%u", func); + size_t name_len = strlen(name) + 1; + host->export_names[func] = malloc(name_len); + if (!host->export_names[func]) scr_trap("scriptc: out of memory\n"); + memcpy(host->export_names[func], name, name_len); + ScrClosure *closure = scr_closure_new(NULL, 2); + closure->caps[0] = scr_box_new_obj(scr_wasm_inst_retain, scr_wasm_inst_release, NULL); + scr_box_set_ref(closure->caps[0], scr_wasm_inst_retain(host)); + closure->caps[1] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[1], (double)func); + uint32_t arity = (uint32_t)scr_wasm_params(host->desc->func_sigs[func]); + host->export_fns[func] = scr_dyn_new_func(closure, scr_wasm_export_call, arity, "", host->export_names[func]); + return scr_dyn_retain(host->export_fns[func]); +} + +static ScrWasmFuncRef *scr_wasm_funcref_of(const ScrDyn *v) { + if (!v || v->kind != SCR_DYN_FUNC || v->v.fn.thunk != scr_wasm_export_call) return NULL; + ScrClosure *clo = v->v.fn.clo; + ScrWasmInst *host = scr_box_get_ref(clo->caps[0]); + uint32_t func = (uint32_t)scr_box_get_f64(clo->caps[1]); + scr_wasm_inst_release(host); + return &host->funcrefs[func]; +} + +/* ── imported functions ──────────────────────────────────────────────── */ + +void scr_wasm_import_call(void *hostp, int32_t index, const uint64_t *args, uint64_t *results) { + ScrWasmInst *host = hostp; + const char *sig = host->desc->func_sigs[index]; + size_t np = scr_wasm_params(sig); + ScrDyn *stack_argv[16]; + ScrDyn **argv = np <= 16 ? stack_argv : malloc(np * sizeof *argv); + if (!argv) scr_trap("scriptc: out of memory\n"); + bool ok = true; + size_t made = 0; + for (; made < np; made++) { + argv[made] = scr_wasm_to_js(sig[made], args[made]); + if (!argv[made]) { + ok = false; + break; + } + } + scr_wasm_running = false; + ScrDyn *res = NULL; + if (ok) { + scr_dyn_this_push_dyn(scr_dyn_undefined()); + res = scr_dyn_call(host->funcs[index], argv, np, "WebAssembly import"); + scr_dyn_this_pop(); + } + for (size_t i = 0; i < made; i++) scr_dyn_release(argv[i]); + if (argv != stack_argv) free(argv); + if (res) { + const char *want = sig[np] == ':' ? sig + np + 1 : ""; + size_t nr = strlen(want); + if (nr == 1) { + ok = scr_wasm_from_js(want[0], res, &results[0]); + if (ok && want[0] == 'x' && results[0]) scr_wasm_keep((ScrDyn *)(uintptr_t)results[0]); + } else if (nr > 1) { + for (size_t i = 0; ok && i < nr; i++) { + ScrDyn *item = scr_dyn_index_get(res, (double)i); + ok = item && scr_wasm_from_js(want[i], item, &results[i]); + if (ok && want[i] == 'x' && results[i]) scr_wasm_keep((ScrDyn *)(uintptr_t)results[i]); + scr_dyn_release(item); + } + } + scr_dyn_release(res); + } else { + ok = false; + } + if (!ok) scr_wasm_trap(SCR_WASM_TRAP_HOST); + scr_wasm_running = true; +} + +/* ── handle members ──────────────────────────────────────────────────── */ + +static ScrDyn *scr_wasm_memory_buffer(ScrWasmMemory *m) { + /* memory.grow in translated code changes the size without telling the + * cached buffer. */ + if (m->buffer && m->buffer_size != m->size) { + scr_dyn_release(m->buffer); + m->buffer = NULL; + } + if (!m->buffer) { + m->buffer_exposed = true; + m->buffer_size = m->size; + ScrBytes *bytes = scr_bytes_from_external(m->base, (size_t)m->size); + /* Earlier buffers JavaScript no longer holds need no tracking. */ + size_t kept = 0; + for (size_t i = 0; i < m->exposed_count; i++) { + if (m->exposed[i]->rc == 1) scr_bytes_release(m->exposed[i]); + else m->exposed[kept++] = m->exposed[i]; + } + m->exposed_count = kept; + ScrBytes **grown = realloc(m->exposed, (m->exposed_count + 1) * sizeof *grown); + if (!grown) scr_trap("scriptc: out of memory\n"); + m->exposed = grown; + m->exposed[m->exposed_count++] = bytes; /* the memory's reference */ + m->buffer = scr_array_buffer_from_bytes(bytes); + } + return scr_dyn_retain(m->buffer); +} + +static ScrDyn *scr_wasm_global_value(ScrWasmGlobal *g) { return scr_wasm_to_js(g->type, g->value); } + +static ScrDyn *scr_wasm_get(void *h, const char *key, size_t len) { + ScrWasmObj *o = h; + switch (o->kind) { + case SCR_WASM_K_INSTANCE: + if (len == 7 && !memcmp(key, "exports", 7)) return scr_dyn_retain(o->inst->exports); + return NULL; + case SCR_WASM_K_MEMORY: + if (len == 6 && !memcmp(key, "buffer", 6)) return scr_wasm_memory_buffer(o->memory); + return NULL; + case SCR_WASM_K_TABLE: + if (len == 6 && !memcmp(key, "length", 6)) return scr_dyn_new_num(o->table->size); + return NULL; + case SCR_WASM_K_GLOBAL: + if (len == 5 && !memcmp(key, "value", 5)) return scr_wasm_global_value(o->global); + return NULL; + default: + return NULL; + } +} + +static bool scr_wasm_global_assign(ScrWasmGlobal *g, const ScrDyn *value) { + if (!g->mutable_) { + scr_wasm_type_error("set WebAssembly.Global.value): Can't set the value of an immutable global."); + return false; + } + uint64_t v; + if (!scr_wasm_from_js_as(g->type, value, &v, "set WebAssembly.Global.value): " SCR_WASM_FUNCREF_EXPECTED)) + return false; + if (g->type == 'x') scr_wasm_global_set_ref(g, (void *)(uintptr_t)v); + else g->value = v; + return true; +} + +static bool scr_wasm_set(void *h, const char *key, size_t len, const ScrDyn *value) { + ScrWasmObj *o = h; + if (o->kind == SCR_WASM_K_GLOBAL && len == 5 && !memcmp(key, "value", 5)) { + scr_wasm_global_assign(o->global, value); + return true; + } + return false; +} + +/* V8's EnforceUint32: ToNumber (its own TypeError for symbols and + * BigInts), then finite, non-negative and at most 2^32-1, truncated. + * `what` names the value ("WebAssembly.Memory.grow(): Argument 0", + * "WebAssembly.Memory(): Property 'initial'"). */ +static bool scr_wasm_u32_arg(const ScrDyn *v, const char *what, uint32_t *out) { + double n; + if (!scr_dyn_number_coerce_js(v ? v : scr_dyn_undefined(), &n)) return false; + const char *problem = !isfinite(n) ? "must be convertible to a valid number" + : n < 0 ? "must be non-negative" + : trunc(n) > 4294967295.0 ? "must be in the unsigned long range" + : NULL; + if (problem) { + char msg[200]; + snprintf(msg, sizeof msg, "%s %s", what, problem); + scr_wasm_type_error(msg); + return false; + } + *out = (uint32_t)trunc(n); + return true; +} + +static ScrDyn *scr_wasm_invoke(void *h, ScrDyn *self, const char *method, ScrDyn *const *args, size_t argc, + const char *what) { + (void)self; + ScrWasmObj *o = h; + if (o->kind == SCR_WASM_K_MEMORY && !strcmp(method, "grow")) { + uint32_t delta; + if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Memory.grow(): Argument 0", &delta)) return NULL; + int64_t old = scr_wasm_memory_grow_by(o->memory, delta); + if (old < 0) { + scr_wasm_range_error("WebAssembly.Memory.grow(): Maximum memory size exceeded"); + return NULL; + } + if (o->inst && o->inst->memory == o->memory) o->inst->inst[1] = o->memory->base; + /* The previous buffer keeps its length (no detachment). */ + scr_dyn_release(o->memory->buffer); + o->memory->buffer = NULL; + return scr_dyn_new_num((double)old); + } + if (o->kind == SCR_WASM_K_TABLE) { + ScrWasmTable *t = o->table; + uint32_t index = 0; + uint64_t value = 0; + if (!strcmp(method, "get")) { + if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Table.get(): Argument 0", &index)) return NULL; + if (index >= t->size) { + char msg[160]; + snprintf(msg, sizeof msg, "WebAssembly.Table.get(): invalid address %u in %s table of size %u", index, + t->elem == 'x' ? "externref" : "funcref", t->size); + scr_wasm_range_error(msg); + return NULL; + } + return scr_wasm_to_js(t->elem, (uint64_t)(uintptr_t)t->elems[index]); + } + if (!strcmp(method, "set")) { + if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Table.set(): Argument 0", &index)) return NULL; + if (index >= t->size) { + char msg[160]; + snprintf(msg, sizeof msg, "WebAssembly.Table.set(): invalid address %u in %s table of size %u", index, + t->elem == 'x' ? "externref" : "funcref", t->size); + scr_wasm_range_error(msg); + return NULL; + } + if (argc > 1) { + if (!scr_wasm_from_js_as(t->elem, args[1], &value, + "WebAssembly.Table.set(): Argument 1 is invalid for table: " SCR_WASM_FUNCREF_EXPECTED)) + return NULL; + } else { + value = scr_wasm_default_value(t->elem); + } + scr_wasm_table_store(t, index, (void *)(uintptr_t)value); + return scr_dyn_retain(scr_dyn_undefined()); + } + if (!strcmp(method, "grow")) { + uint32_t delta; + if (!scr_wasm_u32_arg(argc ? args[0] : NULL, "WebAssembly.Table.grow(): Argument 0", &delta)) return NULL; + if (argc > 1) { + if (!scr_wasm_from_js_as(t->elem, args[1], &value, + "WebAssembly.Table.grow(): Argument 1 is invalid: " SCR_WASM_FUNCREF_EXPECTED)) + return NULL; + } else { + value = scr_wasm_default_value(t->elem); + } + int64_t old = scr_wasm_table_grow_by(t, delta, (void *)(uintptr_t)value); + if (old < 0) { + char msg[160]; + snprintf(msg, sizeof msg, "WebAssembly.Table.grow(): failed to grow table by %u", delta); + scr_wasm_range_error(msg); + return NULL; + } + return scr_dyn_new_num((double)old); + } + } + if (o->kind == SCR_WASM_K_GLOBAL && !strcmp(method, "valueOf")) return scr_wasm_global_value(o->global); + char msg[200]; + snprintf(msg, sizeof msg, "%s is not a function", what ? what : method); + scr_wasm_type_error(msg); + return NULL; +} + +/* Module transfer between threads (postMessage, workerData): a module is + * immutable code plus its descriptor, so the payload is the descriptor + * itself, which every thread of the executable shares. */ +static void *scr_wasm_module_share(void *h) { return (void *)((ScrWasmObj *)h)->desc; } + +static ScrDyn *scr_wasm_module_adopt(void *payload) { + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_MODULE); + o->desc = payload; + return scr_wasm_box(o); +} + +static void scr_wasm_module_drop(void *payload) { (void)payload; } + +static const ScrDynHandleTransfer scr_wasm_module_transfer = { + .share = scr_wasm_module_share, .adopt = scr_wasm_module_adopt, .drop = scr_wasm_module_drop}; + +#define SCR_WASM_OPS(name, tag, xfer) \ + static const ScrDynHandleOps scr_wasm_ops_##name = { \ + .cls = #name, .retain = scr_wasm_obj_retain, .release = scr_wasm_obj_release, \ + .invoke = scr_wasm_invoke, .get = scr_wasm_get, .set = scr_wasm_set, \ + .inspect_view = scr_wasm_empty_view, .json_empty_object = true, \ + .to_string_tag = tag, .inspect_view_empty = true, .transfer = xfer, \ + }; +SCR_WASM_OPS(Module, "WebAssembly.Module", &scr_wasm_module_transfer) +SCR_WASM_OPS(Instance, "WebAssembly.Instance", NULL) +SCR_WASM_OPS(Memory, "WebAssembly.Memory", NULL) +SCR_WASM_OPS(Table, "WebAssembly.Table", NULL) +SCR_WASM_OPS(Global, "WebAssembly.Global", NULL) + +static void scr_wasm_install(void) { + static SCR_TL bool installed; + if (installed) return; + installed = true; + scr_dyn_handle_install(SCR_DYNH_WASM_MODULE, &scr_wasm_ops_Module); + scr_dyn_handle_install(SCR_DYNH_WASM_INSTANCE, &scr_wasm_ops_Instance); + scr_dyn_handle_install(SCR_DYNH_WASM_MEMORY, &scr_wasm_ops_Memory); + scr_dyn_handle_install(SCR_DYNH_WASM_TABLE, &scr_wasm_ops_Table); + scr_dyn_handle_install(SCR_DYNH_WASM_GLOBAL, &scr_wasm_ops_Global); +} + +/* ── modules ─────────────────────────────────────────────────────────── */ + +/* The bytes of a BufferSource, or false with Node's TypeError pending. */ +static bool scr_wasm_bytes_of(const ScrDyn *v, const char *what, const char *expected, const uint8_t **data, + size_t *len) { + if (v && v->kind == SCR_DYN_BYTES) { + *data = v->v.bytes->data; + *len = (size_t)scr_bytes_byte_len(v->v.bytes); + return true; + } + if (v && v->kind == SCR_DYN_HANDLE && scr_array_buffer_is(v)) { + ScrBytes *b = v->v.handle.ptr; + *data = b->data; + *len = (size_t)scr_bytes_byte_len(b); + return true; + } + char msg[200]; + snprintf(msg, sizeof msg, "%s: Argument 0 must be %s", what, expected); + scr_wasm_type_error(msg); + return false; +} + +static const ScrWasmModuleDesc *scr_wasm_lookup(const uint8_t *data, size_t len) { + uint8_t digest[32]; + bool hashed = false; + for (uint64_t i = 0; i < scr_wasm_registry.count; i++) { + const ScrWasmModuleDesc *d = scr_wasm_registry.modules[i]; + if (d->byte_length != len || d->abi_version != SCR_WASM_ABI_VERSION) continue; + if (!hashed) { + scr_wasm_sha256(data, len, digest); + hashed = true; + } + if (!memcmp(d->sha256, digest, 32)) return d; + } + return NULL; +} + +static void scr_wasm_not_translated(const char *what) { + char msg[400]; + snprintf(msg, sizeof msg, + "%s: this module was not translated when the program was built (scriptc compiles WebAssembly ahead of " + "time and cannot compile modules whose bytes it did not see; see https://scriptc.dev/limitations)", + what); + scr_wasm_throw_kind(SCR_WASM_COMPILE_ERROR, msg); +} + +static ScrDyn *scr_wasm_module_from(ScrDyn *bytes, const char *what, const char *expected) { + const uint8_t *data; + size_t len; + if (!scr_wasm_bytes_of(bytes, what, expected, &data, &len)) return NULL; + const ScrWasmModuleDesc *desc = scr_wasm_lookup(data, len); + if (!desc) { + scr_wasm_not_translated(what); + return NULL; + } + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_MODULE); + o->desc = desc; + return scr_wasm_box(o); +} + +ScrDyn *scr_wasm_module_new(ScrDyn *bytes) { + return scr_wasm_module_from(bytes, "WebAssembly.Module()", "a buffer source"); +} + +bool scr_wasm_validate(ScrDyn *bytes) { + const uint8_t *data; + size_t len; + if (!scr_wasm_bytes_of(bytes, "WebAssembly.validate()", "a buffer source", &data, &len)) return false; + return scr_wasm_lookup(data, len) != NULL; +} + +/* ── instances ───────────────────────────────────────────────────────── */ + +/* A member of an import object (+1, NULL when absent or undefined, or with + * an exception pending from a getter). */ +static ScrDyn *scr_wasm_member(const ScrDyn *obj, const char *key, size_t len) { + ScrDyn *v = NULL; + switch (obj->kind) { + case SCR_DYN_OBJ: + case SCR_DYN_FUNC: + case SCR_DYN_ARR: + case SCR_DYN_PROXY: + v = scr_dyn_obj_read(obj, key, len); + break; + case SCR_DYN_TYPED_REF: { + ScrStr *k = scr_str_new(key, len); + v = scr_dyn_typed_ref_key_get(obj, k); + scr_str_release(k); + break; + } + case SCR_DYN_HANDLE: { + ScrStr *k = scr_str_new(key, len); + v = scr_dyn_handle_key_get(obj, k); + scr_str_release(k); + break; + } + default: + break; + } + if (v && v->kind == SCR_DYN_UNDEF) { + scr_dyn_release(v); + v = NULL; + } + return v; +} + +static bool scr_wasm_is_object(const ScrDyn *v) { + return v && (v->kind == SCR_DYN_OBJ || v->kind == SCR_DYN_TYPED_REF || v->kind == SCR_DYN_HANDLE || + v->kind == SCR_DYN_PROXY || v->kind == SCR_DYN_FUNC || v->kind == SCR_DYN_ARR); +} + +static void scr_wasm_link_error(const char *what, uint64_t i, const ScrWasmImportDesc *im, const char *reason) { + char msg[600]; + snprintf(msg, sizeof msg, "%s: Import #%llu \"%.*s\" \"%.*s\": %s", what, (unsigned long long)i, + (int)im->module_len, im->module, (int)im->name_len, im->name, reason); + scr_wasm_throw_kind(SCR_WASM_LINK_ERROR, msg); +} + +static bool scr_wasm_guarded(const ScrWasmModuleDesc *desc) { +#if SCR_WASM_SIGNALS + return !(desc->flags & SCR_WASM_DESC_BOUNDS_CHECKS); +#else + (void)desc; + return false; +#endif +} + +static ScrDyn *scr_wasm_instance_from(const ScrWasmModuleDesc *desc, ScrDyn *imports, const char *what) { + scr_wasm_install(); + char msg[400]; + bool have_imports = imports && imports->kind != SCR_DYN_UNDEF; + if (have_imports && !scr_wasm_is_object(imports)) { + snprintf(msg, sizeof msg, "%s: Argument 1 must be an object", what); + scr_wasm_type_error(msg); + return NULL; + } + if (desc->import_count && !have_imports) { + snprintf(msg, sizeof msg, "%s: Imports argument must be present and must be an object", what); + scr_wasm_type_error(msg); + return NULL; + } + /* Read every import value first (V8's order), then check memories, then + * the rest in order. */ + uint64_t n = desc->import_count; + ScrDyn **values = calloc(n ? n : 1, sizeof *values); + if (!values) scr_trap("scriptc: out of memory\n"); + bool ok = true; + for (uint64_t i = 0; ok && i < n; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + ScrDyn *ns = scr_wasm_member(imports, im->module, (size_t)im->module_len); + if (scr_exc_pending()) { + ok = false; + } else if (!scr_wasm_is_object(ns)) { + snprintf(msg, sizeof msg, "%s: Import #%llu \"%.*s\": module is not an object or function", what, + (unsigned long long)i, (int)im->module_len, im->module); + scr_wasm_type_error(msg); + ok = false; + } else { + values[i] = scr_wasm_member(ns, im->name, (size_t)im->name_len); + if (scr_exc_pending()) ok = false; + } + scr_dyn_release(ns); + } + for (uint64_t i = 0; ok && i < n; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + if (im->kind != SCR_WASM_MEMORY) continue; + ScrWasmObj *mo = scr_wasm_unbox(values[i], SCR_WASM_K_MEMORY); + if (!mo) { + scr_wasm_link_error(what, i, im, "memory import must be a WebAssembly.Memory object"); + ok = false; + break; + } + /* V8's limit checks, with its messages (a missing maximum prints as + * the unsigned -1). */ + char reason[200]; + uint64_t pages = mo->memory->size / SCR_WASM_PAGE; + if (pages < desc->memory_min) { + snprintf(reason, sizeof reason, "memory import has %llu pages which is smaller than the declared initial of %llu", + (unsigned long long)pages, (unsigned long long)desc->memory_min); + } else if (desc->memory_max != UINT64_MAX && mo->memory->declared_max == UINT64_MAX) { + snprintf(reason, sizeof reason, "memory import has no maximum limit, expected at most 4294967295"); + } else if (desc->memory_max != UINT64_MAX && mo->memory->declared_max > desc->memory_max) { + snprintf(reason, sizeof reason, "memory import has a larger maximum size %llu than the module's declared maximum %llu", + (unsigned long long)mo->memory->declared_max, (unsigned long long)desc->memory_max); + } else { + continue; + } + scr_wasm_link_error(what, i, im, reason); + ok = false; + } + for (uint64_t i = 0; ok && i < n; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + const ScrDyn *v = values[i]; + if (im->kind == SCR_WASM_FUNC && !(v && scr_dyn_is_callable(v))) { + scr_wasm_link_error(what, i, im, "function import requires a callable"); + ok = false; + } else if (im->kind == SCR_WASM_FUNC && scr_wasm_funcref_of(v) && + scr_wasm_funcref_of(v)->type != desc->func_types[im->index]) { + /* An exported WebAssembly function keeps its signature. */ + scr_wasm_link_error(what, i, im, "imported function does not match the expected type"); + ok = false; + } else if (im->kind == SCR_WASM_TABLE) { + ScrWasmObj *t = scr_wasm_unbox(v, SCR_WASM_K_TABLE); + const ScrWasmTableDesc *td = &desc->tables[im->index]; + if (!t) { + scr_wasm_link_error(what, i, im, "table import requires a WebAssembly.Table"); + ok = false; + } else if (t->table->size < td->min || (td->max != UINT64_MAX && t->table->declared_max > td->max)) { + /* V8's size checks name the import by number only; the imported + * maximum prints in hexadecimal. */ + char msg[300]; + if (t->table->size < td->min) + snprintf(msg, sizeof msg, "%s: table import %llu is smaller than initial %llu, got %u", what, + (unsigned long long)i, (unsigned long long)td->min, t->table->size); + else if (t->table->declared_max == UINT64_MAX) + snprintf(msg, sizeof msg, "%s: table import %llu has no maximum length; required: %llu", what, + (unsigned long long)i, (unsigned long long)td->max); + else + snprintf(msg, sizeof msg, "%s: table import %llu has a larger maximum size %llx than the module's declared maximum %llu", + what, (unsigned long long)i, (unsigned long long)t->table->declared_max, + (unsigned long long)td->max); + scr_wasm_throw_kind(SCR_WASM_LINK_ERROR, msg); + ok = false; + } else if (t->table->elem != im->type[0]) { + scr_wasm_link_error(what, i, im, "imported table does not match the expected type"); + ok = false; + } + } else if (im->kind == SCR_WASM_GLOBAL) { + ScrWasmObj *g = scr_wasm_unbox(v, SCR_WASM_K_GLOBAL); + bool mutable_ = desc->globals[im->index].mutable_ != 0; + char type = im->type[0]; + if (g) { + if (g->global->mutable_ != mutable_) { + scr_wasm_link_error(what, i, im, "imported global does not match the expected mutability"); + ok = false; + } else if (g->global->type != type) { + scr_wasm_link_error(what, i, im, "imported global does not match the expected type"); + ok = false; + } + } else if (mutable_) { + scr_wasm_link_error(what, i, im, "imported mutable global must be a WebAssembly.Global object"); + ok = false; + } else if (type == 'r') { + if (!v || (v->kind != SCR_DYN_NULL && !scr_wasm_funcref_of(v))) { + scr_wasm_link_error(what, i, im, SCR_WASM_FUNCREF_EXPECTED); + ok = false; + } + } else if (type == 'I' ? !(v && v->kind == SCR_DYN_BIGINT) + : type == 'x' ? false + : !(v && v->kind == SCR_DYN_NUM)) { + scr_wasm_link_error(what, i, im, + "global import must be a number, valid Wasm reference, or WebAssembly.Global object"); + ok = false; + } + } + } + if (!ok) { + for (uint64_t k = 0; k < n; k++) scr_dyn_release(values[k]); + free(values); + return NULL; + } + + ScrWasmInst *host = calloc(1, sizeof *host); + if (!host) scr_trap("scriptc: out of memory\n"); + host->rc = 1; + host->desc = desc; + size_t nf = (size_t)desc->func_count; + size_t slots = 4 + (size_t)desc->table_count + (size_t)desc->global_count; + host->inst = calloc(slots, sizeof(void *)); + host->funcs = calloc(nf ? nf : 1, sizeof *host->funcs); + host->funcrefs = calloc(nf ? nf : 1, sizeof *host->funcrefs); + host->export_fns = calloc(nf ? nf : 1, sizeof *host->export_fns); + host->export_names = calloc(nf ? nf : 1, sizeof *host->export_names); + host->tables = calloc(desc->table_count ? desc->table_count : 1, sizeof *host->tables); + host->globals = calloc(desc->global_count ? desc->global_count : 1, sizeof *host->globals); + host->data_dropped = calloc(desc->data_count ? desc->data_count : 1, 1); + host->elem_dropped = calloc(desc->elem_count ? desc->elem_count : 1, 1); + host->table_objs = calloc(desc->table_count ? desc->table_count : 1, sizeof *host->table_objs); + host->global_objs = calloc(desc->global_count ? desc->global_count : 1, sizeof *host->global_objs); + if (!host->inst || !host->funcs || !host->funcrefs || !host->export_fns || !host->export_names || + !host->tables || !host->globals || !host->data_dropped || !host->elem_dropped || !host->table_objs || + !host->global_objs) + scr_trap("scriptc: out of memory\n"); + void **inst = host->inst; + inst[0] = host; +#ifdef SCR_RC_AUDIT + scr_wasm_note_host(host); +#endif + for (size_t f = 0; f < nf; f++) { + host->funcrefs[f].code = desc->func_codes[f]; + host->funcrefs[f].type = desc->func_types[f]; + host->funcrefs[f].inst = inst; + } + inst[3] = host->funcrefs; + for (uint64_t i = 0; i < n; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + ScrDyn *v = values[i]; + switch (im->kind) { + case SCR_WASM_FUNC: + host->funcs[im->index] = v; + values[i] = NULL; + break; + case SCR_WASM_MEMORY: + host->memory = scr_wasm_memory_retain(scr_wasm_unbox(v, SCR_WASM_K_MEMORY)->memory); + host->memory_obj = v; + values[i] = NULL; + break; + case SCR_WASM_TABLE: + host->tables[im->index] = scr_wasm_table_retain(scr_wasm_unbox(v, SCR_WASM_K_TABLE)->table); + host->table_objs[im->index] = v; + values[i] = NULL; + break; + default: { + ScrWasmObj *g = scr_wasm_unbox(v, SCR_WASM_K_GLOBAL); + if (g) { + host->globals[im->index] = scr_wasm_global_retain(g->global); + host->global_objs[im->index] = v; + values[i] = NULL; + } else { + char type = im->type[0]; + ScrWasmGlobal *cell = scr_wasm_global_new(type, false); + uint64_t value = 0; + scr_wasm_from_js(type, v, &value); + if (type == 'x') scr_wasm_global_set_ref(cell, (void *)(uintptr_t)value); + else cell->value = value; + host->globals[im->index] = cell; + } + break; + } + } + scr_dyn_release(values[i]); + } + free(values); + if (desc->memory_count && !host->memory) { + host->memory = scr_wasm_memory_new(desc->memory_min, desc->memory_max, scr_wasm_guarded(desc), what); + if (!host->memory) return NULL; + } + if (host->memory) { + inst[1] = host->memory->base; + inst[2] = host->memory; + } + for (uint64_t t = 0; t < desc->table_count; t++) { + const ScrWasmTableDesc *td = &desc->tables[t]; + if (!host->tables[t]) host->tables[t] = scr_wasm_table_new((char)td->elem, td->min, td->max); + inst[4 + t] = host->tables[t]; + } + for (uint64_t g = 0; g < desc->global_count; g++) { + const ScrWasmGlobalDesc *gd = &desc->globals[g]; + if (!host->globals[g]) host->globals[g] = scr_wasm_global_new((char)gd->type, gd->mutable_ != 0); + inst[4 + desc->table_count + g] = host->globals[g]; + } +#if SCR_WASM_SIGNALS + if (scr_wasm_guarded(desc)) pthread_once(&scr_wasm_handler_once, scr_wasm_install_handler); +#endif + /* Globals and segments, then the start function. */ + bool was = scr_wasm_instantiating; + scr_wasm_instantiating = true; + int trap = scr_wasm_enter(scr_wasm_init_thunk, inst, NULL, NULL); + scr_wasm_leave(); + scr_wasm_instantiating = was; + if (trap) { + if (trap == SCR_WASM_TRAP_DATA_SEGMENT) { + snprintf(msg, sizeof msg, "%s: data segment %u is out of bounds (offset %u, length %u, memory size %llu)", what, + scr_wasm_segment_failure.seg, scr_wasm_segment_failure.offset, scr_wasm_segment_failure.length, + (unsigned long long)scr_wasm_segment_failure.size); + scr_wasm_throw_kind(SCR_WASM_RUNTIME_ERROR, msg); + } else if (trap == SCR_WASM_TRAP_TABLE_OOB) { + snprintf(msg, sizeof msg, "%s: table index is out of bounds", what); + scr_wasm_throw_kind(SCR_WASM_RUNTIME_ERROR, msg); + } else { + scr_wasm_throw_trap(trap); + } + return NULL; + } + if (desc->start) { + trap = scr_wasm_enter(scr_wasm_start_thunk, inst, NULL, NULL); + scr_wasm_leave(); + if (trap) { + scr_wasm_throw_trap(trap); + return NULL; + } + } + + ScrDyn *exports = scr_dyn_new_obj_null_proto(); + for (uint64_t i = 0; i < desc->export_count; i++) { + const ScrWasmExportDesc *ex = &desc->exports[i]; + ScrDyn *value; + if (ex->kind == SCR_WASM_FUNC) { + value = scr_wasm_function_value(host, (uint32_t)ex->index); + } else { + int kind = ex->kind == SCR_WASM_MEMORY ? SCR_WASM_K_MEMORY + : ex->kind == SCR_WASM_TABLE ? SCR_WASM_K_TABLE + : SCR_WASM_K_GLOBAL; + /* V8 exports an imported object itself, and one object per memory + * and table; a defined global gets a new object per export. */ + ScrDyn **slot = kind == SCR_WASM_K_MEMORY ? &host->memory_obj + : kind == SCR_WASM_K_TABLE ? &host->table_objs[ex->index] + : &host->global_objs[ex->index]; + if (*slot) { + value = scr_dyn_retain(*slot); + } else { + ScrWasmObj *o = scr_wasm_obj_new(kind); + o->inst = scr_wasm_inst_retain(host); + if (kind == SCR_WASM_K_MEMORY) o->memory = scr_wasm_memory_retain(host->memory); + else if (kind == SCR_WASM_K_TABLE) o->table = scr_wasm_table_retain(host->tables[ex->index]); + else o->global = scr_wasm_global_retain(host->globals[ex->index]); + value = scr_wasm_box(o); + if (kind != SCR_WASM_K_GLOBAL) *slot = scr_dyn_retain(value); + } + } + scr_dyn_obj_set(exports, ex->name, (size_t)ex->name_len, value); + } + ScrDyn *frozen = scr_dyn_freeze(exports); + scr_dyn_release(frozen); + host->exports = exports; + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_INSTANCE); + o->inst = host; /* takes the creation reference */ + return scr_wasm_box(o); +} + +ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports) { + scr_wasm_install(); + ScrWasmObj *m = scr_wasm_unbox(module, SCR_WASM_K_MODULE); + if (!m) { + scr_wasm_type_error("WebAssembly.Instance(): Argument 0 must be a WebAssembly.Module"); + return NULL; + } + return scr_wasm_instance_from(m->desc, imports, "WebAssembly.Instance()"); +} + +/* ── constructors and Module's static functions ────────────────────── */ + +/* Memories the program constructs are guarded exactly when the translated + * modules are (every module of a build shares the mode), so any of them + * can import one. */ +static bool scr_wasm_build_guarded(void) { +#if SCR_WASM_SIGNALS + return scr_wasm_registry.count > 0 && !(scr_wasm_registry.modules[0]->flags & SCR_WASM_DESC_BOUNDS_CHECKS); +#else + return false; +#endif +} + +/* V8's GetOptionalIntegerProperty: the descriptor's `name` property as an + * unsigned 32-bit integer within [lower, upper]. An absent or undefined + * property leaves *out unchanged, or throws "is required" when + * `required`. False with an exception pending. */ +static bool scr_wasm_limit(const ScrDyn *descriptor, const char *name, bool required, const char *what, + uint64_t lower, uint64_t upper, uint64_t *out) { + ScrDyn *v = scr_wasm_member(descriptor, name, strlen(name)); + if (scr_exc_pending()) return false; + char msg[240]; + if (!v) { + if (!required) return true; + snprintf(msg, sizeof msg, "%s: Property '%s' is required", what, name); + scr_wasm_type_error(msg); + return false; + } + char label[120]; + snprintf(label, sizeof label, "%s: Property '%s'", what, name); + uint32_t n; + bool ok = scr_wasm_u32_arg(v, label, &n); + scr_dyn_release(v); + if (!ok) return false; + /* V8 prints the lower bound in hexadecimal. */ + if (n < lower) { + snprintf(msg, sizeof msg, "%s: Property '%s': value %u is below the lower bound %llx", what, name, n, + (unsigned long long)lower); + scr_wasm_range_error(msg); + return false; + } + if (n > upper) { + snprintf(msg, sizeof msg, "%s: Property '%s': value %u is above the upper bound %llu", what, name, n, + (unsigned long long)upper); + scr_wasm_range_error(msg); + return false; + } + *out = n; + return true; +} + +/* ToString of a descriptor's type property (+1; NULL with an exception + * pending). An absent property reads as "undefined". */ +static ScrStr *scr_wasm_type_name(const ScrDyn *descriptor, const char *name) { + ScrDyn *v = scr_wasm_member(descriptor, name, strlen(name)); + if (scr_exc_pending()) return NULL; + if (!v) return scr_str_new("undefined", 9); + ScrStr *s = scr_dyn_string_coerce_js(v); + scr_dyn_release(v); + return s; +} + +static bool scr_wasm_str_is(const ScrStr *s, const char *text) { + size_t n = strlen(text); + return s->len == n && !memcmp(s->data, text, n); +} + +/* A descriptor's boolean property (ToBoolean; false when absent). */ +static bool scr_wasm_flag(const ScrDyn *descriptor, const char *name, bool *out) { + ScrDyn *v = scr_wasm_member(descriptor, name, strlen(name)); + if (scr_exc_pending()) return false; + *out = v && scr_dyn_truthy(v); + scr_dyn_release(v); + return true; +} + +static bool scr_wasm_absent(const ScrDyn *v) { return !v || v->kind == SCR_DYN_UNDEF; } + +ScrDyn *scr_wasm_memory_construct(ScrDyn *descriptor) { + scr_wasm_install(); + const char *what = "WebAssembly.Memory()"; + if (!scr_wasm_is_object(descriptor)) { + scr_wasm_type_error("WebAssembly.Memory(): Argument 0 must be a memory descriptor"); + return NULL; + } + uint64_t initial = 0, maximum = UINT64_MAX; + bool shared; + if (!scr_wasm_limit(descriptor, "initial", true, what, 0, SCR_WASM_MAX_PAGES, &initial) || + !scr_wasm_limit(descriptor, "maximum", false, what, initial, SCR_WASM_MAX_PAGES, &maximum) || + !scr_wasm_flag(descriptor, "shared", &shared)) + return NULL; + if (shared && maximum == UINT64_MAX) { + scr_wasm_type_error("WebAssembly.Memory(): If shared is true, maximum property should be defined."); + return NULL; + } + if (shared) { + scr_wasm_type_error("WebAssembly.Memory(): shared memories are not supported yet (scriptc does not translate " + "WebAssembly threads; see https://scriptc.dev/limitations)"); + return NULL; + } + ScrWasmMemory *m = scr_wasm_memory_new(initial, maximum, scr_wasm_build_guarded(), what); + if (!m) return NULL; + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_MEMORY); + o->memory = m; + return scr_wasm_box(o); +} + +ScrDyn *scr_wasm_table_construct(ScrDyn *descriptor, ScrDyn *value) { + scr_wasm_install(); + const char *what = "WebAssembly.Table()"; + if (!scr_wasm_is_object(descriptor)) { + scr_wasm_type_error("WebAssembly.Table(): Argument 0 must be a table descriptor"); + return NULL; + } + ScrStr *type = scr_wasm_type_name(descriptor, "element"); + if (!type) return NULL; + char elem = scr_wasm_str_is(type, "anyfunc") ? 'r' : scr_wasm_str_is(type, "externref") ? 'x' : 0; + scr_str_release(type); + if (!elem) { + scr_wasm_type_error("WebAssembly.Table(): Descriptor property 'element' must be a WebAssembly reference type"); + return NULL; + } + uint64_t initial = 0, maximum = UINT64_MAX; + if (!scr_wasm_limit(descriptor, "initial", true, what, 0, SCR_WASM_MAX_TABLE, &initial) || + !scr_wasm_limit(descriptor, "maximum", false, what, initial, 4294967295u, &maximum)) + return NULL; + uint64_t init = scr_wasm_default_value(elem); + if (!scr_wasm_absent(value) && + !scr_wasm_from_js_as(elem, value, &init, + "WebAssembly.Table(): Argument 2 must be undefined or a value of type compatible with the " + "type of the new table: " SCR_WASM_FUNCREF_EXPECTED ".")) + return NULL; + ScrWasmTable *t = scr_wasm_table_new(elem, initial, maximum); + for (uint32_t i = 0; i < t->size; i++) t->elems[i] = scr_wasm_ref_retain(elem, (void *)(uintptr_t)init); + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_TABLE); + o->table = t; + return scr_wasm_box(o); +} + +ScrDyn *scr_wasm_global_construct(ScrDyn *descriptor, ScrDyn *value) { + scr_wasm_install(); + if (!scr_wasm_is_object(descriptor)) { + scr_wasm_type_error("WebAssembly.Global(): Argument 0 must be a global descriptor"); + return NULL; + } + bool mutable_; + if (!scr_wasm_flag(descriptor, "mutable", &mutable_)) return NULL; + ScrStr *name = scr_wasm_type_name(descriptor, "value"); + if (!name) return NULL; + static const char *const names[] = {"i32", "i64", "f32", "f64", "externref", "anyfunc"}; + static const char letters[] = {'i', 'I', 'f', 'F', 'x', 'r'}; + char type = 0; + for (size_t i = 0; i < sizeof letters; i++) + if (scr_wasm_str_is(name, names[i])) type = letters[i]; + bool v128 = scr_wasm_str_is(name, "v128"); + scr_str_release(name); + if (v128) { + scr_wasm_type_error("WebAssembly.Global(): A global of type 'v128' cannot be created in JavaScript"); + return NULL; + } + if (!type) { + scr_wasm_type_error("WebAssembly.Global(): Descriptor property 'value' must be a WebAssembly type"); + return NULL; + } + uint64_t v = scr_wasm_default_value(type); + if (!scr_wasm_absent(value) && + !scr_wasm_from_js_as(type, value, &v, "WebAssembly.Global(): " SCR_WASM_FUNCREF_EXPECTED)) + return NULL; + ScrWasmGlobal *g = scr_wasm_global_new(type, mutable_); + if (type == 'x') scr_wasm_global_set_ref(g, (void *)(uintptr_t)v); + else g->value = v; + ScrWasmObj *o = scr_wasm_obj_new(SCR_WASM_K_GLOBAL); + o->global = g; + return scr_wasm_box(o); +} + +/* `WebAssembly.Module(...)` and the other classes called without new. */ +ScrDyn *scr_wasm_construct_without_new(double kind, ScrDyn *arg0, ScrDyn *arg1) { + (void)arg0; + (void)arg1; + static const char *const names[] = {"Module", "Instance", "Memory", "Table", "Global"}; + const char *name = names[(int)kind]; + char msg[160]; + snprintf(msg, sizeof msg, "WebAssembly.%s(): WebAssembly.%s must be invoked with 'new'", name, name); + scr_wasm_type_error(msg); + return NULL; +} + +static const ScrWasmModuleDesc *scr_wasm_module_arg(const ScrDyn *v, const char *what) { + ScrWasmObj *m = scr_wasm_unbox(v, SCR_WASM_K_MODULE); + if (m) return m->desc; + char msg[160]; + snprintf(msg, sizeof msg, "%s: Argument 0 must be a WebAssembly.Module", what); + scr_wasm_type_error(msg); + return NULL; +} + +static void scr_wasm_set_text(ScrDyn *obj, const char *key, const char *text, size_t len) { + ScrStr *s = scr_str_new(text, len); + scr_dyn_obj_set(obj, key, strlen(key), scr_dyn_new_str(s)); + scr_str_release(s); +} + +static const char *const scr_wasm_kind_names[] = {"function", "table", "memory", "global"}; + +ScrDyn *scr_wasm_module_imports(ScrDyn *module) { + const ScrWasmModuleDesc *desc = scr_wasm_module_arg(module, "WebAssembly.Module.imports()"); + if (!desc) return NULL; + ScrDyn *out = scr_dyn_new_arr(); + for (uint64_t i = 0; i < desc->import_count; i++) { + const ScrWasmImportDesc *im = &desc->imports[i]; + ScrDyn *row = scr_dyn_new_obj(); + scr_wasm_set_text(row, "module", im->module, (size_t)im->module_len); + scr_wasm_set_text(row, "name", im->name, (size_t)im->name_len); + scr_wasm_set_text(row, "kind", scr_wasm_kind_names[im->kind], strlen(scr_wasm_kind_names[im->kind])); + scr_dyn_arr_push(out, row); + } + return out; +} + +ScrDyn *scr_wasm_module_exports(ScrDyn *module) { + const ScrWasmModuleDesc *desc = scr_wasm_module_arg(module, "WebAssembly.Module.exports()"); + if (!desc) return NULL; + ScrDyn *out = scr_dyn_new_arr(); + for (uint64_t i = 0; i < desc->export_count; i++) { + const ScrWasmExportDesc *ex = &desc->exports[i]; + ScrDyn *row = scr_dyn_new_obj(); + scr_wasm_set_text(row, "name", ex->name, (size_t)ex->name_len); + scr_wasm_set_text(row, "kind", scr_wasm_kind_names[ex->kind], strlen(scr_wasm_kind_names[ex->kind])); + scr_dyn_arr_push(out, row); + } + return out; +} + +/* A fresh ArrayBuffer per matching custom section, in module order. */ +ScrDyn *scr_wasm_module_custom_sections(ScrDyn *module, ScrDyn *name) { + const ScrWasmModuleDesc *desc = scr_wasm_module_arg(module, "WebAssembly.Module.customSections()"); + if (!desc) return NULL; + if (scr_wasm_absent(name)) { + scr_wasm_type_error("WebAssembly.Module.customSections(): Argument 1 is required"); + return NULL; + } + ScrStr *wanted = scr_dyn_string_coerce_js(name); + if (!wanted) return NULL; + ScrDyn *out = scr_dyn_new_arr(); + for (uint64_t i = 0; i < desc->custom_count; i++) { + const ScrWasmCustomDesc *c = &desc->customs[i]; + if (c->name_len != wanted->len || memcmp(c->name, wanted->data, wanted->len) != 0) continue; + ScrBytes *bytes = scr_bytes_from_data(c->data, (size_t)c->len); + scr_dyn_arr_push(out, scr_array_buffer_from_bytes(bytes)); + scr_bytes_release(bytes); + } + scr_str_release(wanted); + return out; +} + +/* `v.valueOf()` on a WebAssembly-typed receiver: a Global's value; any + * other value answers what the generic valueOf answers. */ +ScrDyn *scr_wasm_value_of(ScrDyn *value) { + ScrWasmObj *g = scr_wasm_unbox(value, SCR_WASM_K_GLOBAL); + if (g) return scr_wasm_global_value(g->global); + ScrStr *what = scr_str_new("valueOf", 7); + ScrDyn *out = scr_dyn_value_of(value, what); + scr_str_release(what); + return out; +} + +/* ── promises ────────────────────────────────────────────────────────── */ + +/* A promise settled now: fulfilled with value, or rejected with the + * pending exception. */ +static ScrPromise *scr_wasm_settled(ScrDyn *value) { + if (value) { + ScrPromise *p = scr_promise_settled_ref(value, &scr_dyn_retain_v, &scr_dyn_release_v, &scr_dyn_trace_v); + return p; + } + ScrPromise *p = scr_promise_new(); + scr_promise_reject_pending(p); + return p; +} + +/* compile and instantiate over bytes settle on a later loop turn, after + * the current microtasks, as V8's compile task does. */ +enum { SCR_WASM_JOB_COMPILE, SCR_WASM_JOB_INSTANTIATE }; + +static void scr_wasm_job_fire(ScrClosure *clo) { + ScrPromise *p = scr_box_get_ref(clo->caps[0]); + ScrDyn *source = scr_box_get_ref(clo->caps[1]); + ScrDyn *imports = scr_box_get_ref(clo->caps[2]); + int job = (int)scr_box_get_f64(clo->caps[3]); + const char *what = job == SCR_WASM_JOB_COMPILE ? "WebAssembly.compile()" : "WebAssembly.instantiate()"; + ScrDyn *module = scr_wasm_module_from(source, what, "a buffer source"); + ScrDyn *result = module; + if (module && job == SCR_WASM_JOB_INSTANTIATE) { + ScrDyn *instance = scr_wasm_instance_from(scr_wasm_unbox(module, SCR_WASM_K_MODULE)->desc, imports, what); + if (instance) { + result = scr_dyn_new_obj(); + scr_dyn_obj_set(result, "module", 6, module); + scr_dyn_obj_set(result, "instance", 8, instance); + } else { + scr_dyn_release(module); + result = NULL; + } + } + if (result) { + scr_promise_resolve_dyn(p, result); /* moves result in */ + } else { + scr_promise_reject_pending(p); + } + scr_dyn_release(source); + scr_dyn_release(imports); + scr_promise_release(p); +} + +static ScrPromise *scr_wasm_defer(int job, ScrDyn *source, ScrDyn *imports) { + ScrPromise *p = scr_promise_new(); + ScrClosure *clo = scr_closure_new((void *)scr_wasm_job_fire, 4); + clo->caps[0] = scr_box_new_obj(scr_promise_retain_v, scr_promise_release_v, NULL); + scr_box_set_ref(clo->caps[0], scr_promise_retain(p)); + clo->caps[1] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(clo->caps[1], scr_dyn_retain(source ? source : scr_dyn_undefined())); + clo->caps[2] = scr_box_new_obj(scr_dyn_retain_v, scr_dyn_release_v, scr_dyn_trace_v); + scr_box_set_ref(clo->caps[2], scr_dyn_retain(imports ? imports : scr_dyn_undefined())); + clo->caps[3] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(clo->caps[3], (double)job); + scr_loop_defer(clo); + return p; +} + +static bool scr_wasm_is_buffer_source(const ScrDyn *v) { + return v && (v->kind == SCR_DYN_BYTES || (v->kind == SCR_DYN_HANDLE && scr_array_buffer_is(v))); +} + +ScrPromise *scr_wasm_compile(ScrDyn *bytes) { + scr_wasm_install(); + if (!scr_wasm_is_buffer_source(bytes)) { + scr_wasm_type_error("WebAssembly.compile(): Argument 0 must be a buffer source"); + return scr_wasm_settled(NULL); + } + return scr_wasm_defer(SCR_WASM_JOB_COMPILE, bytes, NULL); +} + +ScrPromise *scr_wasm_instantiate(ScrDyn *source, ScrDyn *imports) { + scr_wasm_install(); + ScrWasmObj *m = scr_wasm_unbox(source, SCR_WASM_K_MODULE); + if (m) return scr_wasm_settled(scr_wasm_instance_from(m->desc, imports, "WebAssembly.instantiate()")); + if (!scr_wasm_is_buffer_source(source)) { + scr_wasm_type_error("WebAssembly.instantiate(): Argument 0 must be a buffer source or a WebAssembly.Module object"); + return scr_wasm_settled(NULL); + } + if (imports && imports->kind != SCR_DYN_UNDEF && !scr_wasm_is_object(imports)) { + scr_wasm_type_error("WebAssembly.instantiate(): Argument 1 must be an object"); + return scr_wasm_settled(NULL); + } + return scr_wasm_defer(SCR_WASM_JOB_INSTANTIATE, source, imports); +} + +/* ── instanceof and the error constructors ───────────────────────────── */ + +bool scr_wasm_is(const ScrDyn *value, double kind) { + int k = (int)kind; + if (k <= SCR_WASM_K_GLOBAL) return scr_wasm_unbox(value, k) != NULL; + ScrError *e = value ? scr_errdyn_err_of(value) : NULL; + if (!e) return false; + int brand = scr_wasm_error_brand(e); + scr_error_release(e); + return brand == k - 5; +} + +ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options) { + ScrStr *text = NULL; + if (message && message->kind != SCR_DYN_UNDEF) { + text = scr_dyn_string_coerce_js(message); + if (!text) return NULL; + } + ScrError *e = scr_wasm_error_make((int)kind - 5, text); + if (!text) e->message_present = false; + scr_str_release(text); + if (options && scr_wasm_is_object(options)) { + ScrDyn *cause = scr_wasm_member(options, "cause", 5); + if (cause) { + scr_error_define_cause(e, cause); + scr_dyn_release(cause); + } + } + ScrDyn *d = scr_dyn_from_error(e); + scr_error_release(e); + return d; +} + +/* ── the namespace and its functions as values ───────────────────────── */ + +enum { SCR_WASM_FN_COMPILE, SCR_WASM_FN_INSTANTIATE, SCR_WASM_FN_VALIDATE }; +static const char *const scr_wasm_fn_names[] = {"compile", "instantiate", "validate"}; + +static ScrDyn *scr_wasm_fn_call(ScrClosure *clo, ScrDyn *const *args, size_t argc) { + int kind = (int)scr_box_get_f64(clo->caps[0]); + ScrDyn *a0 = argc > 0 ? args[0] : scr_dyn_undefined(); + ScrDyn *a1 = argc > 1 ? args[1] : scr_dyn_undefined(); + if (kind == SCR_WASM_FN_VALIDATE) { + bool ok = scr_wasm_validate(a0); + if (scr_exc_pending()) return NULL; + return scr_dyn_new_bool(ok); + } + ScrPromise *p = kind == SCR_WASM_FN_COMPILE ? scr_wasm_compile(a0) : scr_wasm_instantiate(a0, a1); + ScrDyn *boxed = scr_dyn_new_promise(p); + scr_promise_release(p); + return boxed; +} + +/* WebAssembly.compile, instantiate and validate as function values (+1), + * one per thread and function, as in Node. */ +static SCR_TL ScrDyn *scr_wasm_fns[3]; +static SCR_TL ScrDyn *scr_wasm_ns; + +ScrDyn *scr_wasm_function(double kind) { + ScrDyn **fns = scr_wasm_fns; + int k = (int)kind; + if (!fns[k]) { + ScrClosure *closure = scr_closure_new(NULL, 1); + closure->caps[0] = scr_box_new(SCR_BOX_F64); + scr_box_set_f64(closure->caps[0], (double)k); + fns[k] = scr_dyn_new_func(closure, scr_wasm_fn_call, k == SCR_WASM_FN_INSTANTIATE ? 1 : 1, "", + scr_wasm_fn_names[k]); + } + return scr_dyn_retain(fns[k]); +} + +/* globalThis.WebAssembly as a value (+1): an object with the namespace's + * functions. The classes are not values yet: their constructors and + * instanceof lower at their use sites. */ +ScrDyn *scr_wasm_namespace(void) { + if (!scr_wasm_ns) { + scr_wasm_ns = scr_dyn_new_obj(); + for (int k = 0; k < 3; k++) + scr_dyn_obj_set(scr_wasm_ns, scr_wasm_fn_names[k], strlen(scr_wasm_fn_names[k]), scr_wasm_function(k)); + } + return scr_dyn_retain(scr_wasm_ns); +} + +/* The global object seen through a cast to a type with a `WebAssembly` + * member (`globalThis as unknown as { WebAssembly: ... }`): an object + * whose WebAssembly member is the namespace (+1). */ +ScrDyn *scr_wasm_global_view(void) { + ScrDyn *view = scr_dyn_new_obj(); + scr_dyn_obj_set(view, "WebAssembly", 11, scr_wasm_namespace()); + return view; +} + +#ifdef SCR_RC_AUDIT +static void scr_wasm_teardown(void) { + for (size_t i = 0; i < scr_wasm_host_count; i++) { + ScrWasmInst *host = scr_wasm_hosts[i]; + const ScrWasmModuleDesc *desc = host->desc; + scr_dyn_release(host->exports); + scr_dyn_release(host->memory_obj); + for (uint64_t t = 0; t < desc->table_count; t++) scr_dyn_release(host->table_objs[t]); + for (uint64_t g = 0; g < desc->global_count; g++) scr_dyn_release(host->global_objs[g]); + free(host->table_objs); + free(host->global_objs); + for (uint64_t f = 0; f < desc->func_count; f++) { + scr_dyn_release(host->export_fns[f]); + scr_dyn_release(host->funcs[f]); + free(host->export_names[f]); + } + scr_wasm_memory_release(host->memory); + for (uint64_t t = 0; t < desc->table_count; t++) scr_wasm_table_release(host->tables[t]); + for (uint64_t g = 0; g < desc->global_count; g++) scr_wasm_global_release(host->globals[g]); + free(host->funcs); + free(host->funcrefs); + free(host->export_fns); + free(host->export_names); + free(host->tables); + free(host->globals); + free(host->data_dropped); + free(host->elem_dropped); + free(host->inst); + free(host); + } + free(scr_wasm_hosts); + scr_wasm_hosts = NULL; + scr_wasm_host_count = scr_wasm_host_cap = 0; + for (int k = 0; k < 3; k++) { + scr_dyn_release(scr_wasm_fns[k]); + scr_wasm_fns[k] = NULL; + } + scr_dyn_release(scr_wasm_ns); + scr_wasm_ns = NULL; +} +#endif diff --git a/packages/runtime/src/scr_wasm.h b/packages/runtime/src/scr_wasm.h new file mode 100644 index 000000000..f7a791a26 --- /dev/null +++ b/packages/runtime/src/scr_wasm.h @@ -0,0 +1,31 @@ +/* scriptc runtime — the WebAssembly JavaScript API (scr_wasm.c): the entry + * points the LLVM backend calls for the wasm.* lib functions. Included by + * scr_runtime.h, which says where new declarations go. The contract with + * translated modules, including scr_wasm_registry, is in scr_wasm_abi.h. */ +#ifndef SCR_WASM_H +#define SCR_WASM_H + +#include "scr_async.h" +#include "scr_dyn.h" +#include "scr_wasm_abi.h" + +ScrPromise *scr_wasm_compile(ScrDyn *bytes); +bool scr_wasm_validate(ScrDyn *bytes); +ScrPromise *scr_wasm_instantiate(ScrDyn *source, ScrDyn *imports); +ScrDyn *scr_wasm_module_new(ScrDyn *bytes); +ScrDyn *scr_wasm_instance_new(ScrDyn *module, ScrDyn *imports); +bool scr_wasm_is(const ScrDyn *value, double kind); +ScrDyn *scr_wasm_error_new(double kind, ScrDyn *message, ScrDyn *options); +ScrDyn *scr_wasm_function(double kind); +ScrDyn *scr_wasm_namespace(void); +ScrDyn *scr_wasm_global_view(void); +ScrDyn *scr_wasm_memory_construct(ScrDyn *descriptor); +ScrDyn *scr_wasm_table_construct(ScrDyn *descriptor, ScrDyn *value); +ScrDyn *scr_wasm_global_construct(ScrDyn *descriptor, ScrDyn *value); +ScrDyn *scr_wasm_construct_without_new(double kind, ScrDyn *arg0, ScrDyn *arg1); +ScrDyn *scr_wasm_module_imports(ScrDyn *module); +ScrDyn *scr_wasm_module_exports(ScrDyn *module); +ScrDyn *scr_wasm_module_custom_sections(ScrDyn *module, ScrDyn *name); +ScrDyn *scr_wasm_value_of(ScrDyn *value); + +#endif /* SCR_WASM_H */ diff --git a/packages/runtime/src/scr_wasm_abi.h b/packages/runtime/src/scr_wasm_abi.h new file mode 100644 index 000000000..083a2f77a --- /dev/null +++ b/packages/runtime/src/scr_wasm_abi.h @@ -0,0 +1,221 @@ +/* The contract between translated WebAssembly modules and the runtime + * (scr_wasm.c). The compiler's translator (packages/compiler/src/wasm/ + * translate.ts) emits each module's code, an init function and one + * ScrWasmModuleDesc as LLVM IR in the program module, plus the registry + * scr_wasm_registry listing every translated module. The runtime + * implements the JavaScript API once, generically, over these + * descriptors; it never sees the translator's own data structures, and + * the translator never sees the runtime's. + * + * Every descriptor field is pointer- or 64-bit sized so the emitted LLVM + * structs and these C structs have the same layout without padding rules. + * Signatures are strings "params:results" over the letters i (i32), + * I (i64), f (f32), F (f64), r (funcref) and x (externref). Values travel + * in uint64_t slots: f32 and f64 as their bit patterns, references as + * pointers (externref: a borrowed ScrDyn *, NULL for null; funcref: a + * ScrWasmFuncRef *, NULL for null). + * + * Instance layout (the first argument of every translated function): + * 0 ScrWasmInst *host runtime state + * 8 uint8_t *memory_base memory 0's data + * 16 ScrWasmMemory *memory memory 0, or NULL + * 24 ScrWasmFuncRef *funcrefs one record per function + * 32 ScrWasmTable *tables[table_count] + * .. uint64_t *globals[global_count] one cell per global + * With guard pages (no SCR_WASM_DESC_BOUNDS_CHECKS) memory_base never + * changes: the memory is reserved once at its largest size plus a guard + * region, and grow only commits pages. With explicit bounds checks the + * memory is reserved at its largest size without a guard region, so + * memory_base does not change either where the host can reserve address + * space; the translated code still reloads memory_base and the byte + * length after every call. */ +#ifndef SCR_WASM_ABI_H +#define SCR_WASM_ABI_H +#include +#include +#include + +#define SCR_WASM_ABI_VERSION 2 + +enum { SCR_WASM_FUNC = 0, SCR_WASM_TABLE = 1, SCR_WASM_MEMORY = 2, SCR_WASM_GLOBAL = 3 }; + +/* Trap codes the translated code passes to scr_wasm_trap (translate.ts + * TRAP_*), plus the runtime's own. */ +enum { + SCR_WASM_TRAP_NONE = 0, + SCR_WASM_TRAP_UNREACHABLE = 1, + SCR_WASM_TRAP_MEMORY_OOB = 2, + SCR_WASM_TRAP_DIV_BY_ZERO = 3, + SCR_WASM_TRAP_DIV_UNREPRESENTABLE = 4, + SCR_WASM_TRAP_REM_BY_ZERO = 5, + SCR_WASM_TRAP_FLOAT_UNREPRESENTABLE = 6, + SCR_WASM_TRAP_TABLE_OOB = 7, + SCR_WASM_TRAP_SIGNATURE = 8, + SCR_WASM_TRAP_NULL = 9, + SCR_WASM_TRAP_ELEM_SEGMENT = 10, + /* Runtime-only codes. */ + SCR_WASM_TRAP_EXHAUSTION = 100, /* RangeError: Maximum call stack size exceeded */ + SCR_WASM_TRAP_HOST = 101, /* a JavaScript exception is pending */ + SCR_WASM_TRAP_DATA_SEGMENT = 102, /* instantiation: active data segment out of bounds */ +}; + +/* Descriptor flags. */ +#define SCR_WASM_DESC_BOUNDS_CHECKS 1u + +typedef void (*ScrWasmThunk)(void *inst, const uint64_t *args, uint64_t *results); +/* A signature's call thunk: calls `code` (a function of that signature, + * `(inst, params...) -> results`) with the arguments in slots. */ +typedef void (*ScrWasmCallThunk)(void *code, void *inst, const uint64_t *args, uint64_t *results); + +typedef struct { + const char *module; + uint64_t module_len; + const char *name; + uint64_t name_len; + uint64_t kind; + /* The index among imports of this kind (function imports: the function + * index). */ + uint64_t index; + /* Functions: the signature; tables: the element letter; globals: the + * value letter; memories: "". */ + const char *type; +} ScrWasmImportDesc; + +typedef struct { + const char *name; + uint64_t name_len; + uint64_t kind; + uint64_t index; /* function, table, memory or global index */ + const char *type; + ScrWasmThunk call; /* functions */ +} ScrWasmExportDesc; + +typedef struct { + uint64_t elem; /* 'r' or 'x' */ + uint64_t min; + uint64_t max; /* UINT64_MAX when absent */ + uint64_t imported; +} ScrWasmTableDesc; + +typedef struct { + uint64_t type; /* signature letter */ + uint64_t mutable_; + uint64_t imported; +} ScrWasmGlobalDesc; + +typedef struct { + const uint8_t *data; + uint64_t len; +} ScrWasmDataDesc; + +/* An element segment: per item a function index, -1 for null, or + * -(2 + g) for the value of global g. */ +typedef struct { + uint64_t elem; + uint64_t count; + const int64_t *items; +} ScrWasmElemDesc; + +/* A custom section: its name and payload, in module order. Only programs + * that call WebAssembly.Module.customSections carry them (the translator + * emits none otherwise, so debug sections cost nothing). */ +typedef struct { + const char *name; + uint64_t name_len; + const uint8_t *data; + uint64_t len; +} ScrWasmCustomDesc; + +typedef struct ScrWasmModuleDesc { + uint8_t sha256[32]; + uint64_t byte_length; + uint64_t abi_version; + uint64_t flags; + uint64_t import_count; + const ScrWasmImportDesc *imports; + uint64_t export_count; + const ScrWasmExportDesc *exports; + uint64_t func_count; /* imported + defined */ + uint64_t imported_func_count; + /* Per function: the native code `(inst, params...) -> results` for + * functions a reference can name (NULL otherwise), the canonical + * signature identity call_indirect compares, and the signature text. */ + void *const *func_codes; + const uint64_t *func_types; + const char *const *func_sigs; + uint64_t table_count; + const ScrWasmTableDesc *tables; + uint64_t memory_count; /* 0 or 1 */ + uint64_t memory_min; /* pages */ + uint64_t memory_max; /* pages, UINT64_MAX when absent */ + uint64_t global_count; + const ScrWasmGlobalDesc *globals; + uint64_t data_count; + const ScrWasmDataDesc *datas; + uint64_t elem_count; + const ScrWasmElemDesc *elems; + /* Globals, table initializers and active segments, under the trap + * guard; then the start function `void (inst)`, or NULL. */ + void (*init)(void *inst); + void (*start)(void *inst); + uint64_t custom_count; + const ScrWasmCustomDesc *customs; + /* Per function: the call thunk of its signature for referenced + * functions the module does not export (NULL otherwise), so any + * function reference can be called from JavaScript. */ + const ScrWasmCallThunk *func_call_thunks; +} ScrWasmModuleDesc; + +/* The registry the compiler emits in the program module of every program + * that uses the WebAssembly API (count may be 0). */ +typedef struct { + uint64_t count; + const ScrWasmModuleDesc *const *modules; +} ScrWasmRegistry; +extern const ScrWasmRegistry scr_wasm_registry; + +/* Runtime objects the translated code reads at fixed offsets. */ +typedef struct ScrWasmMemory { + uint8_t *base; /* 0 */ + uint64_t size; /* 8: bytes */ + uint64_t max_pages; /* 16 */ + uint64_t reserved; /* 24: bytes of address space reserved at base */ + size_t rc; + bool guarded; + struct ScrDyn *buffer; /* the cached ArrayBuffer */ + uint64_t buffer_size; /* the size it was created at */ + uint64_t declared_max; /* pages; UINT64_MAX when the memory has no maximum */ + bool buffer_exposed; /* a buffer over the storage was handed out */ + /* The byte stores of every ArrayBuffer handed out over this memory's + * storage (retained). The storage outlives the memory while JavaScript + * still holds one of them or a view over it. */ + struct ScrBytes **exposed; + size_t exposed_count; + struct ScrWasmMemory *next_parked; /* released, storage still exposed */ +} ScrWasmMemory; + +typedef struct ScrWasmFuncRef { + void *code; /* 0 */ + uint64_t type; /* 8 */ + void *inst; /* 16: the instance the code expects; its host and the + * record's position in its funcrefs name the function */ +} ScrWasmFuncRef; + +typedef struct ScrWasmTable { + void **elems; /* 0: ScrWasmFuncRef * or ScrDyn * (retained) */ + uint32_t size; /* 8 */ + uint32_t max; + size_t rc; + char elem; /* 'r' or 'x' */ + uint64_t declared_max; /* UINT64_MAX when the table has no maximum */ +} ScrWasmTable; + +/* A global's cell: the value in the low bytes of `value`. */ +typedef struct ScrWasmGlobal { + uint64_t value; /* 0 */ + size_t rc; + char type; + bool mutable_; +} ScrWasmGlobal; + +#endif diff --git a/packages/runtime/src/scr_worker_events.c b/packages/runtime/src/scr_worker_events.c index 53041f3d5..b4efa3e0d 100644 --- a/packages/runtime/src/scr_worker_events.c +++ b/packages/runtime/src/scr_worker_events.c @@ -122,7 +122,7 @@ static ScrClosure *scr_worker_stdin_callback(ScrWorkerThread *thread, void *fire } static ScrStream *scr_worker_stdin_new(ScrWorkerThread *thread) { - return scr_worker_stdio_ops->writable( + return scr_worker_stdio_ops->writable("WritableWorkerStdio", scr_worker_stdin_callback(thread, (void *)scr_worker_stdin_write), scr_worker_stdin_write, scr_worker_stdin_callback(thread, (void *)scr_worker_stdin_final), scr_worker_stdin_final); } @@ -1350,7 +1350,8 @@ static ScrDyn *scr_worker_new_options(double root, ScrStr *filename, ScrDyn *opt for (int i = 0; i < 2; i++) { view->stdio_captured[i] = (capture >> i) & 1; /* without the stream unit nothing can read the stream: the output is dropped */ - if (view->stdio_captured[i] && scr_worker_stdio_ops) view->stdio[i] = scr_worker_stdio_ops->readable(); + if (view->stdio_captured[i] && scr_worker_stdio_ops) + view->stdio[i] = scr_worker_stdio_ops->readable("ReadableWorkerStdio"); } view->stdin_captured = capture & 4; if (view->stdin_captured && scr_worker_stdio_ops) view->stdin_stream = scr_worker_stdin_new(thread); diff --git a/packages/runtime/test/sha-kernels.test.ts b/packages/runtime/test/sha-kernels.test.ts index 724841fb1..6f7759348 100644 --- a/packages/runtime/test/sha-kernels.test.ts +++ b/packages/runtime/test/sha-kernels.test.ts @@ -42,7 +42,7 @@ const runtimeSources = [ ]; // Compiled once with ASan, UBSan and the RC audit. The test runs the SHA-NI and portable -// SHA-1/SHA-256 blocks over the same inputs and checks known vectors. +// SHA-1/SHA-256 blocks and the SHA-512 kernels over the same inputs and checks known vectors. beforeAll(async () => { await mkdir(join(testDir, "build"), { recursive: true }); await execFileAsync("clang", [ @@ -64,7 +64,7 @@ beforeAll(async () => { ]); }, 300_000); -test("SHA-1 and SHA-256: the SHA-NI blocks match the portable blocks", async () => { +test("SHA-1, SHA-256, SHA-384 and SHA-512: the CPU kernels match the portable blocks", async () => { const { stderr } = await execFileAsync(bin, [], { env: { ...process.env, UBSAN_OPTIONS: "halt_on_error=1" }, }); diff --git a/packages/runtime/test/test_cycle.c b/packages/runtime/test/test_cycle.c index ce1e9ba85..d4ea39a93 100644 --- a/packages/runtime/test/test_cycle.c +++ b/packages/runtime/test/test_cycle.c @@ -1054,6 +1054,35 @@ static void check_rebuffer_past_mature(void) { * cycle-headered count has grown by SCR_CYC_NURSERY_ALLOCATIONS (4096 in * scr_cycle.c) since the last one, so the dead nodes left uncollected stay * within that much plus the ring in hand. */ +size_t scr_cyc_backlog_threshold_at(unsigned gen); +void scr_cyc_forget_backlog_at(unsigned gen); + +/* A heap that grows while its mature buffer stays empty: every release is + * of one old object, so the mature passes that growth brings have no + * candidates and only widen the mature growth fraction. Until a mature pass + * has had candidates, the backlog threshold widens with it, so a program + * that then reads the structure it built does not walk a quarter of it at + * once to find nothing. */ +static void check_unjudged_backlog_follows_growth(void) { + enum { LIVE = 65536 }; + Node **leaves = calloc(LIVE, sizeof(*leaves)); + if (!leaves) scr_trap("cycle test: out of memory\n"); + Node *anchor = make_leaf(); + age_to_old(anchor); + scr_cyc_forget_backlog_at(SCR_CYC_MATURE); + for (size_t i = 0; i < LIVE; i++) { + leaves[i] = make_leaf(); + read_live(anchor); + } + check(scr_cyc_backlog_threshold_at(SCR_CYC_MATURE) > scr_cyc_live / 4, + "an unjudged mature backlog stayed at a quarter of the live heap"); + drop_leaves(leaves, LIVE); + anchor->rc--; + scr_cyc_on_dead(anchor); + node_free(anchor); + free(leaves); +} + static void check_allocation_growth_runs_passes(void) { enum { RINGS = 80, NODES_PER_RING = 64, ALLOCATION_WINDOW = 4096 }; scr_collect_cycles(); @@ -1364,6 +1393,7 @@ int main(void) { check_publication_takes_candidates(); check_limited_pass(); check_large_old_buffer_with_nursery_candidates(); + check_unjudged_backlog_follows_growth(); printf("cycle collection checks passed: threshold=%zu growth_cap=%zu\n", configured_nursery_threshold(), configured_growth_cap()); return 0; diff --git a/packages/runtime/test/test_sha_kernels.c b/packages/runtime/test/test_sha_kernels.c index 7f854216e..da7d79235 100644 --- a/packages/runtime/test/test_sha_kernels.c +++ b/packages/runtime/test/test_sha_kernels.c @@ -1,9 +1,11 @@ -/* SHA-1 and SHA-256 block kernels (scr_lib.c), built by +/* SHA-1, SHA-256, SHA-384 and SHA-512 block kernels (scr_lib.c), built by * sha-kernels.test.ts. The unit is included directly so the test can run - * the SHA-NI blocks and the portable blocks over the same inputs: known - * vectors, every length from 0 to 1100 bytes, and 1 MiB, each fed whole - * and in 1-, 7-, 64- and 100-byte updates. On CPUs without the SHA - * extensions only the portable blocks run. */ + * each kernel the CPU supports and the portable blocks over the same + * inputs: known vectors, every length from 0 to 1100 bytes, and 1 MiB, + * each fed whole and in 1-, 7-, 64- and 100-byte updates. The SHA-1 and + * SHA-256 kernels use the SHA extensions; the SHA-512 kernels use AVX2 or + * AVX-512VL on x86-64 and the SHA-512 extension on arm64. On CPUs without + * them only the portable blocks run. */ #include "../src/scr_lib.c" #include @@ -45,6 +47,71 @@ static void sha1_of(const unsigned char *data, size_t len, size_t chunk, unsigne scr_sha1_final(&ctx, out); } +static void sha512_of(const unsigned char *data, size_t len, size_t chunk, bool sha384, + unsigned char out[64]) { + ScrSha512Ctx ctx; + scr_sha512_init(&ctx, sha384); + for (size_t at = 0; at < len;) { + size_t take = chunk == 0 || chunk > len - at ? len - at : chunk; + scr_sha512_update(&ctx, data + at, take); + at += take; + } + scr_sha512_final(&ctx, out, sha384); +} + +/* FIPS 180-4 example vectors for SHA-384 and SHA-512. */ +static void vectors512(const char *path) { + unsigned char d[64]; + char text[129]; + static const char two_block[] = + "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"; + sha512_of((const unsigned char *)"abc", 3, 0, false, d); + hex(d, 64, text); + check(strcmp(text, "ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f") == 0, path, 3, 0); + sha512_of((const unsigned char *)"", 0, 0, false, d); + hex(d, 64, text); + check(strcmp(text, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e") == 0, path, 0, 0); + sha512_of((const unsigned char *)two_block, sizeof two_block - 1, 0, false, d); + hex(d, 64, text); + check(strcmp(text, "8e959b75dae313da8cf4f72814fc143f8f7779c6eb9f7fa17299aeadb6889018501d289e4900f7e4331b99dec4b5433ac7d329eeb6dd26545e96e55b874be909") == 0, path, sizeof two_block - 1, 0); + sha512_of((const unsigned char *)"abc", 3, 0, true, d); + hex(d, 48, text); + check(strcmp(text, "cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7") == 0, path, 3, 0); + sha512_of((const unsigned char *)two_block, sizeof two_block - 1, 0, true, d); + hex(d, 48, text); + check(strcmp(text, "09330c33f71147e83d192fc782cd1b4753111b173b3b05d22fa08086e3b0f712fcc7c71a557e2db966c3e9fa91746039") == 0, path, sizeof two_block - 1, 0); + static unsigned char million[1000000]; + memset(million, 'a', sizeof million); + sha512_of(million, sizeof million, 0, false, d); + hex(d, 64, text); + check(strcmp(text, "e718483d0ce769644e2e42c7bc15b4638e1f98b13b2044285632a803afa973ebde0ff244877ea60a4cb0432ce577c31beb009c5c2c49aa2e4eadb217ad8cc09b") == 0, path, sizeof million, 0); + sha512_of(million, sizeof million, 0, true, d); + hex(d, 48, text); + check(strcmp(text, "9d0e1809716474cb086e834e310a4a1ced149e9c00f248527972cec5704c2a5b07b8b3dc38ecc4ebae97ddd87f3d8985") == 0, path, sizeof million, 0); +} + +/* Selects the SHA-512 kernel: 0 the portable blocks, otherwise the CPU's + * kernel. Returns false when the CPU has none. */ +static bool select512(int kernel) { +#ifdef SCR_SHA_NI + scr_sha512_x86_state = 0; + int best = scr_sha512_x86(); + if (kernel > best - 1) return false; + scr_sha512_x86_state = kernel == 0 ? 1 : kernel + 1; + return true; +#elif defined(SCR_SHA512_ARM) + scr_sha512_arm_state = 0; + bool available = scr_sha512_arm(); + if (kernel > (available ? 1 : 0)) return false; + scr_sha512_arm_state = kernel == 0 ? 2 : 1; + return true; +#else + return kernel == 0; +#endif +} + +static const char *const kernel512_names[] = {"portable", "avx2-or-arm", "avx512"}; + static void vectors(const char *path) { unsigned char d[32]; char text[65]; @@ -108,8 +175,31 @@ int main(void) { vectors("ni"); } #endif + /* SHA-512 and SHA-384: every kernel against the portable blocks, and + * the known vectors through each. */ + int kernels512 = 0; + for (int k = 0; k < 3 && select512(k); k++) { + kernels512 = k + 1; + vectors512(kernel512_names[k]); + } + for (int k = 1; k < kernels512; k++) { + for (size_t len = 0; len <= 1100 + 1; len++) { + size_t n = len == 1101 ? BIG : len; + for (size_t c = 0; c < sizeof chunks / sizeof chunks[0]; c++) { + for (int sha384 = 0; sha384 < 2; sha384++) { + unsigned char a[64], b[64]; + select512(k); + sha512_of(data, n, chunks[c], sha384, a); + select512(0); + sha512_of(data, n, chunks[c], sha384, b); + check(memcmp(a, b, sha384 ? 48 : 64) == 0, kernel512_names[k], n, chunks[c]); + } + } + } + } free(data); fprintf(stderr, "sha-ni %s\n", ni ? "available" : "absent"); + fprintf(stderr, "sha-512 kernels %d\n", kernels512); fprintf(stderr, "%ld/%ld cases passed\n", total - failed, total); return failed != 0; } diff --git a/packages/threads/package.json b/packages/threads/package.json index e3b5c91ff..358b06dc4 100644 --- a/packages/threads/package.json +++ b/packages/threads/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/threads", - "version": "0.3.6", + "version": "0.4.0", "description": "Zero-copy publication of immutable object graphs to worker threads (scriptc), with a deep-freezing Node.js implementation", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/packages/types/package.json b/packages/types/package.json index b694abb1a..74d19fc24 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@scriptc/types", - "version": "0.3.6", + "version": "0.4.0", "description": "Contract types for scriptc: type-only annotations (Int32, NoThrow, Immutable) that scriptc checks and optimizes, with no runtime semantics", "license": "Apache-2.0", "homepage": "https://scriptc.dev", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41d9e02ac..3974f346e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,6 +106,9 @@ importers: '@scriptc/runtime': specifier: workspace:* version: link:../runtime + tsc-ts: + specifier: 0.0.5 + version: 0.0.5 typescript: specifier: 7.0.2 version: 7.0.2 @@ -1354,6 +1357,10 @@ packages: cpu: [x64] os: [win32] + '@scriptc/threads@0.3.6': + resolution: {integrity: sha512-XZb4JhXM/ivhx4MMLxqLLF2mCI28nUK+VVKlCnELO83tacUuGlq7YCe/8LwvzO7fZwy/rCH4+genAYYEc6BF6Q==} + engines: {node: '>=24.0.0'} + '@sinclair/typebox@0.25.24': resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} @@ -1367,6 +1374,45 @@ packages: '@ts-morph/common@0.11.1': resolution: {integrity: sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==} + '@tsc-ts/darwin-arm64@0.0.5': + resolution: {integrity: sha512-yP64b2VpAKWG92KepBqxs1P89wpwiheYwNMk4EmmhSl6aw/ImIBrdHYNjvao3sg6FHZCXTc+PB6Vukh6hyQiJQ==} + cpu: [arm64] + os: [darwin] + + '@tsc-ts/darwin-x64@0.0.5': + resolution: {integrity: sha512-CEeHOFfpx5H9V3WSq+V7XGU8tfc06nRN818auiRVwuwPHvpti9gHq0HYkvf/43PH1pIGcgf68sQYdIxJhrQDKQ==} + cpu: [x64] + os: [darwin] + + '@tsc-ts/linux-arm64-gnu@0.0.5': + resolution: {integrity: sha512-qwybOqGDEa35eV9x/3WO81ExAUbUMY8288ATC83kUfYGmOAVStaO+JM2IdrsiI8hLWVC7/f0RFgZ+7dyWh9/nA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tsc-ts/linux-arm64-musl@0.0.5': + resolution: {integrity: sha512-6oSw3NbQIrnxxCRuEe190MbMFoOrM+P+u1usascQMgSjsMcVRnJ/ztvTnRrCDtfPEHJ5EC8drgWIpcdrhGsB0w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tsc-ts/linux-x64-gnu@0.0.5': + resolution: {integrity: sha512-KrlBLUcG7M6p7lN+ke+kHVLqy7IiY/H0FxFZzq5slXanhrps/BfjdkUahsyREg7KVprr08xFuXqJTBtLEI4eow==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tsc-ts/linux-x64-musl@0.0.5': + resolution: {integrity: sha512-fr8yGzkocCcTYoE+NwC+/WGPNteQ2GmOPJTy/z9NcsLchnpSIZ1fzFll15UYPKQlwQdNRJXxKK3npVBtLRdHTg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tsc-ts/win32-x64-msvc@0.0.5': + resolution: {integrity: sha512-SQwufJafzsiOBYncHOxYkyELWjLlBijcKB+xOs6hr6Lxxp5MMLoPtvDFNG88c7G5xv2TDb1XbvyxnjQltr6SHw==} + cpu: [x64] + os: [win32] + '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} @@ -2524,6 +2570,11 @@ packages: ts-toolbelt@6.15.5: resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} + tsc-ts@0.0.5: + resolution: {integrity: sha512-Eea+zNv7y696pqNZtXBd0AX7/6PXIhR7aaEb0EhgpHadbk7VEiURD94/NngeLpYWts2tPjetkpwFLJY0+6yOnQ==} + engines: {node: '>=20'} + hasBin: true + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -3381,6 +3432,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true + '@scriptc/threads@0.3.6': {} + '@sinclair/typebox@0.25.24': {} '@standard-schema/spec@1.1.0': {} @@ -3394,6 +3447,27 @@ snapshots: mkdirp: 1.0.4 path-browserify: 1.0.1 + '@tsc-ts/darwin-arm64@0.0.5': + optional: true + + '@tsc-ts/darwin-x64@0.0.5': + optional: true + + '@tsc-ts/linux-arm64-gnu@0.0.5': + optional: true + + '@tsc-ts/linux-arm64-musl@0.0.5': + optional: true + + '@tsc-ts/linux-x64-gnu@0.0.5': + optional: true + + '@tsc-ts/linux-x64-musl@0.0.5': + optional: true + + '@tsc-ts/win32-x64-msvc@0.0.5': + optional: true + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 @@ -4730,6 +4804,18 @@ snapshots: ts-toolbelt@6.15.5: {} + tsc-ts@0.0.5: + dependencies: + '@scriptc/threads': 0.3.6 + optionalDependencies: + '@tsc-ts/darwin-arm64': 0.0.5 + '@tsc-ts/darwin-x64': 0.0.5 + '@tsc-ts/linux-arm64-gnu': 0.0.5 + '@tsc-ts/linux-arm64-musl': 0.0.5 + '@tsc-ts/linux-x64-gnu': 0.0.5 + '@tsc-ts/linux-x64-musl': 0.0.5 + '@tsc-ts/win32-x64-msvc': 0.0.5 + tslib@2.8.1: optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8f10bfca8..a3e992b0a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,6 +7,14 @@ minimumReleaseAgeExclude: - "effect@4.0.0" - "@effect/platform-node@4.0.0" - "@effect/platform-node-shared@4.0.0" + - "@tsc-ts/darwin-arm64@0.0.5" + - "@tsc-ts/darwin-x64@0.0.5" + - "@tsc-ts/linux-arm64-gnu@0.0.5" + - "@tsc-ts/linux-arm64-musl@0.0.5" + - "@tsc-ts/linux-x64-gnu@0.0.5" + - "@tsc-ts/linux-x64-musl@0.0.5" + - "@tsc-ts/win32-x64-msvc@0.0.5" + - "tsc-ts@0.0.5" allowBuilds: esbuild: true overrides: diff --git a/scripts/build-native-cli.mts b/scripts/build-native-cli.mts index fa4cb74a7..c8a14adbd 100644 --- a/scripts/build-native-cli.mts +++ b/scripts/build-native-cli.mts @@ -5,7 +5,7 @@ import { basename, dirname, join, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import { compile } from "../packages/compiler/src/index.js"; import { NATIVE_TARGETS, nativeCodegenTarget } from "../packages/compiler/src/backend/targets.js"; -import { ts7Executable } from "../packages/compiler/src/frontend/ts7/rpc-api.js"; +import { tsgoExecutable } from "../packages/compiler/src/frontend/ts7/rpc-api.js"; import { stageNativeCliAssets } from "./native-cli-assets.mjs"; import type { NativeToolchainManifest } from "../packages/compiler/src/native/toolchain.js"; @@ -108,7 +108,7 @@ const assets = stageNativeCliAssets({ repository: root, output, target, - ts7: ts7Executable(), + ts7: tsgoExecutable(), compilerVersion, }); // A cross-libc seed may use Zig on the build host. The installed compiler diff --git a/scripts/checker-identity.mjs b/scripts/checker-identity.mjs new file mode 100644 index 000000000..a710d5946 --- /dev/null +++ b/scripts/checker-identity.mjs @@ -0,0 +1,156 @@ +#!/usr/bin/env node +// Compiles every corpus program to LLVM IR with each TypeScript 7 API server (tsc-ts, the default, and tsgo with +// SCRIPTC_CHECKER=tsgo) and fails unless both give the same IR and the same diagnostics. The text of a checker panic +// is the checker's own (Go's runtime error for tsgo, tsc-ts's for tsc-ts), so it is compared as `panic: …`. +// +// Usage: node scripts/checker-identity.mjs [--jobs ] [--optimization release|dev] [--filter ] +// +// It needs `pnpm build`. Each checker compiles in its own worker processes, which read SCRIPTC_CHECKER when they +// start their servers. +import { fork } from "node:child_process"; +import { globSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { availableParallelism, tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { parseArgs } from "node:util"; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const corpusDir = join(repoRoot, "tests/corpus"); +const checkers = ["tsgo", "tsc-ts"]; + +if (process.argv[2] === "--worker") await worker(process.argv.slice(3)); +else await main(); + +async function main() { + const { values } = parseArgs({ + options: { + jobs: { + type: "string", + default: String(Math.max(1, Math.floor(availableParallelism() / 2))), + }, + optimization: { type: "string", default: "release" }, + filter: { type: "string", default: "" }, + }, + }); + if (values.optimization !== "release" && values.optimization !== "dev") + throw new Error("--optimization must be release or dev"); + // The entries llvm-differential.test.ts compiles. + const entries = ["ts", "js", "mjs", "cjs"] + .flatMap((ext) => [ + ...globSync(join(corpusDir, `*.${ext}`)), + ...globSync(join(corpusDir, `*/main.${ext}`)), + ]) + .filter((file) => file.includes(values.filter)) + .sort(); + const jobs = Math.max(1, Number(values.jobs)); + const outRoot = mkdtempSync(join(tmpdir(), "scriptc-checker-identity-")); + try { + const results = new Map(checkers.map((checker) => [checker, new Map()])); + const runs = []; + for (const checker of checkers) + for (let index = 0; index < jobs; index++) + runs.push( + new Promise((done, fail) => { + const child = fork( + fileURLToPath(import.meta.url), + [ + "--worker", + join(outRoot, checker), + values.optimization, + ...entries.filter((_, i) => i % jobs === index), + ], + { env: { ...process.env, SCRIPTC_CHECKER: checker } }, + ); + child.on("message", (record) => results.get(checker).set(record.file, record)); + child.on("error", fail); + child.on("exit", (code) => + code === 0 ? done() : fail(new Error(`${checker} worker exited with ${code}`)), + ); + }), + ); + await Promise.all(runs); + const differences = []; + for (const file of entries) { + const [a, b] = checkers.map((checker) => results.get(checker).get(file)); + const name = file.slice(corpusDir.length + 1); + if (a === undefined || b === undefined) differences.push(`${name}: not compiled`); + else if (a.ok !== b.ok || withoutPanicText(a.diagnostics) !== withoutPanicText(b.diagnostics)) + differences.push( + `${name}: tsgo ${a.ok ? "compiles" : a.diagnostics}; tsc-ts ${b.ok ? "compiles" : b.diagnostics}`, + ); + else if (a.ok && irWithoutPanicText(a.llvm) !== irWithoutPanicText(b.llvm)) + differences.push(`${name}: LLVM IR differs`); + } + if (differences.length > 0) { + console.error( + `${differences.length} of ${entries.length} programs differ between tsgo and tsc-ts:`, + ); + for (const line of differences) console.error(` ${line}`); + process.exitCode = 1; + } else { + console.log( + `${entries.length} programs: tsgo and tsc-ts give the same LLVM IR and diagnostics (${values.optimization}).`, + ); + } + } finally { + rmSync(outRoot, { recursive: true, force: true }); + } +} + +/** A diagnostic list with each SC0004 checker panic's text replaced by `…`. */ +function withoutPanicText(diagnostics) { + return diagnostics?.replace(/(SC0004: [^;]*?: panic: )[^;]*/g, "$1…"); +} + +/** A module's LLVM IR with each SC0004 checker panic's text replaced by `…`. A fence statement keeps the + * diagnostic's message in a string constant named for a hash of its contents, and passes its length. */ +function irWithoutPanicText(llvm) { + let ir = readFileSync(llvm, "utf8"); + const constant = + /^(@sc_cs_\d+) = internal constant \[\d+ x i8\] c"(the TypeScript checker crashed answering a query here [^"]*?: panic: )[^"]*?( \[SC0004 at [^"]*)"$/gm; + const names = []; + ir = ir.replace(constant, (_, name, head, tail) => { + names.push(name); + return `@checker_panic_${names.length} = internal constant c"${head}…${tail}"`; + }); + names.forEach((name, index) => { + ir = ir.replace( + new RegExp(`${name}\\b(, i64 \\d+)?`, "g"), + (_, length) => `@checker_panic_${index + 1}${length === undefined ? "" : ", i64 …"}`, + ); + }); + return ir; +} + +async function worker([outDir, optimization, ...files]) { + const { compile } = await import( + pathToFileURL(join(repoRoot, "packages/compiler/dist/index.js")).href + ); + for (const file of files) { + const name = file.slice(corpusDir.length + 1); + const llvm = join(outDir, `${name}.ll`); + let record; + try { + const result = await compile(file, { + outputKind: "llvm", + outPath: llvm, + outDir: join(outDir, ".work", name), + optimization, + }); + record = result.ok + ? { file, ok: true, llvm } + : { + file, + ok: false, + diagnostics: result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("; "), + }; + } catch (error) { + record = { + file, + ok: false, + diagnostics: `threw ${error instanceof Error ? error.message : String(error)}`, + }; + } + process.send(record); + } +} diff --git a/scripts/ci-lanes.mjs b/scripts/ci-lanes.mjs index 9edc48a1c..b020f9de0 100644 --- a/scripts/ci-lanes.mjs +++ b/scripts/ci-lanes.mjs @@ -7,18 +7,20 @@ // own exemptions; a change to this script runs every lane until it is on the base branch. It uses only Node and git. // // Lanes (each an output, true or false): -// - linux: the Linux build and test shards, the TypeScript 7 parity sweep and the smaller Linux, Windows and mobile +// - linux: the Linux build and test shards, the TypeScript 7 parity sweep and the smaller Linux, Windows and Android // jobs. Off only when every change is documentation. -// - macos: the macOS native build, the darwin test shards and the macOS LLVM artifact jobs. On when the change touches -// a darwin package, a path naming darwin, macOS, iOS or Mach-O, the darwin test files of scripts/ci-test-plan.mjs, -// the LLVM helper (native/, packages/llvm-*) or the shared runtime pack code, or changed lines in packages/{runtime,compiler,cli} that mention darwin-specific APIs (__APPLE__, kqueue, Mach-O -// and similar). +// - macos: the macOS native build, the darwin test shards, the macOS LLVM artifact jobs and the iOS runtime packs. On +// when the change touches a darwin package, a path naming darwin, macOS, iOS, Mach-O or kqueue, the runtime's +// platform header, the darwin suite's test files (scripts/ci-test-plan.mjs), the LLVM helper (native/, +// packages/llvm-*) or the shared runtime pack code, or changed lines in packages/{runtime,compiler,cli} that mention +// darwin-specific APIs (__APPLE__, kqueue, Mach-O and similar). // - effects: the Effect suites. On when the change touches their harness or fixtures. // - bootstrap: the native bootstrap seed and self-rebuild contracts. On when the change touches the bootstrap scripts, // the native CLI build, the cli-* packages or the self-hosting tests. // Every lane is on when the change touches CI configuration, the workspace manifests or lockfile, vitest -// configuration, the shared test harness modules or this script, or when the diff cannot be computed. Pushes to main -// and the nightly sweep do not use this script: they run every lane. +// configuration, the shared test harness modules or this script, or when the diff cannot be computed. Pushes to main, +// the nightly sweep and manual runs do not use this script: pushes to main run every lane except macos, and the others +// run every lane. import { execFileSync } from "node:child_process"; import { appendFileSync } from "node:fs"; @@ -38,9 +40,11 @@ const everything = [ /^packages\/[^/]+\/package\.json$/, ]; const documentation = [/^docs\//, /\.mdx?$/, /^LICENSE$/, /^CHANGELOG\.md$/]; -const darwinPath = /darwin|macos|ios|apple|mach-?o/i; +const darwinPath = /darwin|macos|ios|apple|mach-?o|kqueue/i; // Native code generation and runtime packs every platform shares. const sharedNative = /^native\/|^packages\/(llvm-[^/]+|runtime-pack-common)\//; +// The darwin suite's files (darwinCorpusFiles and darwinFiles in scripts/ci-test-plan.mjs), the LLVM differential +// the macOS artifact jobs run, and the runtime's platform header. const darwinFiles = [ "packages/cli/test/bootstrap.test.ts", "packages/cli/test/native-link-info.test.ts", @@ -49,7 +53,10 @@ const darwinFiles = [ "packages/compiler/test/debug-variables.test.ts", "packages/compiler/test/native-codegen-integration.test.ts", "packages/compiler/test/source-debugging.test.ts", + "packages/runtime-darwin-arm64/test/archive.test.ts", + "packages/runtime/src/scr_platform.h", "packages/runtime/test/string.test.ts", + "tests/harness/differential.test.ts", "tests/harness/ffi-callback-pool.test.ts", "tests/harness/ffi-catalog.test.ts", "tests/harness/ffi-frameworks.test.ts", diff --git a/scripts/ci-test-plan.mjs b/scripts/ci-test-plan.mjs index 063af2ab9..2b76b52d6 100644 --- a/scripts/ci-test-plan.mjs +++ b/scripts/ci-test-plan.mjs @@ -1,4 +1,5 @@ import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; // Case-sharded harnesses run in every job without Vitest file sharding. export const effectFiles = ["tests/harness/effect.test.ts", "tests/harness/effect4.test.ts"]; @@ -68,13 +69,14 @@ export function sideOwners(flavor, count) { return owners; } -// Pull requests and pushes to main run the full suite on Linux. Their macOS -// lane runs the darwin suite, which keeps the darwin clang/libc/kqueue -// oracles: the Node differential corpus, plus every file whose assertions run -// only on darwin or take darwin-specific branches (including the Mach-O -// library localization arm and the arm64 runtime string oracle). -// The nightly schedule and manual runs with macos_full run the full suite on -// macOS. +// Pull requests and pushes to main run the full suite on Linux. Pull requests +// that change darwin code (scripts/ci-lanes.mjs) also run the darwin suite on +// macOS, which keeps the darwin clang/libc/kqueue oracles: the Node +// differential corpus, plus every file whose assertions run only on darwin or +// take darwin-specific branches (including the Mach-O library localization arm +// and the arm64 runtime string oracle). Pushes to main run no macOS jobs. The +// nightly schedule and manual runs run the full suite on macOS, or the darwin +// suite with macos_suite set to darwin. export const darwinCorpusFiles = ["tests/harness/differential.test.ts"]; export const darwinFiles = [ "packages/cli/test/bootstrap.test.ts", @@ -97,7 +99,28 @@ export const darwinFiles = [ "tests/harness/terminal-geometry.test.ts", ]; -export function ciTestPlan({ shard, flavor, workers = 4, suite = "full" }) { +// LeakSanitizer over the sanitized corpus on Linux (macOS ASan has none, and +// the workflow keeps it off everywhere else): a leak fails its program. The +// suppressions file lists intentional leaks, with the reason for each. +export const leakSuppressions = fileURLToPath( + new URL("../tests/harness/lsan.supp", import.meta.url), +); + +function corpusLeakEnv(flavor, platform) { + if (flavor !== "san" || platform !== "linux") return {}; + return { + ASAN_OPTIONS: "detect_leaks=1", + LSAN_OPTIONS: `suppressions=${leakSuppressions}:print_suppressions=0`, + }; +} + +export function ciTestPlan({ + shard, + flavor, + workers = 4, + suite = "full", + platform = process.platform, +}) { const match = /^([1-9]\d*)\/([1-9]\d*)$/.exec(shard); if ( !match || @@ -194,6 +217,7 @@ export function ciTestPlan({ shard, flavor, workers = 4, suite = "full" }) { args: ["test", ...corpusFiles], env: { ...env, + ...corpusLeakEnv(flavor, platform), SCRIPTC_TEST_WORKERS: String(workers - sideWorkers), SCRIPTC_LLVM_TEST_MODE: "dev", }, diff --git a/scripts/generate-ts7-ast-schema.mjs b/scripts/generate-ts7-ast-schema.mjs index 45a01c213..30c506869 100644 --- a/scripts/generate-ts7-ast-schema.mjs +++ b/scripts/generate-ts7-ast-schema.mjs @@ -268,9 +268,10 @@ if (process.argv.includes("--check")) { // declarations, and require every wire property to have a declared getter. const declarations = readFileSync(join(packageRoot, "dist/api/node/node.generated.d.ts"), "utf8"); const names = [...new Set(Object.values(protocol.childProperties).flat())].sort(); -// These views adapt TS7's shared postfix slot and reparsed JSDoc bodies. -// Their implementations live outside the generated region in AstNode. -const adaptedGetters = new Set(["body", "questionToken"]); +// These views adapt TS7's shared postfix slot and reparsed JSDoc bodies, and +// return modifiers as a modifier list. Their implementations live outside the +// generated region in AstNode. +const adaptedGetters = new Set(["body", "modifiers", "questionToken"]); const getters = names .filter((name) => !adaptedGetters.has(name)) .map((name) => { diff --git a/scripts/generate-ts7-ast-views.mjs b/scripts/generate-ts7-ast-views.mjs index 1525f6246..91e7ac2f7 100644 --- a/scripts/generate-ts7-ast-views.mjs +++ b/scripts/generate-ts7-ast-views.mjs @@ -65,6 +65,8 @@ export function generateAstViews(root, packageRoot, version, check) { if (key === "jsdocPropertyTags") type = "JSDocPropertyTag"; // A list's children are a node list, as every other list field. if (name === "SyntaxList" && key === "children") type = "NodeArray"; + // A declaration's modifiers are a modifier list, with their flags. + if (key === "modifiers") type = type.replace("NodeArray<", "ModifierArray<"); if (member.questionToken) type += " | undefined"; fields.set(key, ` readonly ${key}: ${type};`); } @@ -74,7 +76,7 @@ export function generateAstViews(root, packageRoot, version, check) { ...header, "", 'import type { AstNode } from "./ast-node.js";', - 'import type { JsxTagNamePropertyAccess, NodeArray } from "./ast-types.js";', + 'import type { JsxTagNamePropertyAccess, ModifierArray, NodeArray } from "./ast-types.js";', 'import { SyntaxKind, NodeFlags, ModifierFlags, TokenFlags } from "./enums.js";', "type Node = AstNode;", ]; @@ -99,6 +101,9 @@ export function generateAstViews(root, packageRoot, version, check) { ? `<${node.typeParameters.map((param) => param.getText(generated)).join(", ")}>` : ""; const fields = members(node.name.text); + // A default clause has no expression; the declaration shares the case clause's. + if (node.name.text === "DefaultClause" && fields.has("expression")) + fields.set("expression", " readonly expression: undefined;"); if (!fields.has("kind")) { const kinds = new Set(); for (const [name, child] of interfaces) { @@ -131,6 +136,7 @@ export function generateAstViews(root, packageRoot, version, check) { ...header, "", 'import { SyntaxKind, NodeFlags, ScriptKind, OuterExpressionKinds } from "./enums.js";', + 'import { getSourceFileOfNode } from "./node-functions.js";', ]; for (const stem of ["is.generated", "is"]) { const declarations = parse(`${stem}.d.ts`); @@ -173,10 +179,9 @@ export function generateAstViews(root, packageRoot, version, check) { } let body = fn.body.getText(source); if (name === "isJSDocTypeAssertion") - body = body.replace( - "expression.kind !==", - "expression === undefined || expression.kind !==", - ); + body = body + .replace("expression.kind !==", "expression === undefined || expression.kind !==") + .replace("node.getSourceFile()", "getSourceFileOfNode(node)"); if (head.includes("node: Node") && name.startsWith("is")) { head = head.replace("node: Node", "node: Node | undefined"); body = body.replace("{", "{\n if (node === undefined) return false;"); diff --git a/scripts/native-cli-assets.mts b/scripts/native-cli-assets.mts index 614fe52d5..b3d1436bd 100644 --- a/scripts/native-cli-assets.mts +++ b/scripts/native-cli-assets.mts @@ -2,6 +2,10 @@ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync import { basename, dirname, join, relative } from "node:path"; import type { NativeTargetSpec } from "../packages/compiler/src/backend/targets.js"; import type { NativeToolchainManifest } from "../packages/compiler/src/native/toolchain.js"; +import { + tscTsExecutable, + tscTsPlatformPackage, +} from "../packages/compiler/src/frontend/ts7/checker-backend.js"; /** Copy release payloads explicitly; development caches and build trees never ship. */ function copyPackage(source: string, destination: string, members: readonly string[]): void { @@ -28,6 +32,7 @@ export function stageNativeCliAssets(options: { }): Pick< NativeToolchainManifest, | "ts7" + | "tsc_ts" | "llvm_package" | "runtime_pack" | "runtime_packs" @@ -54,6 +59,14 @@ export function stageNativeCliAssets(options: { ); const typescript = join(lib, "typescript"); copyPackage(dirname(dirname(options.ts7)), typescript, ["lib"]); + // tsc-ts, the default checker, from the tsc-ts package the compiler depends on. + const tscTsSource = tscTsExecutable( + [join(repository, "packages/compiler")], + tscTsPlatformPackage(target.platform, target.architecture, target.name.endsWith("-musl")), + target.platform, + ); + const tscTs = join(lib, "tsc-ts"); + copyPackage(dirname(dirname(tscTsSource)), tscTs, ["lib"]); const sources = join(lib, "runtime-sources"); copyPackage(join(repository, "packages/runtime"), sources, ["src", "vendor"]); const declarations = join(lib, "declarations"); @@ -74,11 +87,15 @@ export function stageNativeCliAssets(options: { const identity = JSON.parse(readFileSync(join(typescript, "package.json"), "utf8")) as { version: string; }; + const tscTsIdentity = JSON.parse(readFileSync(join(tscTs, "package.json"), "utf8")) as { + version: string; + }; writeFileSync( join(output, "THIRD_PARTY_NOTICES"), [ - `scriptc ${options.compilerVersion} includes the existing TypeScript ${identity.version}, LLVM, and C runtime dependencies.`, + `scriptc ${options.compilerVersion} includes the existing TypeScript ${identity.version}, tsc-ts ${tscTsIdentity.version}, LLVM, and C runtime dependencies.`, "TypeScript licensing: lib/typescript/LICENSE and lib/typescript/NOTICE.txt.", + "tsc-ts licensing and notices: lib/tsc-ts/LICENSE and lib/tsc-ts/THIRD_PARTY_NOTICES.md.", "LLVM licensing and notices: lib/llvm/LICENSE and lib/llvm/THIRD_PARTY_NOTICES.", "Runtime dependency licenses: lib/runtime-sources/vendor/*/LICENSE*.", "", @@ -86,6 +103,7 @@ export function stageNativeCliAssets(options: { ); return { ts7: relative(bin, join(typescript, "lib", basename(options.ts7))), + tsc_ts: relative(bin, join(tscTs, "lib", basename(tscTsSource))), llvm_package: relative(bin, helper), runtime_pack: relative(bin, runtime), runtime_packs: [{ target: target.name, path: relative(bin, runtime) }], diff --git a/scripts/package-native-cli.mjs b/scripts/package-native-cli.mjs index dd42b8eaa..6d1c05991 100644 --- a/scripts/package-native-cli.mjs +++ b/scripts/package-native-cli.mjs @@ -44,6 +44,7 @@ for (const name of readdirSync(packages) for (const path of [ binary, resolve(bin, hostPath(manifest.ts7)), + resolve(bin, hostPath(manifest.tsc_ts)), resolve(bin, hostPath(manifest.comptime)), join( resolve(bin, hostPath(manifest.llvm_package)), diff --git a/scripts/verify-native-cli.mjs b/scripts/verify-native-cli.mjs index b1e061664..115573b5c 100644 --- a/scripts/verify-native-cli.mjs +++ b/scripts/verify-native-cli.mjs @@ -28,6 +28,7 @@ const within = (path) => { }; for (const name of [ "ts7", + "tsc_ts", "llvm_package", "runtime_pack", "runtime_sources", diff --git a/scripts/zero-cost-compare.mjs b/scripts/zero-cost-compare.mjs index a89fe4650..426be3143 100644 --- a/scripts/zero-cost-compare.mjs +++ b/scripts/zero-cost-compare.mjs @@ -388,6 +388,39 @@ export function dynamicTierSymbols(names) { .sort(); } +/* Bench workloads whose release binaries link dynamic-tier symbols on + * b14377864: 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. bench/thread-slots and + * bench/channel-messages, added later, link them through worker messages: + * node:worker_threads values are dyn objects. Every other bench workload is fully typed and must link none. The gate and + * tests/harness/bench-dynamic-tier.test.ts enforce that, and the test also + * fails when a listed workload stops linking them, so remove a workload here + * when a leak fix makes its binary clean. */ +export 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/thread-slots", + "bench/channel-messages", + "bench/typed-array-scan-workers", + "bench/log-summary", + "bench/inventory-report", +]); + +/** Names of the functions (text and weak symbols) in `nm` output. Mach-O + * prefixes C names with an underscore; on Darwin it is dropped so the names + * match DYNAMIC_TIER_PREFIXES as they do on ELF. */ +export function definedFunctionNames(text, platform = process.platform) { + return parseNm(text) + .filter((s) => /^[tTwW]$/.test(s.type)) + .map((s) => (platform === "darwin" && s.name.startsWith("_") ? s.name.slice(1) : s.name)); +} + /* ── cachegrind ────────────────────────────────────────────────────────── */ /** Background threads whose instruction counts depend on scheduling: the diff --git a/scripts/zero-cost-gate.mjs b/scripts/zero-cost-gate.mjs index 297e741e5..de3c0d0fc 100644 --- a/scripts/zero-cost-gate.mjs +++ b/scripts/zero-cost-gate.mjs @@ -32,10 +32,11 @@ import { compareArtifact, compareCounts, compareExecutables, + DYNAMIC_WORKLOADS, + definedFunctionNames, dynamicTierSymbols, median, nmOutput, - parseNm, programVerdict, timeRatio, } from "./zero-cost-compare.mjs"; @@ -96,7 +97,7 @@ that straddles it is reported as inconclusive. 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. +DYNAMIC_WORKLOADS in scripts/zero-cost-compare.mjs) may define none. --cachegrind (Linux, valgrind on PATH) runs differing executables under cachegrind (bench workloads with their arguments, corpus programs without) @@ -307,32 +308,7 @@ function nm(binary) { } /** 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. bench/thread-slots, added later, links them through worker - * messages: node:worker_threads values are dyn objects. Every other bench - * workload is fully typed and must link none. - * Remove a workload here when a leak fix makes its binary clean. */ -const DYNAMIC_WORKLOADS = new Set([ - "bench/json-records", - "bench/cli-config", - "bench/http-api", - "bench/validate-errors", - "bench/regex-logs", - "bench/atomics-contended", - "bench/intern-table", - "bench/thread-slots", - "bench/typed-array-scan-workers", - "bench/log-summary", - "bench/inventory-report", -]); +const definedFunctions = (binary) => definedFunctionNames(nm(binary)); /** The I3 symbol check. Every executable may not gain dynamic-tier symbols * over the baseline (unless the program matches --allow); a fully typed diff --git a/tests/corpus/1052-comptime-records.ts b/tests/corpus/1052-comptime-records.ts index 47cccf7b3..658aaed32 100644 --- a/tests/corpus/1052-comptime-records.ts +++ b/tests/corpus/1052-comptime-records.ts @@ -43,3 +43,20 @@ const meta = comptime(() => { return { kind: "meta", tags, count: tags.length }; }); console.log(meta.kind, meta.tags.join("|"), meta.count); + +// A discriminated union whose arms both fit a "labeled" value without a +// label: the discriminant picks the arm. +type Item = { kind: "plain"; v: number } | { kind: "labeled"; v: number; label?: string }; +const items: Item[] = comptime( + () => + [ + { kind: "plain", v: 1 }, + { kind: "labeled", v: 2 }, + { kind: "labeled", v: 3, label: "x" }, + ] as Item[], +); +for (const item of items) { + if (item.kind === "labeled") console.log("labeled", item.v, item.label); + else console.log("plain", item.v); +} +console.log(JSON.stringify(items)); diff --git a/tests/corpus/5467-typed-array-loop-versions.ts b/tests/corpus/5467-typed-array-loop-versions.ts new file mode 100644 index 000000000..35a1da145 --- /dev/null +++ b/tests/corpus/5467-typed-array-loop-versions.ts @@ -0,0 +1,298 @@ +// Typed-array loops whose accesses are proved in bounds by one test before +// the loop run without per-element checks; every other entry runs the +// checked loop. Both must keep JavaScript's exact out-of-bounds behavior: +// reads of missing elements are undefined (NaN in arithmetic), writes are +// ignored, DataView accesses throw at the failing iteration, and +// fractional, negative and NaN indices never touch storage. + +const table: Int32Array = new Int32Array(256); +for (let n = 0; n < 256; n++) table[n] = (n * 2654435761) | 0; +const shortTable: Int32Array = new Int32Array(100); +for (let n = 0; n < 100; n++) shortTable[n] = n + 1; + +function masked(t: Int32Array, data: Uint8Array): number { + let h = 0; + for (let i = 0; i < data.length; i++) h = (h ^ t[(h ^ data[i]) & 0xff]) | 0; + return h; +} + +function maskedWrite(t: Int32Array, count: number): void { + for (let i = 0; i < count; i++) t[(i * 7) & 0xff] = i; +} + +const data = new Uint8Array(64); +for (let i = 0; i < data.length; i++) data[i] = (i * 37 + 11) & 0xff; +console.log("masked full", masked(table, data)); +console.log("masked short", masked(shortTable, data)); +const writable = new Int32Array(200); +maskedWrite(writable, 300); +console.log("masked write", writable[0], writable[7], writable[199], writable.length); + +// Counter plus an invariant base, in bounds and running off either end. +function shifted(src: Float64Array, out: Float64Array, base: number, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + out[base + i] = src[i] * 2; + sum += src[base + i - 1]; + } + return sum; +} + +const src = new Float64Array(8); +for (let i = 0; i < 8; i++) src[i] = i + 0.25; +for (const [base, n] of [ + [1, 4], + [0, 8], + [5, 8], + [-3, 6], + [2, 0], + [3, 2.5], +]) { + const out = new Float64Array(8); + const sum = shifted(src, out, base, n); + console.log("shifted", base, n, sum, Array.from(out).join(",")); +} + +// Descending loops, including ones that walk below zero. +function descending(a: Uint16Array, low: number): number { + let sum = 0; + for (let i = a.length - 1; i >= low; i--) { + a[i] = a[i] + 1000; + sum += a[i]; + } + return sum; +} +const u16 = new Uint16Array([65000, 1, 2, 3]); +console.log("descending", descending(u16, 0), Array.from(u16).join(",")); +console.log("descending below", descending(u16, -2), Array.from(u16).join(",")); + +// Fractional starts and offsets never name an element. +function fractional(a: Float32Array): string { + const seen: string[] = []; + for (let i = 0.5; i < 3; i++) seen.push(String(a[i])); + for (let i = 0; i < 3; i++) { + a[i + 0.5] = 99; + seen.push(String(a[i] + a[i - 0.5])); + } + return seen.join(" "); +} +console.log("fractional", fractional(new Float32Array([1.5, 2.5, 3.5]))); + +function nanIndex(a: Int8Array, offset: number): number { + let sum = 0; + for (let i = 0; i < a.length; i++) { + a[i + offset] = 120; + sum += a[i + offset] | 0; + } + return sum; +} +const i8 = new Int8Array([1, 2, 3]); +console.log("nan index", nanIndex(i8, NaN), nanIndex(i8, -0), Array.from(i8).join(",")); + +// Unproven limits keep the integer loop's guard: huge, infinite and NaN. +function limits(a: Uint8Array, limit: number): number { + let sum = 0; + for (let i = 0; i < limit; i++) { + if (i >= a.length + 2) break; + sum += a[i] | 0; + a[i] = i; + } + return sum; +} +const lim = new Uint8Array([5, 6, 7]); +console.log("limits", limits(lim, 2), limits(lim, 1e300), limits(lim, Infinity), limits(lim, NaN)); + +// A DataView access past the end throws after the earlier iterations ran. +const viewProgress: number[] = [0]; +function viewLoop(view: DataView, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + sum += view.getUint32(i * 4, true) + view.getUint8(i * 4 + 3); + viewProgress[0] = sum; + } + return sum; +} +function viewSum(view: DataView, n: number): number { + viewProgress[0] = -1; + try { + return viewLoop(view, n); + } catch (e) { + console.log("view throw", (e as Error).name, viewProgress[0]); + } + return viewProgress[0]; +} +const dvBytes = new Uint8Array(16); +for (let i = 0; i < 16; i++) dvBytes[i] = i * 3; +const view = new DataView(dvBytes.buffer); +console.log("view ok", viewSum(view, 4)); +console.log("view partial", viewSum(view, 6)); + +// Strided loops: the last counter value of a non-unit step is exact, so a +// record that ends at the last element still takes the unchecked loop and +// a partial trailing record does not. +function quads(words: Uint16Array): number { + let sum = 0; + for (let i = 0; i < words.length; i += 4) sum += words[i] + words[i + 3] * 2; + return sum; +} +console.log("quads", quads(new Uint16Array([1, 2, 3, 4, 5, 6, 7, 8]))); +console.log("quads partial", quads(new Uint16Array([1, 2, 3, 4, 5, 6]))); +function backTriples(words: Uint16Array): number { + let sum = 0; + for (let i = words.length - 1; i >= 0; i -= 3) sum = sum * 3 + words[i] + (words[i - 2] | 0); + return sum; +} +console.log("back triples", backTriples(new Uint16Array([1, 2, 3, 4, 5, 6]))); +console.log("back triples partial", backTriples(new Uint16Array([1, 2, 3, 4, 5, 6, 7]))); +function scaled(words: Uint16Array, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) sum += words[i * 2] - (words[(i + 1) * 2 - 1] | 0) + (words[9 - i] | 0); + return sum; +} +const scaledWords = new Uint16Array(10); +for (let i = 0; i < 10; i++) scaledWords[i] = i * i; +console.log("scaled", scaled(scaledWords, 5), scaled(scaledWords, 6), scaled(scaledWords, 11)); +// A negative zero index names element 0 and stays -0 as a number. +function negativeZero(words: Uint16Array, n: number): string { + const seen: string[] = []; + for (let i = 0; i < n; i++) { + const k = i * -1; + const m = (i - 1) * 2; + seen.push(`${words[k]}:${Object.is(k, -0)}:${words[m] | 0}:${Object.is(m, -0)}`); + } + return seen.join(" "); +} +console.log("negative zero", negativeZero(scaledWords, 3)); + +// Clamped and wrapping element types on the unchecked path. +function convert(c: Uint8ClampedArray, s: Int16Array): string { + for (let i = 0; i < 4; i++) { + c[i] = i * 100 - 50.5; + s[i] = i * 20000; + } + return Array.from(c).join(",") + " " + Array.from(s).join(","); +} +console.log("convert", convert(new Uint8ClampedArray(4), new Int16Array(4))); + +// Module receivers stay correct across calls in the loop body, including a +// call that writes the same arrays. +const left: Float64Array = new Float64Array(16); +const right: Float64Array = new Float64Array(16); +function poke(k: number): number { + right[k] = right[k] + 1; + return k; +} +for (let i = 0; i < 16; i++) left[i] = i; +let crossed = 0; +for (let i = 0; i < 16; i++) { + right[i] = left[i] * 3; + crossed += right[poke(i)]; +} +console.log("module receivers", crossed, right[0], right[15]); + +// A body that reassigns its receiver keeps reading the current binding. +function rebinding(first: Uint8Array, second: Uint8Array): number { + let current = first; + let sum = 0; + for (let i = 0; i < 4; i++) { + sum += current[i] | 0; + if (i === 1) current = second; + } + return sum; +} +console.log("rebinding", rebinding(new Uint8Array([1, 2, 3, 4]), new Uint8Array([10, 20]))); + +// A throwing body leaves the writes of earlier iterations in place. +function failing(a: Uint32Array, at: number): void { + for (let i = 0; i < a.length; i++) { + if (i === at) throw new Error("stop at " + i); + a[i] = 4294967295 - i; + } +} +const u32 = new Uint32Array(5); +try { + failing(u32, 3); +} catch (e) { + console.log((e as Error).message, Array.from(u32).join(",")); +} + +// An outer loop proved in bounds around an inner loop that is not. +function nested(grid: Int32Array, w: number, h: number): number { + let sum = 0; + for (let y = 0; y < h; y++) { + sum += grid[y & 7]; + for (let x = 0; x < w; x++) { + grid[y * w + x] = grid[y * w + x] + 1; + sum += grid[y * w + x] | 0; + } + } + return sum; +} +const grid = new Int32Array(16); +console.log("nested", nested(grid, 4, 4), nested(grid, 5, 4), nested(grid, 4, 5), grid.join(",")); + +// A matrix product over module-level constant arrays. +const N = 6; +const ma: Float64Array = new Float64Array(N * N); +const mb: Float64Array = new Float64Array(N * N); +const mc: Float64Array = new Float64Array(N * N); +for (let i = 0; i < N * N; i++) { + ma[i] = (i % 7) - 3; + mb[i] = (i % 5) * 0.5; +} +for (let i = 0; i < N; i++) { + for (let k = 0; k < N; k++) { + const aik = ma[i * N + k]; + const kOff = k * N; + const iOff = i * N; + for (let j = 0; j < N; j++) mc[iOff + j] += aik * mb[kOff + j]; + } +} +let trace = 0; +for (let i = 0; i < N; i++) trace += mc[i * N + i]; +console.log("matmul", trace, mc[1], mc[N * N - 1]); + +// Float16Array elements are two bytes; versioned loops bound them in +// elements, and stores round to nearest-even in the unchecked copy too. +function halves(src: Float16Array, out: Float16Array, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + out[i] = src[i + 1] * 1.001 + 0.1; + sum += src[i]; + } + return sum; +} +const halfSrc = new Float16Array(10); +for (let i = 0; i < halfSrc.length; i++) halfSrc[i] = i * 0.3 - 1; +const halfOut = new Float16Array(10); +console.log("f16 in bounds", halves(halfSrc, halfOut, 9), Array.from(halfOut).join(",")); +console.log("f16 past end", halves(halfSrc, halfOut, 12), Array.from(halfOut).join(",")); +const halfShort = new Float16Array(4); +console.log("f16 short out", halves(halfSrc, halfShort, 9), Array.from(halfShort).join(",")); +console.log("f16 rounding", halves(new Float16Array([65519, 2049, 1e-8, 0]), new Float16Array(3), 3)); + +function halfTable(t: Float16Array, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) sum += t[i & 7]; + return sum; +} +console.log("f16 masked", halfTable(new Float16Array([0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5]), 20)); +console.log("f16 masked short", halfTable(new Float16Array([0.5, 1.5, 2.5, 3.5, 4.5]), 20)); + +// DataView half-precision accesses are two bytes wide: the last odd offset +// throws. +function halfView(v: DataView, n: number): number { + let sum = 0; + for (let i = 0; i < n; i++) { + v.setFloat16(i * 2, i + 0.25, true); + sum += v.getFloat16(i * 2 + 1, false); + } + return sum; +} +const halfBytes = new DataView(new ArrayBuffer(11)); +console.log("f16 view", halfView(halfBytes, 4)); +try { + halfView(halfBytes, 6); +} catch (e) { + console.log((e as Error).message, new Uint8Array(halfBytes.buffer).join(",")); +} diff --git a/tests/corpus/5468-typed-array-loop-aliasing.ts b/tests/corpus/5468-typed-array-loop-aliasing.ts new file mode 100644 index 000000000..019913d1c --- /dev/null +++ b/tests/corpus/5468-typed-array-loop-aliasing.ts @@ -0,0 +1,107 @@ +// Element stores may alias any other view of the same buffer, whatever its +// element type, offset or length, but never a typed array's own length and +// data pointer. Loops that write through one view and read through another +// must observe every store in program order, also when the loop runs +// without per-element bounds checks. + +const buffer = new ArrayBuffer(64); +const f64 = new Float64Array(buffer); +const u8 = new Uint8Array(buffer); +const i32 = new Int32Array(buffer, 8, 8); + +// Writes through Float64Array observed through a Uint8Array in the same +// iteration. +let topBytes = 0; +for (let i = 0; i < f64.length; i++) { + f64[i] = -(i + 1) * 1.5; + topBytes += u8[i * 8 + 7]; +} +console.log("f64 then u8", topBytes, u8[7], u8[63]); + +// Byte stores observed through an overlapping Int32Array window. +let words = 0; +for (let i = 0; i < 32; i++) { + u8[8 + i] = i + 1; + words = (words + i32[i >> 2]) | 0; +} +console.log("u8 then i32", words, i32[0], i32[7]); + +// A shifted subarray reads what the previous iteration wrote: a prefix +// recurrence that any reordering or vector widening would break. +const chain = new Uint8Array(40); +chain[0] = 1; +const ahead = chain.subarray(1); +for (let i = 0; i < ahead.length; i++) ahead[i] = chain[i] + 2; +console.log("recurrence", chain[0], chain[1], chain[20], chain[39]); + +// Overlapping copies in both directions. +const values = new Int16Array(12); +for (let i = 0; i < values.length; i++) values[i] = i * 10; +const lower = values.subarray(0, 11); +const upper = values.subarray(1); +for (let i = 0; i < lower.length; i++) lower[i] = upper[i]; +console.log("copy down", Array.from(values).join(",")); +for (let i = 0; i < values.length; i++) values[i] = i * 10; +for (let i = 0; i < lower.length; i++) upper[i] = lower[i]; +console.log("copy up", Array.from(values).join(",")); + +// Same-array accumulation with a lagging read. +function lagged(a: Float64Array, lag: number): number { + for (let i = lag; i < a.length; i++) a[i] = a[i] + a[i - lag]; + return a[a.length - 1]; +} +const fib = new Float64Array(30); +fib[0] = 1; +fib[1] = 1; +console.log("lagged", lagged(fib, 1), lagged(fib, 2)); + +// Two parameters that are the same array, and a destination that aliases +// part of its own source. +function axpy(alpha: number, x: Float64Array, y: Float64Array, n: number): void { + for (let i = 0; i < n; i++) y[i] = y[i] + alpha * x[i]; +} +const same = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]); +axpy(2, same, same, same.length); +console.log("axpy same", Array.from(same).join(",")); +const whole = new Float64Array([1, 2, 3, 4, 5, 6, 7, 8]); +axpy(1, whole, whole.subarray(2), 6); +console.log("axpy shifted", Array.from(whole).join(",")); + +// Element stores never change a length or a module binding, even when a +// float store writes the bit pattern of a small length. +const meta: Float64Array = new Float64Array(4); +const lengths: number[] = []; +for (let i = 0; i < 4; i++) { + meta[i] = 5e-324 * (i + 1); + lengths.push(meta.length); +} +console.log("lengths", lengths.join(","), meta[3] > 0); + +// Global numbers next to typed-array stores keep their values. +let scale = 3; +const scaled: Float32Array = new Float32Array(6); +for (let i = 0; i < scaled.length; i++) { + scaled[i] = scale * i; + if (i === 2) scale = 0.5; +} +console.log("scaled", Array.from(scaled).join(",")); + +// Half-precision stores observed through integer views of the same buffer +// in the same iteration, and byte stores observed through Float16Array. +const halfBuffer = new ArrayBuffer(32); +const f16 = new Float16Array(halfBuffer); +const u16 = new Uint16Array(halfBuffer); +const halfBytes = new Uint8Array(halfBuffer); +let bits = 0; +for (let i = 0; i < f16.length; i++) { + f16[i] = (i - 8) * 0.75; + bits = (bits + u16[i]) | 0; +} +console.log("f16 then u16", bits, u16[0], u16[15]); +let halfSum = 0; +for (let i = 0; i < f16.length; i++) { + halfBytes[i * 2 + 1] = 0x3c + (i & 3); + halfBytes[i * 2] = i; + halfSum += f16[i]; +} +console.log("u8 then f16", halfSum, f16[0], f16[15]); diff --git a/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts b/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts new file mode 100644 index 000000000..f899b9d07 --- /dev/null +++ b/tests/corpus/5469-typed-array-loop-initializer-rebinding.ts @@ -0,0 +1,46 @@ +// A typed-array loop whose initializer rebinds the array it indexes: the +// bounds the loop relies on belong to the array the initializer installs, +// so reads past the shorter replacement are undefined (NaN in arithmetic) +// and writes past it are ignored, as in JavaScript. + +function sumAfterRebinding(data: Uint8Array, replacement: Uint8Array, n: number): number { + let total = 0; + for (let i = ((data = replacement), 0); i < n; i++) total += data[i]; + return total; +} + +function fillAfterRebinding(data: Int32Array, replacement: Int32Array, n: number): Int32Array { + for (let i = ((data = replacement), 0); i < n; i++) data[i] = i + 1; + return data; +} + +// The initializer assigns the receiver without declaring the counter. +function countAfterAssignment(data: Float64Array, replacement: Float64Array, n: number): number { + let i: number; + let total = 0; + for (data = replacement, i = 0; i < n; i++) total += data[i]; + return total; +} + +const long = new Uint8Array(64).fill(1); +const short = new Uint8Array(4).fill(2); +console.log("sum", sumAfterRebinding(long, short, 64)); +console.log("sum in bounds", sumAfterRebinding(long, short, 4)); + +const target = new Int32Array(64); +const small = new Int32Array(3); +const filled = fillAfterRebinding(target, small, 64); +console.log("fill", filled.length, Array.from(filled).join(","), target[0], target[63]); + +const wide = new Float64Array(32).fill(0.5); +const narrow = new Float64Array(2).fill(4); +console.log("count", countAfterAssignment(wide, narrow, 32)); +console.log("count in bounds", countAfterAssignment(wide, narrow, 2)); + +// A loop whose initializer leaves the receiver alone still runs normally. +function plainSum(data: Uint8Array, n: number): number { + let total = 0; + for (let i = 0; i < n; i++) total += data[i]; + return total; +} +console.log("plain", plainSum(long, 64), plainSum(short, 6)); diff --git a/tests/corpus/5607-string-char-compare.ts b/tests/corpus/5607-string-char-compare.ts new file mode 100644 index 000000000..f80581c10 --- /dev/null +++ b/tests/corpus/5607-string-char-compare.ts @@ -0,0 +1,37 @@ +// Comparing one character of a string with a one-unit literal: index and +// charAt forms, === and !==, the literal on either side, indexes out of +// range, negative, fractional and NaN, multi-byte and astral text, lone +// surrogates, and literals of other lengths. +function bits(s: string, i: number): string { + return [ + s[i] === "_", s.charAt(i) === "_", "_" === s.charAt(i), s.charAt(i) !== "_", s[i] !== "_", + s[i] === "a", s.charAt(i) === "a", "a" === s.charAt(i), s.charAt(i) !== "a", s[i] !== "a", + s[i] === "é", s.charAt(i) === "é", "é" === s.charAt(i), s.charAt(i) !== "é", s[i] !== "é", + s[i] === "\uD83D", s.charAt(i) === "\uD83D", "\uD83D" === s.charAt(i), s.charAt(i) !== "\uD83D", s[i] !== "\uD83D", + s[i] === "\uDE00", s.charAt(i) === "\uDE00", "\uDE00" === s.charAt(i), s.charAt(i) !== "\uDE00", s[i] !== "\uDE00", + s[i] === "\uD800", s.charAt(i) === "\uD800", "\uD800" === s.charAt(i), s.charAt(i) !== "\uD800", s[i] !== "\uD800", + s[i] === "語", s.charAt(i) === "語", "語" === s.charAt(i), s.charAt(i) !== "語", s[i] !== "語", + s[i] === "", s.charAt(i) === "", "" === s.charAt(i), s.charAt(i) !== "", s[i] !== "", + s[i] === "ab", s.charAt(i) === "ab", "ab" === s.charAt(i), s.charAt(i) !== "ab", s[i] !== "ab", + ] + .map((b) => (b ? "1" : "0")) + .join(""); +} +const samples: string[] = ["a_b", "", "é_ü", "x😀y", "\uD800z", "日本_語", "ab"]; +const indexes = [0, 1, 2, 3, 4, -1, 1.5, NaN, 100]; +for (const s of samples) console.log(JSON.stringify(s), indexes.map((i) => bits(s, i)).join(" ")); + +function countUnderscores(text: string): number { + let n = 0; + for (let i = 0; i < text.length; i++) if (text[i] === "_") n++; + return n; +} +function firstSpace(text: string): number { + for (let i = 0; i < text.length; i++) if (text.charAt(i) === " ") return i; + return -1; +} +console.log(countUnderscores("a_b_c__d"), countUnderscores(""), firstSpace("type Foo = Bar"), firstSpace("none")); +let k = 0; +const s = "ab"; +const at = (): number => k++; +console.log(s.charAt(at()) === "a", s.charAt(at()) === "a", k); diff --git a/tests/corpus/5608-string-char-switch.ts b/tests/corpus/5608-string-char-switch.ts new file mode 100644 index 000000000..8ea54f1f5 --- /dev/null +++ b/tests/corpus/5608-string-char-switch.ts @@ -0,0 +1,95 @@ +// switch on one character of a string with one-unit case literals: the +// index and charAt forms, indexes out of range, negative, fractional and +// NaN, astral text and lone surrogates, duplicate cases, fallthrough, a +// switch without default, few and many cases, and an index expression +// with a side effect. +function classify(ch: string): string { + return ch; +} +function kind(s: string, i: number): string { + switch (s[i]) { + case "(": + case ")": + return "paren"; + case "{": + return "brace"; + case "é": + return "e-acute"; + case "\uD83D": + return "high"; + case "\uDE00": + return "low"; + case "\uD800": + return "lone"; + case "(": + return "duplicate"; + case " ": + return "space"; + default: + return "other"; + } +} +function few(s: string, i: number): number { + let n = 0; + switch (s.charAt(i)) { + case "a": + n += 1; + case "b": + n += 2; + break; + case "c": + n += 4; + } + return n; +} +const samples = ["(x) {é}", "😀", "\uD800", "", "abc", " "]; +const indexes = [0, 1, 2, 3, -1, 0.5, NaN, 100]; +for (const s of samples) { + console.log( + JSON.stringify(s), + indexes.map((i) => kind(s, i)).join(","), + indexes.map((i) => few(s, i)).join(","), + ); +} +let calls = 0; +const text = "abc"; +const next = (): number => calls++; +for (let r = 0; r < 4; r++) { + switch (text.charAt(next())) { + case "a": + console.log("a", calls); + break; + case "c": + console.log("c", calls); + break; + default: + console.log("default", calls); + } +} +function tokens(text: string): string { + const out: string[] = []; + for (let i = 0; i < text.length; i++) { + switch (text[i]) { + case "(": + case ")": + out.push("p"); + break; + case "+": + case "-": + case "*": + case "/": + out.push("o"); + break; + case "\uD83D": + out.push("h"); + break; + case " ": + break; + default: + out.push(text[i]); + } + } + return out.join(""); +} +console.log(tokens("(a + b) * c / d - é"), tokens("x😀y"), tokens("")); +console.log(classify("ok")); diff --git a/tests/corpus/5609-string-switch-optional.ts b/tests/corpus/5609-string-switch-optional.ts new file mode 100644 index 000000000..20e36b739 --- /dev/null +++ b/tests/corpus/5609-string-switch-optional.ts @@ -0,0 +1,57 @@ +// switch on a string | undefined value with string literal cases: array +// reads, optional fields and Map lookups that may be undefined, matching +// and missing values, the empty string, non-ASCII and long literals, +// duplicate cases, fallthrough, default in the middle, and few cases. +interface Token { + text?: string; +} +function kind(t: string | undefined): string { + switch (t) { + case "function": + case "class": + return "decl"; + case "": + return "empty"; + case "naïve": + return "unicode"; + case "a-much-longer-keyword-than-eight-bytes": + return "long"; + case "function": + return "duplicate"; + default: + return t === undefined ? "missing" : "other"; + case "return": + return "flow"; + } +} +function fallthrough(t: string | undefined): number { + let n = 0; + switch (t) { + case "a": + n += 1; + case "b": + n += 2; + break; + default: + n += 8; + case "c": + n += 4; + } + return n; +} +function few(t: string | undefined): string { + switch (t) { + case "x": + return "x"; + } + return "none"; +} +const words = ["function", "class", "", "naïve", "a-much-longer-keyword-than-eight-bytes", "return", "other", "Function"]; +for (let i = 0; i <= words.length; i++) { + const w: string | undefined = words[i]; + console.log(i, kind(w), fallthrough(w), few(w)); +} +const tokens: Token[] = [{ text: "a" }, {}, { text: "c" }, { text: "x" }]; +console.log(tokens.map((t) => `${kind(t.text)}:${fallthrough(t.text)}:${few(t.text)}`).join(" ")); +const table = new Map([["k", "return"], ["e", ""]]); +console.log(["k", "e", "z"].map((key) => kind(table.get(key))).join(" ")); diff --git a/tests/corpus/5730-thread-slots-widened-values.ts b/tests/corpus/5730-thread-slots-widened-values.ts new file mode 100644 index 000000000..88a1803e7 --- /dev/null +++ b/tests/corpus/5730-thread-slots-widened-values.ts @@ -0,0 +1,45 @@ +import { ThreadSlots } from "@scriptc/experimental"; + +// A ThreadSlots const annotated with a structurally wider value type than +// its constructor's: the values are stored with the constructor's layout and +// read back through the annotation's. Node.js prints the same. + +class Key { + readonly id: number; + constructor(id: number) { + this.id = id; + } +} + +interface Counted { + count: number; + label: string; +} + +interface Labeled { + label: string; +} + +interface Tagged { + label: string; + tag?: string; +} + +const counted: ThreadSlots = new ThreadSlots("widened.counted"); +const tagged: ThreadSlots = new ThreadSlots("widened.tagged"); + +const first = new Key(1); +const second = new Key(2); +const value: Counted = { count: 7, label: "seven" }; +counted.set(first, value); +const read = counted.get(first)!; +console.log(read.label, counted.get(second), counted.has(first)); + +tagged.set(second, { count: 2, label: "two" } as Counted); +const t = tagged.get(second); +console.log(t?.label, t?.tag, tagged.get(first)); + +function labelOf(slots: Labeled | undefined): string { + return slots === undefined ? "none" : slots.label; +} +console.log(labelOf(counted.get(first)), labelOf(counted.get(second))); diff --git a/tests/corpus/5750-string-from-char-codes.ts b/tests/corpus/5750-string-from-char-codes.ts new file mode 100644 index 000000000..6229e6d81 --- /dev/null +++ b/tests/corpus/5750-string-from-char-codes.ts @@ -0,0 +1,27 @@ +// String.fromCharCode with several arguments: ASCII and non-ASCII units, +// surrogate pairs that combine, lone and reversed surrogates, ToUint16 of +// large, negative, fractional and NaN codes, zero to seventeen arguments, +// and the spread form. +const show = (s: string): string => + `${JSON.stringify(s)}:${s.length}:${Array.from(s, (c) => c.codePointAt(0)!.toString(16)).join(".")}`; +console.log(show(String.fromCharCode())); +console.log(show(String.fromCharCode(104, 105))); +console.log(show(String.fromCharCode(0xe9, 0x65e5, 0x41))); +console.log(show(String.fromCharCode(0xd83d, 0xde00))); +console.log(show(String.fromCharCode(0xde00, 0xd83d))); +console.log(show(String.fromCharCode(0xd800, 0x41, 0xdc00))); +console.log(show(String.fromCharCode(0x10041, -1, 65.9, NaN, 2 ** 32 + 66))); +console.log( + show(String.fromCharCode(97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112)), +); +console.log( + show(String.fromCharCode(97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113)), +); +const codes = [0x48, 0xd83d, 0xde00, 0x21]; +console.log(show(String.fromCharCode(...codes))); +let out = ""; +for (let i = 0; i < 26; i++) out += String.fromCharCode(97 + i, 65 + i); +console.log(out, out.length); +let calls = 0; +const next = (): number => 65 + calls++; +console.log(String.fromCharCode(next(), next(), next()), calls); diff --git a/tests/corpus/5751-string-length-cache.ts b/tests/corpus/5751-string-length-cache.ts new file mode 100644 index 000000000..e0e5f8c67 --- /dev/null +++ b/tests/corpus/5751-string-length-cache.ts @@ -0,0 +1,36 @@ +// .length of long non-ASCII strings: many texts read in turn, appends in +// place (including a lone high surrogate completed by a low one), strings +// freed and rebuilt at reused addresses, split pieces, and slices and +// searches on texts whose lengths were read before. +let unit = ""; +while (unit.length < 300) unit += "naïve 日本語 "; +const texts: string[] = []; +for (let i = 0; i < 40; i++) texts.push(unit.repeat(1 + (i % 7)) + "é".repeat(i)); +let sum = 0; +for (let r = 0; r < 3; r++) for (const t of texts) sum += t.length; +console.log(sum, texts[39].length, texts[0].length); + +let grow = unit; +const lengths: number[] = []; +for (let i = 0; i < 20; i++) { + grow += i % 2 ? "ü" : "x"; + lengths.push(grow.length); +} +console.log(lengths.join(",")); +let pair = unit + "\uD83D"; +const before = pair.length; +pair += "\uDE00"; +console.log(before, pair.length, pair.codePointAt(pair.length - 2)!.toString(16)); + +const seen: number[] = []; +for (let i = 0; i < 50; i++) { + const temp = unit + "ß".repeat(i % 5) + i; + seen.push(temp.length); +} +console.log(seen.slice(0, 10).join(","), seen.reduce((a, b) => a + b, 0)); + +const parts = (unit + "|").repeat(30).split("|"); +console.log(parts.length, parts[0].length, parts.map((p) => p.length).reduce((a, b) => a + b, 0)); + +const big = texts[39]; +console.log(big.slice(big.length - 5), big.substring(10, 20).length, big.indexOf("日本"), big.lastIndexOf("é"), big.length); diff --git a/tests/corpus/5775-md5-lengths.ts b/tests/corpus/5775-md5-lengths.ts new file mode 100644 index 000000000..957491568 --- /dev/null +++ b/tests/corpus/5775-md5-lengths.ts @@ -0,0 +1,34 @@ +// MD5 and md5-sha1 over every length from 0 to 520 bytes, fed whole and +// in 1-, 7-, 64- and 100-byte updates, then over 1 MiB, and HMAC-MD5 +// over a few lengths. Each length's digests are folded into one running +// SHA-512 so the output stays short. +import { createHash, createHmac } from "node:crypto"; + +const data = Buffer.alloc(1 << 20); +let x = 0x9e3779b9; +for (let i = 0; i < data.length; i++) { + x ^= x << 13; + x ^= x >>> 17; + x ^= x << 5; + data[i] = x & 0xff; +} + +function digest(algorithm: string, input: Buffer, chunk: number): Buffer { + const hash = createHash(algorithm); + if (chunk === 0) hash.update(input); + else for (let at = 0; at < input.length; at += chunk) hash.update(input.subarray(at, at + chunk)); + return hash.digest(); +} + +for (const algorithm of ["md5", "md5-sha1"]) { + for (const chunk of [0, 1, 7, 64, 100]) { + const fold = createHash("sha512"); + for (let length = 0; length <= 520; length++) fold.update(digest(algorithm, data.subarray(0, length), chunk)); + console.log(algorithm, "chunk", chunk, fold.digest("hex").slice(0, 32)); + } + console.log(algorithm, "1 MiB", digest(algorithm, data, 0).toString("hex").slice(0, 32), digest(algorithm, data, 4096).toString("hex").slice(0, 32)); +} +console.log("abc", createHash("md5").update("abc").digest("hex")); +for (const length of [0, 1, 55, 56, 63, 64, 65, 119, 120, 1000]) { + console.log("hmac", length, createHmac("md5", "key").update(data.subarray(0, length)).digest("hex").slice(0, 32)); +} diff --git a/tests/corpus/5779-sha512-lengths.ts b/tests/corpus/5779-sha512-lengths.ts new file mode 100644 index 000000000..277408409 --- /dev/null +++ b/tests/corpus/5779-sha512-lengths.ts @@ -0,0 +1,35 @@ +// SHA-384 and SHA-512 over every length from 0 to 520 bytes, fed whole and +// in 1-, 7-, 100- and 128-byte updates, then over 1 MiB, and HMAC-SHA512 +// over a few lengths. Each length's digests are folded into one running +// SHA-512 so the output stays short. The CPU's SHA-512 kernel (vector or +// SHA-512 instructions) and the portable blocks give the same digests. +import { createHash, createHmac } from "node:crypto"; + +const data = Buffer.alloc(1 << 20); +let x = 0x9e3779b9; +for (let i = 0; i < data.length; i++) { + x ^= x << 13; + x ^= x >>> 17; + x ^= x << 5; + data[i] = x & 0xff; +} + +function digest(algorithm: string, input: Buffer, chunk: number): Buffer { + const hash = createHash(algorithm); + if (chunk === 0) hash.update(input); + else for (let at = 0; at < input.length; at += chunk) hash.update(input.subarray(at, at + chunk)); + return hash.digest(); +} + +for (const algorithm of ["sha384", "sha512"]) { + for (const chunk of [0, 1, 7, 100, 128]) { + const fold = createHash("sha512"); + for (let length = 0; length <= 520; length++) fold.update(digest(algorithm, data.subarray(0, length), chunk)); + console.log(algorithm, "chunk", chunk, fold.digest("hex").slice(0, 32)); + } + console.log(algorithm, "1 MiB", digest(algorithm, data, 0).toString("hex").slice(0, 32), digest(algorithm, data, 4096).toString("hex").slice(0, 32)); +} +console.log("abc", createHash("sha512").update("abc").digest("hex")); +for (const length of [0, 1, 111, 112, 127, 128, 129, 255, 256, 1000]) { + console.log("hmac", length, createHmac("sha512", "key").update(data.subarray(0, length)).digest("hex").slice(0, 32)); +} diff --git a/tests/corpus/5816-fixed-into-unknown-rest.ts b/tests/corpus/5816-fixed-into-unknown-rest.ts new file mode 100644 index 000000000..f6d287021 --- /dev/null +++ b/tests/corpus/5816-fixed-into-unknown-rest.ts @@ -0,0 +1,39 @@ +// A function with only `unknown` parameters, or none, flows into a +// `(...args: unknown[]) => R` slot: each call passes the callers' +// arguments in order, undefined for a parameter the caller left out, and +// drops the extra ones, as JS calls do. Sync and async functions, stored +// in variables and arrays and passed as arguments. +type Handler = (...args: unknown[]) => void; +type AsyncHandler = (...args: unknown[]) => Promise; + +const none: Handler = () => console.log("none"); +const one: Handler = (a: unknown) => console.log("one", a); +const two: Handler = (a: unknown, b: unknown) => console.log("two", a, b); + +none(); +none(1, 2, 3); +one(); +one("x"); +one("x", "y"); +two(1); +two(1, [2], { three: 3 }); + +const handlers: Handler[] = [none, one, two]; +for (const h of handlers) h("all", true); + +function dispatch(h: Handler, ...args: unknown[]): void { + h(...args); +} +dispatch(two, "spread", null); +dispatch(one); + +const first: AsyncHandler = async (value: unknown) => value; +const pair: AsyncHandler = async (a: unknown, b: unknown) => [a, b]; + +async function main(): Promise { + console.log(await first(10, 20)); + console.log(await first()); + console.log(await pair("p")); + console.log(await pair("p", "q", "r")); +} +main().then(() => console.log("done")); diff --git a/tests/corpus/5817-constructor-function-instanceof.cjs b/tests/corpus/5817-constructor-function-instanceof.cjs new file mode 100644 index 000000000..355f53a64 --- /dev/null +++ b/tests/corpus/5817-constructor-function-instanceof.cjs @@ -0,0 +1,15 @@ +// `this instanceof F` in a constructor function, in a program that uses no +// EventEmitter or stream constructor value: the call-without-new idiom +// returns a new instance, and instanceof follows the function's prototype. +"use strict"; +function Glob(pattern) { + if (!(this instanceof Glob)) return new Glob(pattern); + this.pattern = pattern; +} +Glob.prototype.describe = function () { + return `glob ${this.pattern}`; +}; +const called = Glob("*.ts"); +const constructed = new Glob("*.js"); +console.log(called.describe(), constructed.describe()); +console.log(called instanceof Glob, {} instanceof Glob, Object.create(Glob.prototype) instanceof Glob); diff --git a/tests/corpus/5818-constructor-function-emitter-defaults.cjs b/tests/corpus/5818-constructor-function-emitter-defaults.cjs new file mode 100644 index 000000000..d89383b3a --- /dev/null +++ b/tests/corpus/5818-constructor-function-emitter-defaults.cjs @@ -0,0 +1,53 @@ +// EventEmitter.defaultMaxListeners read and written through the untyped +// EventEmitter constructor (Node's accessor and its validation), and a +// legacy pipe whose destination emits 'close' synchronously from end(). +"use strict"; +const util = require("util"); +const EventEmitter = require("events"); +const Stream = require("stream"); + +function Bus() { + EventEmitter.call(this); +} +util.inherits(Bus, EventEmitter); + +console.log(EventEmitter.defaultMaxListeners); +console.log(new Bus().getMaxListeners()); +EventEmitter.defaultMaxListeners = 3; +console.log(EventEmitter.defaultMaxListeners, new Bus().getMaxListeners()); +for (const value of [-1, "4", Number.NaN]) { + try { + EventEmitter.defaultMaxListeners = value; + } catch (error) { + console.log(error.name, error.code, error.message); + } +} +console.log(EventEmitter.defaultMaxListeners); +EventEmitter.defaultMaxListeners = 10; + +function Source() { + Stream.call(this); + this.readable = true; +} +util.inherits(Source, Stream); + +function Sink() { + Stream.call(this); + this.writable = true; +} +util.inherits(Sink, Stream); +Sink.prototype.write = function (chunk) { + console.log("write", chunk); + return true; +}; +Sink.prototype.end = function () { + console.log("sink end"); + this.emit("close"); +}; + +const source = new Source(); +const sink = new Sink(); +source.pipe(sink); +source.emit("data", "a"); +console.log("emit end", source.emit("end")); +console.log("listeners", source.listenerCount("end"), source.listenerCount("data"), sink.listenerCount("close")); diff --git a/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts b/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts new file mode 100644 index 000000000..a72328d3f --- /dev/null +++ b/tests/corpus/5819-node-stream-dyn-direct-pipe/main.ts @@ -0,0 +1,59 @@ +// A native Readable piped straight into a JavaScript sink, the only +// untyped stream flow in the program; unpipe of a NodeJS.WritableStream +// that holds a native Writable or a JavaScript sink; unpipe() of every +// JavaScript sink; and JavaScript destinations whose prependListener or +// writableNeedDrain getter throws. +import { Readable, Writable } from "node:stream"; +import { plainSink, throwingSink } from "./sources.js"; + +function detach(src: Readable, dst: NodeJS.WritableStream): void { + src.unpipe(dst); +} + +async function main(): Promise { + Readable.from(["a", "b"]).pipe(plainSink("direct")); + await new Promise((resolve) => setTimeout(resolve, 10)); + + const parts: string[] = []; + const native = new Writable({ + write(chunk: Buffer, _encoding, done) { + parts.push(chunk.toString()); + done(); + }, + }); + native.on("unpipe", () => console.log("native heard unpipe")); + const source = new Readable({ read() {} }); + source.pipe(native); + detach(source, native); + source.push("dropped"); + await new Promise((resolve) => setTimeout(resolve, 10)); + console.log("native got", JSON.stringify(parts.join("")), source.isPaused()); + + const sink = plainSink("detached"); + const second = new Readable({ read() {} }); + second.pipe(sink); + detach(second, sink); + detach(second, plainSink("never piped")); + second.push("late"); + await new Promise((resolve) => setTimeout(resolve, 10)); + console.log("second paused", second.isPaused()); + + const third = new Readable({ read() {} }); + third.pipe(plainSink("first of two")); + third.pipe(plainSink("second of two")); + third.unpipe(); + third.push("after unpipe()"); + await new Promise((resolve) => setTimeout(resolve, 10)); + console.log("third paused", third.isPaused(), third.listenerCount("data")); + + for (const member of ["prependListener", "writableNeedDrain"]) { + try { + new Readable({ read() {} }).pipe(throwingSink(member)); + console.log(member, "piped"); + } catch (error) { + console.log("pipe threw", (error as Error).message); + } + } +} + +void main(); diff --git a/tests/corpus/5819-node-stream-dyn-direct-pipe/sources.js b/tests/corpus/5819-node-stream-dyn-direct-pipe/sources.js new file mode 100644 index 000000000..a06d04899 --- /dev/null +++ b/tests/corpus/5819-node-stream-dyn-direct-pipe/sources.js @@ -0,0 +1,45 @@ +// A plain JavaScript sink, and sinks whose prependListener or +// writableNeedDrain getter throws. + +/** @returns {any} */ +export function plainSink(label) { + const parts = []; + const listeners = {}; + const sink = { + write(chunk) { + parts.push(String(chunk)); + return true; + }, + end() { + console.log(label, "sink end", parts.join("")); + }, + on(event, listener) { + (listeners[event] ??= []).push(listener); + return sink; + }, + once(event, listener) { + return sink.on(event, listener); + }, + removeListener(event, listener) { + listeners[event] = (listeners[event] ?? []).filter((each) => each !== listener); + return sink; + }, + emit(event, ...args) { + if (event === "unpipe") console.log(label, "heard unpipe"); + for (const listener of listeners[event] ?? []) listener(...args); + return true; + }, + }; + return sink; +} + +/** @returns {any} */ +export function throwingSink(member) { + const sink = plainSink(member); + Object.defineProperty(sink, member, { + get() { + throw new Error(`${member} getter failed`); + }, + }); + return sink; +} diff --git a/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts b/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts new file mode 100644 index 000000000..223e10ee8 --- /dev/null +++ b/tests/corpus/5820-node-stream-dyn-constructor-overrides/main.ts @@ -0,0 +1,35 @@ +// Constructor-function streams in NodeJS.ReadableStream / WritableStream +// slots: member calls run the stream's own pause, resume and on, and a pipe +// into one, and an unpipe of it, are the runtime's. +import { Readable } from "node:stream"; +import { collector, recordingSource } from "./sources.js"; + +function control(stream: NodeJS.ReadableStream): void { + stream.pause(); + stream.resume(); + stream.on("end", () => {}); +} + +function attach(src: Readable, dst: NodeJS.WritableStream): void { + src.pipe(dst); +} + +function detach(src: Readable, dst: NodeJS.WritableStream): void { + src.unpipe(dst); +} + +const recording = recordingSource(); +control(recording); +console.log("recorded", recording.calls.join(",")); + +const sink = collector(); +sink.on("pipe", () => console.log("pipe event")); +sink.on("unpipe", () => console.log("unpipe event")); +const source = new Readable({ read() {} }); +attach(source, sink); +source.push("a"); +setImmediate(() => { + detach(source, sink); + source.push("b"); + setImmediate(() => console.log("collected", sink.chunks.join(","), source.isPaused())); +}); diff --git a/tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js b/tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js new file mode 100644 index 000000000..462cd0538 --- /dev/null +++ b/tests/corpus/5820-node-stream-dyn-constructor-overrides/sources.js @@ -0,0 +1,39 @@ +// Constructor-function streams that override stream methods, and a plain +// JavaScript sink. +import { Readable, Writable } from "node:stream"; +import util from "node:util"; + +/** A Readable whose pause, resume and on record their calls before + * running Readable's own. */ +function Recording() { + Readable.call(this, { read() {} }); + this.calls = []; +} +util.inherits(Recording, Readable); +for (const name of ["pause", "resume", "on"]) { + Recording.prototype[name] = function (...args) { + this.calls.push(name); + return Readable.prototype[name].apply(this, args); + }; +} + +/** @returns {any} */ +export function recordingSource() { + return new Recording(); +} + +/** A Writable without overrides, piped into natively. */ +function Collector() { + Writable.call(this); + this.chunks = []; +} +util.inherits(Collector, Writable); +Collector.prototype._write = function (chunk, _encoding, done) { + this.chunks.push(String(chunk)); + done(); +}; + +/** @returns {any} */ +export function collector() { + return new Collector(); +} diff --git a/tests/corpus/5876-readline-dispose-instanceof.ts b/tests/corpus/5876-readline-dispose-instanceof.ts new file mode 100644 index 000000000..63f058e9b --- /dev/null +++ b/tests/corpus/5876-readline-dispose-instanceof.ts @@ -0,0 +1,28 @@ +// rl[Symbol.dispose]() closes the interface as close() does, and a second +// call does nothing. instanceof tells node:readline's Interface from +// node:readline/promises' one. +import * as readline from "node:readline"; +import * as readlinePromises from "node:readline/promises"; +import { PassThrough } from "node:stream"; + +const rl = readline.createInterface({ input: new PassThrough() }); +rl.on("close", () => console.log("close 1")); +rl[Symbol.dispose](); +console.log("disposed"); +rl[Symbol.dispose](); +rl.close(); +console.log("after second dispose"); + +const prl = readlinePromises.createInterface({ input: new PassThrough() }); +prl.on("close", () => console.log("close 2")); +console.log(rl instanceof readline.Interface, rl instanceof readlinePromises.Interface); +console.log(prl instanceof readline.Interface, prl instanceof readlinePromises.Interface); +prl[Symbol.dispose](); + +function make(): readline.Interface { + return readline.createInterface({ input: new PassThrough() }); +} +const made = make(); +made.on("close", () => console.log("close 3")); +made[Symbol.dispose](); +console.log("done"); diff --git a/tests/corpus/5877-readline-question-query.cjs b/tests/corpus/5877-readline-question-query.cjs new file mode 100644 index 000000000..734311b16 --- /dev/null +++ b/tests/corpus/5877-readline-question-query.cjs @@ -0,0 +1,51 @@ +// question() with a query that is not a string: a non-terminal interface +// throws (callback form) or rejects (readline/promises) with Node's +// ERR_INVALID_ARG_TYPE from the output write, and the question stays +// pending; a terminal interface writes the query as a string. +const readline = require("readline"); +const readlinePromises = require("readline/promises"); +const { PassThrough } = require("stream"); + +function capture(name) { + const output = new PassThrough(); + output.on("data", (d) => console.log(name, JSON.stringify(String(d)))); + return output; +} + +async function main() { + const input = new PassThrough(); + const rl = readline.createInterface({ input, output: capture("out") }); + rl.on("line", (line) => console.log("line", line)); + try { + rl.question(42, (answer) => console.log("answer", answer)); + } catch (e) { + console.log(e.name, e.code, e.message); + } + try { + rl.prompt(); + } catch (e) { + console.log("prompt", e.code, e.message); + } + input.write("first\nsecond\n"); + await new Promise((resolve) => setImmediate(resolve)); + console.log("prompt after", JSON.stringify(rl.getPrompt())); + rl.close(); + + for (const query of [undefined, null, { a: 1 }, [1, 2], true]) { + const prl = readlinePromises.createInterface({ input: new PassThrough() }); + try { + await prl.question(query); + } catch (e) { + console.log("rejected", e.code, e.message); + } + prl.close(); + } + + const tin = new PassThrough(); + const trl = readline.createInterface({ input: tin, output: capture("tty"), terminal: true }); + trl.question(7, (answer) => console.log("tty answer", answer)); + tin.write("x\r"); + await new Promise((resolve) => setImmediate(resolve)); + trl.close(); +} +main(); diff --git a/tests/corpus/5878-readline-dynamic-streams.mjs b/tests/corpus/5878-readline-dynamic-streams.mjs new file mode 100644 index 000000000..b1b9f4ee5 --- /dev/null +++ b/tests/corpus/5878-readline-dynamic-streams.mjs @@ -0,0 +1,41 @@ +// createInterface over an input or output that is a function's result, +// an object's member or an array element. +import * as readline from "node:readline"; +import * as readlinePromises from "node:readline/promises"; +import { PassThrough, Readable, Writable } from "node:stream"; + +function source(lines) { + return Readable.from([lines.join("\n") + "\n"]); +} +const streams = { input: source(["a", "b"]), output: new PassThrough() }; +streams.output.on("data", (d) => console.log("out", JSON.stringify(String(d)))); + +const rl1 = readline.createInterface({ input: source(["one", "two"]) }); +for await (const line of rl1) console.log("rl1", line); + +const rl2 = readline.createInterface({ input: streams.input, output: streams.output, prompt: "? " }); +rl2.prompt(); +for await (const line of rl2) console.log("rl2", line); + +const inputs = [source(["x"]), source(["y"])]; +for (const [i, input] of inputs.entries()) { + const rl = readline.createInterface(input); + rl.on("line", (line) => console.log("rl3", i, line)); + await new Promise((resolve) => { + rl.on("close", () => resolve(undefined)); + }); +} + +const sink = new Writable({ + write(chunk, _encoding, callback) { + console.log("sink", JSON.stringify(String(chunk))); + callback(); + }, +}); +const make = () => ({ input: new PassThrough(), output: sink }); +const opts = make(); +const prl = readlinePromises.createInterface(opts.input, opts.output); +const answer = prl.question("name? "); +opts.input.write("ada\n"); +console.log("answer", await answer); +prl.close(); diff --git a/tests/corpus/5891-typed-array-field-borrows.ts b/tests/corpus/5891-typed-array-field-borrows.ts new file mode 100644 index 000000000..cdd315191 --- /dev/null +++ b/tests/corpus/5891-typed-array-field-borrows.ts @@ -0,0 +1,68 @@ +// Typed-array locals read from fields and parameters: reads while nothing +// replaces the field, the field replaced directly or by a callee while the +// local is in use, rebound parameters, views and detached-length reads. + +class Scanner { + chars: Uint16Array; + pos = 0; + constructor(text: string) { + this.chars = Scanner.units(text); + } + static units(text: string): Uint16Array { + const out = new Uint16Array(text.length + 2); + for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i); + return out; + } + sum(): number { + const chars = this.chars; + let total = 0; + for (let i = 0; i < chars.length; i++) total += chars[i]!; + return total; + } + replaceThenRead(): number { + const chars = this.chars; + this.chars = Scanner.units("zz"); + return chars[0]! * 1000 + this.chars[0]!; + } + reset(text: string): void { + this.chars = Scanner.units(text); + } + readAcrossReset(): string { + const chars = this.chars; + const before = chars[0]!; + this.reset("q"); + return before + ":" + chars[1] + ":" + this.chars[0] + ":" + chars.length; + } +} + +function firstOf(units: Uint16Array, fallback: Uint16Array): number { + if (units.length === 0) units = fallback; + let n = 0; + while (n < units.length && units[n] !== 0) n++; + return n; +} + +function walk(a: Uint8Array): number { + let total = 0; + while (a.length > 1) { + total += a[0]!; + a = a.subarray(1); + } + return total + a.length; +} + +const s = new Scanner("hello"); +console.log(s.sum(), s.replaceThenRead(), s.sum()); +const t = new Scanner("abc"); +console.log(t.readAcrossReset(), t.sum()); +console.log(firstOf(new Uint16Array(0), Scanner.units("xyz")), firstOf(Scanner.units("ab"), new Uint16Array(1))); +console.log(walk(new Uint8Array([1, 2, 3, 4, 5])), walk(new Uint8Array(0))); + +const holders: Scanner[] = []; +for (let i = 0; i < 50; i++) holders.push(new Scanner("item" + i)); +let acc = 0; +for (const h of holders) { + acc += h.sum(); + acc += h.replaceThenRead(); +} +console.log(acc); diff --git a/tests/corpus/5916-dyn-error-stack-property.ts b/tests/corpus/5916-dyn-error-stack-property.ts new file mode 100644 index 000000000..2e1b42ef3 --- /dev/null +++ b/tests/corpus/5916-dyn-error-stack-property.ts @@ -0,0 +1,147 @@ +// Errors held as `any` or `unknown` have an own `stack` like Node's: a +// non-enumerable accessor that every error shares. The first read formats +// "Name: message" from the error's name and message at that point, and +// later reads answer the same string. Assigning a value replaces it, +// `delete` removes it, Object.keys, spread, Object.assign and +// JSON.stringify leave it out, `in` and Object.hasOwn find it, objects +// that inherit from an error read the error's stack. Typed and dynamic +// reads answer the same string, and a stack read through `any` captures +// frames. Only the first line is printed: frame lines differ between +// runtimes. + +class ParseError extends Error { + constructor(message: string) { + super(message); + this.name = "ParseError"; + } +} + +class PlainSubclass extends Error {} + +function dyn(value: unknown): any { + return value; +} + +function firstLine(value: unknown): string { + return typeof value === "string" ? JSON.stringify(value.split("\n")[0]) : `(${typeof value} ${String(value)})`; +} + +function describe(label: string, error: any): void { + console.log( + label, + "|", + typeof error.stack, + firstLine(error.stack), + "|", + Object.keys(error).join(","), + JSON.stringify(error), + "|", + "stack" in error, + Object.hasOwn(error, "stack"), + error.propertyIsEnumerable("stack"), + ); +} + +try { + throw new RangeError("thrown"); +} catch (error) { + describe("caught", error); +} + +const unknownError: unknown = new TypeError("bad thing"); +describe("unknown", unknownError); +describe("plain", dyn(new Error("plain"))); +describe("no message", dyn(new SyntaxError())); +describe("subclass", dyn(new ParseError("line 3"))); +describe("plain subclass", dyn(new PlainSubclass("inherited name"))); + +const coded = dyn(new Error("with code")); +coded.code = "E_CODE"; +describe("own property", coded); + +const descriptor = Object.getOwnPropertyDescriptor(dyn(new Error("a")), "stack")!; +const other = Object.getOwnPropertyDescriptor(dyn(new TypeError("b")), "stack")!; +console.log("descriptor", Object.keys(descriptor).join(","), descriptor.enumerable, descriptor.configurable); +console.log("shared accessors", typeof descriptor.get, descriptor.get === other.get, descriptor.set === other.set); +console.log("foreign receiver", descriptor.get!.call({}), firstLine(descriptor.get!.call(dyn(new TypeError("b"))))); +const subclassDescriptor = Object.getOwnPropertyDescriptor(dyn(new PlainSubclass("s")), "stack")!; +console.log("subclass descriptor", Object.keys(subclassDescriptor).join(","), subclassDescriptor.get === descriptor.get); + +const renamed = dyn(new Error("before")); +renamed.message = "after"; +renamed.name = "Renamed"; +console.log("first read", firstLine(renamed.stack)); +renamed.message = "later"; +console.log("later read", firstLine(renamed.stack)); +const subclassRenamed = dyn(new PlainSubclass("before")); +subclassRenamed.message = "after"; +console.log("subclass first read", firstLine(subclassRenamed.stack)); + +const replaced = dyn(new Error("replaced")); +replaced.stack = "custom stack\n at nowhere"; +describe("assigned", replaced); +console.log("assigned descriptor", Object.keys(Object.getOwnPropertyDescriptor(replaced, "stack")!).join(",")); +const subclassReplaced = dyn(new ParseError("replaced")); +subclassReplaced.stack = "subclass stack"; +console.log("subclass assigned", subclassReplaced.stack); +const typedReplaced = new ParseError("typed write"); +typedReplaced.stack = "typed subclass stack"; +console.log("typed subclass assigned", typedReplaced.stack, dyn(typedReplaced).stack); +const subclassNumbered = dyn(new PlainSubclass("numbered")); +subclassNumbered.stack = 7; +console.log("subclass number", subclassNumbered.stack, typeof subclassNumbered.stack); +subclassNumbered.stack = "string again"; +console.log("subclass string again", subclassNumbered.stack); +const numbered = dyn(new Error("numbered")); +numbered.stack = 5; +console.log("number", numbered.stack, typeof numbered.stack); +const removed = dyn(new Error("removed")); +delete removed.stack; +console.log("deleted", removed.stack, "stack" in removed, Object.hasOwn(removed, "stack")); + +const spreadSource = dyn(new Error("spread")); +console.log("spread", Object.keys({ ...spreadSource }).length, Object.keys(Object.assign({}, spreadSource)).length); +console.log("inherited", firstLine(Object.create(dyn(new RangeError("prototype"))).stack)); + +const typed = new Error("typed"); +const view = dyn(typed); +console.log("typed and dynamic", view.stack === typed.stack, firstLine(typed.stack)); +const typedSubclass = new ParseError("typed subclass"); +console.log("typed subclass", firstLine(dyn(typedSubclass).stack), dyn(typedSubclass).stack === typedSubclass.stack); +const assignedThroughView = new Error("typed"); +dyn(assignedThroughView).stack = "through the view"; +console.log("assigned through view", assignedThroughView.stack); + +function inner(): any { + return dyn(new Error("deep")); +} +function outer(): any { + return inner(); +} +const deep: string = outer().stack; +console.log("frames", deep.split("\n").length > 1, deep.includes("at inner"), deep.includes("at outer")); +function makeSubclass(): any { + return dyn(new PlainSubclass("deep")); +} +console.log("subclass frames", (makeSubclass().stack as string).includes("at makeSubclass")); + +const formatted = dyn(new Error("formatted")); +console.log("formatted before clone", firstLine(formatted.stack)); +console.log("clone keeps a formatted stack", firstLine(dyn(structuredClone(formatted)).stack)); + +class GrumpyError extends Error { + override get name(): string { + throw new Error("no name today"); + } +} +try { + console.log("throwing name getter", firstLine(dyn(new GrumpyError("grumpy")).stack)); +} catch (error) { + console.log("throwing name getter threw", (error as Error).message); +} + +function readKey(target: any, key: string): unknown { + return target[key]; +} +const computed = readKey(inner(), ["sta", "ck"].join("")) as string; +console.log("computed key", firstLine(computed), computed.includes("at inner")); diff --git a/tests/corpus/5983-runtime-constructor.ts b/tests/corpus/5983-runtime-constructor.ts new file mode 100644 index 000000000..3fb9672d0 --- /dev/null +++ b/tests/corpus/5983-runtime-constructor.ts @@ -0,0 +1,48 @@ +// x.constructor on instances of EventEmitter and the stream classes: +// .name is the class Node reports (fs, zlib and program subclasses +// included), === compares with the instance's own class, and the value +// is the class object for EventEmitter and the stream classes. +import { EventEmitter } from "node:events"; +import { Duplex, PassThrough, Readable, Transform, Writable } from "node:stream"; +import * as fs from "node:fs"; +import * as zlib from "node:zlib"; + +class Bus extends EventEmitter {} + +const emitter = new EventEmitter(); +const bus: EventEmitter = new Bus(); +console.log("emitter", emitter.constructor === EventEmitter, emitter.constructor.name); +console.log("subclass", bus.constructor === EventEmitter, bus.constructor.name); + +const streams: Readable[] = [new Readable(), new Duplex(), new Transform(), new PassThrough()]; +for (const s of streams) { + console.log( + "stream", + s.constructor.name, + s.constructor === Readable, + s.constructor === Duplex, + s.constructor === Transform, + s.constructor === PassThrough, + s.constructor !== Readable, + ); +} + +const from = Readable.from(["a"]); +console.log("from", from.constructor.name, from.constructor === Readable); + +const readStream: Readable = fs.createReadStream(import.meta.filename); +const writeStream: Writable = fs.createWriteStream("/dev/null"); +console.log("fs", readStream.constructor.name, readStream.constructor === Readable); +console.log("fs", writeStream.constructor.name, writeStream.constructor === Writable); +readStream.destroy(); +writeStream.destroy(); + +const gzip: Transform = zlib.createGzip(); +const inflate: Transform = zlib.createInflateRaw(); +console.log("zlib", gzip.constructor.name, inflate.constructor.name, gzip.constructor === Transform); + +const kinds: EventEmitter[] = [new EventEmitter(), new Writable(), new PassThrough()]; +for (const k of kinds) { + const Kind = k.constructor as typeof EventEmitter; + console.log("value", Kind.name, Kind === EventEmitter, k instanceof Kind, new Kind() instanceof EventEmitter); +} diff --git a/tests/corpus/6004-class-instance-integrity.ts b/tests/corpus/6004-class-instance-integrity.ts new file mode 100644 index 000000000..7df516802 --- /dev/null +++ b/tests/corpus/6004-class-instance-integrity.ts @@ -0,0 +1,169 @@ +export {}; +import { inspect } from "node:util"; + +const message = (run: () => void): string => { + try { + run(); + return "no error"; + } catch (e) { + return `${e instanceof TypeError ? "TypeError" : "Error"}: ${(e as Error).message}`; + } +}; + +class Point { + x = 1; + y = 2; + label = "p"; + move(dx: number): void { + this.x += dx; + } +} + +const p = new Point(); +console.log(Object.isFrozen(p), Object.isSealed(p), Object.isExtensible(p)); +const same = Object.freeze(p); +console.log(same === p, Object.isFrozen(p), Object.isSealed(p), Object.isExtensible(p)); +console.log(message(() => { + p.x = 5; +})); +console.log(message(() => p.move(1))); +console.log(p.x, Object.keys(p)); +const q = new Point(); +q.x = 9; +console.log(q.x, Object.isFrozen(q)); + +class Point3 extends Point { + z = 3; +} +const p3 = new Point3(); +Object.freeze(p3); +console.log(message(() => { + p3.z = 1; +})); +const asBase: Point = new Point3(); +Object.freeze(asBase); +console.log(message(() => { + asBase.y = 0; +})); +console.log(asBase.y); + +class Counter { + n = 0; +} +const c = Object.seal(new Counter()); +c.n = 5; +console.log(c.n, Object.isSealed(c), Object.isFrozen(c), Object.isExtensible(c)); +const d = new Counter(); +Object.preventExtensions(d); +d.n = 2; +console.log(d.n, Object.isExtensible(d), Object.isSealed(d), Object.isFrozen(d)); +const loose: any = d; +console.log(message(() => { + loose.extra = 1; +})); + +class Config { + mode = "dev"; + level = 1; +} +const cfg = new Config(); +Object.defineProperty(cfg, "mode", { value: "prod", writable: false }); +console.log(cfg.mode, Object.keys(cfg), Object.values(cfg)); +console.log(message(() => { + cfg.mode = "x"; +})); +Object.defineProperty(cfg, "level", { enumerable: false }); +console.log(Object.keys(cfg), Object.entries(cfg), cfg.level); +Object.defineProperty(cfg, "level", { configurable: false }); +console.log(message(() => Object.defineProperty(cfg, "level", { enumerable: true }))); +console.log(JSON.stringify(Object.getOwnPropertyDescriptor(cfg, "mode"))); +console.log(JSON.stringify(Object.getOwnPropertyDescriptor(cfg, "level"))); +cfg.level = 7; +console.log(cfg.level); + +const fz = Object.freeze(new Config()); +console.log(message(() => Object.defineProperty(fz, "level", { value: 2 }))); +console.log(message(() => Object.defineProperty(fz, "level", { value: 1 }))); +console.log(fz.level, JSON.stringify(Object.getOwnPropertyDescriptor(fz, "level"))); + +class Frozen { + a = 1; + constructor() { + Object.freeze(this); + } +} +const frozenInCtor = new Frozen(); +console.log(frozenInCtor.a, Object.isFrozen(frozenInCtor)); +console.log(message(() => { + frozenInCtor.a = 2; +})); + +class Extended extends Frozen { + b = 2; +} +console.log(message(() => new Extended())); + +class Empty {} +const empty = new Empty(); +console.log(Object.isExtensible(empty), Object.isSealed(empty), Object.isFrozen(empty)); +Object.preventExtensions(empty); +console.log(Object.isExtensible(empty), Object.isSealed(empty), Object.isFrozen(empty)); + +class Totals { + count = 1; + items: string[] = []; +} +const t = new Totals(); +Object.freeze(t); +console.log(message(() => { + t.count += 1; +})); +console.log(message(() => { + t.count++; +})); +console.log(message(() => { + ({ value: t.count } = { value: 5 }); +})); +t.items.push("still mutable"); +console.log(t.count, t.items, Object.isFrozen(t.items)); +console.log(JSON.stringify(Object.getOwnPropertyDescriptor(t, "count"))); + +class Holder { + value: T; + constructor(value: T) { + this.value = value; + } +} +const h = Object.freeze(new Holder("text")); +console.log(h.value, Object.isFrozen(h)); +const h2 = new Holder(4); +Object.seal(h2); +h2.value = 5; +console.log(h2.value, Object.isSealed(h2)); + +class Settings { + mode = "dev"; + level = 1; +} +const settings = new Settings(); +console.log(settings); +Object.defineProperty(settings, "level", { enumerable: false }); +console.log(settings, inspect(settings)); +console.log([settings], { settings }); + +class Pair { + x = 1; + y = 2; +} +const pair = new Pair(); +Object.freeze(pair); +console.log(pair, Object.isFrozen(pair)); +const sealed = new Pair(); +(sealed as any).extra = "added"; +Object.seal(sealed); +console.log(sealed, Object.isSealed(sealed)); + +// JSON.stringify reads the same view: non-enumerable fields are skipped. +console.log(JSON.stringify(cfg), JSON.stringify(settings)); +console.log(JSON.stringify({ settings, pair, sealed })); +console.log(JSON.stringify([fz, t])); diff --git a/tests/corpus/6005-class-instance-integrity-sloppy.cjs b/tests/corpus/6005-class-instance-integrity-sloppy.cjs new file mode 100644 index 000000000..92c959d34 --- /dev/null +++ b/tests/corpus/6005-class-instance-integrity-sloppy.cjs @@ -0,0 +1,40 @@ +class Box { + constructor() { + this.x = 1; + } + bump() { + this.x = 2; + } +} + +const b = new Box(); +Object.freeze(b); +b.x = 5; +console.log(b.x, Object.isFrozen(b)); + +function strictWrite() { + "use strict"; + try { + b.x = 6; + } catch (e) { + console.log(e instanceof TypeError, e.message); + } +} +strictWrite(); + +try { + b.bump(); +} catch (e) { + console.log("class body:", e.message); +} +console.log(b.x); + +const s = new Box(); +Object.seal(s); +s.x = 3; +console.log(s.x, Object.isSealed(s), Object.isFrozen(s)); + +const plain = new Box(); +Object.defineProperty(plain, "x", { writable: false }); +plain.x = 9; +console.log(plain.x, JSON.stringify(Object.getOwnPropertyDescriptor(plain, "x"))); diff --git a/tests/corpus/6055-class-generator-iterables.ts b/tests/corpus/6055-class-generator-iterables.ts new file mode 100644 index 000000000..50641c261 --- /dev/null +++ b/tests/corpus/6055-class-generator-iterables.ts @@ -0,0 +1,86 @@ +// Classes whose [Symbol.iterator] is a generator method iterate the +// generator it returns, called once: in for-of (an early exit closes it), +// array and Math.max spreads, Array.from, new Set, new Map, +// Object.fromEntries and yield*. +class Counter implements Iterable { + n: number; + constructor(n: number) { + this.n = n; + } + *[Symbol.iterator](): Generator { + for (let i = 0; i < this.n; i++) yield i; + } +} +const c = new Counter(3); +for (const v of c) console.log("of", v); +console.log([...c], Array.from(c), Math.max(...c)); +for (const v of c) { + if (v === 1) break; + console.log("early", v); +} + +class Pairs implements Iterable<[string, number]> { + *[Symbol.iterator](): Generator<[string, number]> { + yield ["a", 1]; + yield ["b", 2]; + } +} +class Nums implements Iterable { + *[Symbol.iterator](): Generator { + yield 1; + yield 2; + yield 2; + } +} +const p = new Pairs(); +const n = new Nums(); +console.log(new Set(n).size); +console.log(new Map(p).get("b")); + +console.log(Object.fromEntries(p)); +function* outer(): Generator { + yield 0; + yield* n; +} +console.log([...outer()]); +function* plain(): Generator { + yield 3; + yield 3; + yield 4; +} +console.log(new Set(plain()).size, [...new Set(n)]); + +class Tracked implements Iterable { + log: string[] = []; + *[Symbol.iterator](): Generator { + try { + for (const word of ["a", "b", "c"]) { + this.log.push("yield " + word); + yield word; + } + } finally { + this.log.push("closed"); + } + } +} +const t = new Tracked(); +for (const w of t) { + if (w === "b") break; + t.log.push("saw " + w); +} +console.log(t.log.join(", ")); +class Tree implements Iterable { + value: number; + children: Tree[]; + constructor(value: number, children: Tree[] = []) { + this.value = value; + this.children = children; + } + *[Symbol.iterator](): Generator { + yield this.value; + for (const child of this.children) yield* child; + } +} +class Leafy extends Tree {} +const tree = new Tree(1, [new Tree(2, [new Tree(3)]), new Leafy(4)]); +console.log([...tree], Array.from(new Leafy(9)), Math.min(...tree)); diff --git a/tests/corpus/6056-class-symbol-method-calls.ts b/tests/corpus/6056-class-symbol-method-calls.ts new file mode 100644 index 000000000..2829dc067 --- /dev/null +++ b/tests/corpus/6056-class-symbol-method-calls.ts @@ -0,0 +1,78 @@ +// Well-known symbol methods called through element access, and +// Iterator.from over a class whose [Symbol.iterator] is a generator method: +// both give the class's own generator, so iterator helpers apply to it. +class Range { + lo: number; + hi: number; + constructor(lo: number, hi: number) { + this.lo = lo; + this.hi = hi; + } + *[Symbol.iterator](): Generator { + for (let i = this.lo; i < this.hi; i++) yield i; + } +} +class Labeled extends Range { + label = "l"; +} +class Words { + text: string; + constructor(text: string) { + this.text = text; + } + *[Symbol.iterator](): Generator { + for (const w of this.text.split(" ")) yield w; + } +} +class Money { + cents: number; + constructor(cents: number) { + this.cents = cents; + } + [Symbol.toPrimitive](hint: string): string | number { + return hint === "number" ? this.cents / 100 : `$${(this.cents / 100).toFixed(2)}`; + } +} +class Handle { + name: string; + constructor(name: string) { + this.name = name; + } + [Symbol.dispose](): void { + console.log("dispose", this.name); + } +} + +const r = new Range(1, 6); +const direct = r[Symbol.iterator](); +console.log(direct.next(), direct.next()); +console.log(r[Symbol.iterator]().map((x) => x + 1).toArray()); +const ranges: Range[] = [new Range(0, 3), new Labeled(5, 7)]; +for (const x of ranges) console.log(x[Symbol.iterator]().toArray()); +console.log(r[Symbol.iterator]().filter((x) => x > 3).toArray()); + +const it = Iterator.from(r); +console.log(it.next().value, it.next().value); +console.log([...it]); +console.log(Iterator.from(r).map((x) => x * 10).toArray()); +console.log(Iterator.from(r).filter((x) => x % 2 === 1).toArray()); +console.log(Iterator.from(r).take(2).toArray(), Iterator.from(r).drop(3).toArray()); +console.log(Iterator.from(r).reduce((a, b) => a + b, 0)); +console.log(Iterator.from(r).some((x) => x > 4), Iterator.from(r).every((x) => x > 0)); +console.log(Iterator.from(r).find((x) => x > 2)); +console.log(Iterator.from(new Words("a bb ccc")).map((w) => w.length).toArray()); +Iterator.from(r).forEach((x) => console.log("each", x)); +for (const x of Iterator.from(r).flatMap((x) => [x, -x]).take(4)) console.log(x); +const g = Iterator.from(r); +for (const x of g) { + if (x === 2) break; +} +console.log(g.next()); + +console.log(Iterator.from(r), r[Symbol.iterator](), Iterator.from(r).map((x) => x)); +console.log(Iterator.from(r).flatMap((x) => new Set([x, x + 10])).toArray()); +console.log(r[Symbol.iterator]().flatMap((x) => new Map([[x, String(x)]])).toArray()); + +const m = new Money(1234); +console.log(m[Symbol.toPrimitive]("number"), m[Symbol.toPrimitive]("default")); +new Handle("h")[Symbol.dispose](); diff --git a/tests/corpus/6058-class-view-asserted-bindings.ts b/tests/corpus/6058-class-view-asserted-bindings.ts new file mode 100644 index 000000000..5014e3b4b --- /dev/null +++ b/tests/corpus/6058-class-view-asserted-bindings.ts @@ -0,0 +1,78 @@ +// A class instance held in a binding that asserts a possibly undefined +// value (`const node = stack.pop()!`) keeps that undefined at run time. +// Asserting the binding to a view of the class (`node as unknown as View`) +// gives the same live instance a direct cast does: getters run on each +// read, the view is the instance for identity, and an undefined value +// stays undefined until a property read throws Node's TypeError. +interface View { + readonly kind: number; + readonly label: string; +} + +abstract class Base { + reads = 0; + abstract get kind(): number; + get label(): string { + this.reads++; + return `node${this.kind}`; + } +} + +class Leaf extends Base { + private readonly value: number; + constructor(value: number) { + super(); + this.value = value; + } + get kind(): number { + return this.value; + } +} + +class Branch extends Base { + get kind(): number { + return 10; + } +} + +function walk(roots: Base[], cb: (node: View) => void): void { + while (roots.length > 0) { + const node = roots.pop()!; + cb(node as unknown as View); + } +} + +const asView = (node: Base): View => node as unknown as View; + +function walkWith(roots: Base[], cb: (node: View) => void): void { + while (roots.length > 0) { + const node = roots.pop()!; + cb(asView(node)); + } +} + +const seen: View[] = []; +let total = 0; +const leaf = new Leaf(2); +walk([new Leaf(1), leaf, new Branch()], (view) => { + total += view.kind; + seen.push(view); +}); +console.log(total, seen.map((view) => view.label).join(",")); +console.log(seen.includes(leaf as unknown as View), leaf.reads); + +const labels: string[] = []; +walkWith([new Branch(), new Leaf(3)], (view) => labels.push(`${view.kind}:${view.label}`)); +console.log(labels.join(" ")); + +function first(roots: Base[]): View { + const node = roots.pop()!; + return node as unknown as View; +} +const missing = first([]); +console.log(missing === undefined); +try { + console.log(missing.kind); +} catch (error) { + console.log(error instanceof TypeError, (error as Error).message); +} diff --git a/tests/corpus/6061-type-only-class-fields/emitted.ts b/tests/corpus/6061-type-only-class-fields/emitted.ts new file mode 100644 index 000000000..aa2ebb2df --- /dev/null +++ b/tests/corpus/6061-type-only-class-fields/emitted.ts @@ -0,0 +1,23 @@ +export class Emitted { + readonly count: number; + readonly files: string[]; + constructor(count: number, files: string[]) { + this.count = count; + this.files = files; + } + summary(): string { + return `${this.count} files`; + } +} + +export class Sealed { + #secret = 1; + read(): number { + return this.#secret; + } +} + +export class Totals { + emitted = 0; + skipped = 0; +} diff --git a/tests/corpus/6061-type-only-class-fields/holder.ts b/tests/corpus/6061-type-only-class-fields/holder.ts new file mode 100644 index 000000000..d3cc1d1ab --- /dev/null +++ b/tests/corpus/6061-type-only-class-fields/holder.ts @@ -0,0 +1,13 @@ +import type { Emitted, Sealed, Totals } from "./emitted.ts"; + +export class Holder { + items: Emitted | undefined = undefined; + sealed: Sealed | undefined = undefined; + history: Emitted[] = []; + totals: Totals = { emitted: 0, skipped: 0 }; + record(result: Emitted): void { + this.items = result; + this.history.push(result); + this.totals.emitted += result.count; + } +} diff --git a/tests/corpus/6061-type-only-class-fields/main.ts b/tests/corpus/6061-type-only-class-fields/main.ts new file mode 100644 index 000000000..264bd4c6b --- /dev/null +++ b/tests/corpus/6061-type-only-class-fields/main.ts @@ -0,0 +1,17 @@ +// Classes that the program reaches only through `import type`: no module +// loads emitted.ts, so no instance of its classes exists, and their types +// describe the object shapes the program does hold, as an interface would. +// Fields of those types hold undefined, object literals, and arrays of them. +import { Holder } from "./holder.ts"; +import type { Emitted } from "./emitted.ts"; + +const holder = new Holder(); +console.log(holder.items === undefined, holder.sealed === undefined); +const result: Emitted = { count: 2, files: ["a.js", "b.js"], summary: () => "two files" }; +holder.record(result); +holder.record({ count: 0, files: [], summary: () => "none" }); +console.log(holder.items?.count, holder.items?.summary(), holder.history.length); +console.log(holder.history[0] === result, holder.history[0]!.files.join(",")); +console.log(JSON.stringify(holder.history.map((entry) => entry.count))); +holder.totals.skipped++; +console.log(holder.totals, JSON.stringify(holder.totals)); diff --git a/tests/corpus/6063-union-class-field-checks.ts b/tests/corpus/6063-union-class-field-checks.ts new file mode 100644 index 000000000..426182b28 --- /dev/null +++ b/tests/corpus/6063-union-class-field-checks.ts @@ -0,0 +1,65 @@ +// A checked conversion of a dynamic value to a union of object types picks +// the member whose fields match, including a field of type `C | undefined` +// that holds an instance of the class C: workerData read as such a union, +// and an unknown value asserted to it, give the instance or undefined. +import { isMainThread, parentPort, Worker, workerData } from "node:worker_threads"; +import { publish } from "@scriptc/threads"; + +class Job { + readonly name: string; + constructor(name: string) { + this.name = name; + } +} + +interface WithJob { + readonly kind: "job"; + readonly job: Job | undefined; +} +interface Plain { + readonly kind: "plain"; + readonly note: string; +} +interface Counted { + readonly kind: "counted"; + readonly count: number; + readonly last: Job | undefined; +} +type Data = WithJob | Plain | Counted; + +function describe(data: Data): string { + switch (data.kind) { + case "job": + return data.job === undefined ? "job none" : `job ${data.job.name}`; + case "plain": + return `plain ${data.note}`; + case "counted": + return `counted ${data.count} ${data.last?.name ?? "none"}`; + } +} + +function run(data: Data): Promise { + return new Promise((resolve) => { + const worker = new Worker(new URL(import.meta.url), { workerData: data }); + worker.on("message", (message) => console.log("worker:", message)); + worker.on("error", (error) => console.log("worker failed:", String(error))); + worker.on("exit", () => resolve()); + }); +} + +if (isMainThread) { + const values: unknown[] = [ + { kind: "job", job: new Job("local") }, + { kind: "job", job: undefined }, + { kind: "plain", note: "text" }, + { kind: "counted", count: 2, last: new Job("tail") }, + ]; + for (const value of values) console.log("local:", describe(value as Data)); + const shared = publish(new Job("shared")); + await run({ kind: "job", job: shared }); + await run({ kind: "job", job: undefined }); + await run({ kind: "plain", note: "worker" }); + await run({ kind: "counted", count: 3, last: shared }); +} else { + parentPort!.postMessage(describe(workerData as Data)); +} diff --git a/tests/corpus/6230-date-parse-legacy.ts b/tests/corpus/6230-date-parse-legacy.ts new file mode 100644 index 000000000..cc71961c0 --- /dev/null +++ b/tests/corpus/6230-date-parse-legacy.ts @@ -0,0 +1,113 @@ +// Date.parse and new Date(string) follow V8's date-string grammar: the ES5 +// date-time format first, then the legacy forms Node accepts (RFC 1123 and +// RFC 2822 HTTP dates, Date#toString output, month names, slashes, AM/PM, +// zone names and numeric offsets). Strings without a zone are local time, +// so every input is parsed under several zones, including DST transitions. +const fixed = [ + "Mon, 01 Jan 2024 00:00:00 GMT", + "Wed, 21 Oct 2015 07:28:00 GMT", + "Sun, 06 Nov 1994 08:49:37 GMT", + "Sunday, 06-Nov-94 08:49:37 GMT", + "Sun Nov 6 08:49:37 1994", + "Tue, 2 Jan 2024 03:04:05 +0100", + "2 Jan 2024 03:04 -0800", + "Mon Jan 01 2024 00:00:00 GMT+0000 (Coordinated Universal Time)", + "Thu Jan 01 1970 00:00:00 GMT-0500 (EST)", + "Jan 5", + "1/2", + "1/2/3", + "2024/01/02", + "2024/01/02 10:00", + "2024-01-02 10:00:00", + "2024-01-02T10:00:00", + "2024-01-02T10:00:00.5", + "2024-01-02T10:00:00.123456Z", + "2024-01-02T10:00:00+0530", + "2024-01-02T24:00:00Z", + "2024-01-02T24:00:01Z", + "2024-01-02T1:00Z", + " 2024-01-01", + "2024-01-01 ", + "Jan 2, 2024", + "January 2, 2024 10:00 PM", + "12:00 am Jan 1 2024", + "12:00 pm Jan 1 2024", + "13:00 pm Jan 1 2024", + "Jan 1 2024 10:00 GMT+5", + "Jan 1 2024 10:00 UTC+0530", + "Jan 1 2024 10:00 +05:30", + "Jan 1 2024 10:00 PST", + "Jan 1 2024 10:00 edt", + "Jan 1 2024 10:00 XYZ", + "Jan 1 2024 10.30", + "10:20:30 Jan 1 2024", + "Jan 1 2024 10:: GMT", + "Jan 1 2024 (a (nested) comment) 10:00", + "Jan 1 2024 10:00)", + "foo 2024-01-01", + "foo2024", + "May 2024", + "2024 May 3", + "3 May 2024", + "May 3 99", + "May 3 49", + "May 3 50", + "Feb 29 2023", + "Feb 30 2024 GMT", + "Dec 31 1969 23:59:59.999 GMT", + "2024-13-01", + "2024-02-31T00:00:00Z", + "+002024-01-01", + "-000000-01-01", + "-000001-01-01T00:00:00Z", + "+275760-09-13T00:00:00.000Z", + "+275760-09-13T00:00:00.001Z", + "Sat, 13 Sep 275760 00:00:00 GMT", + "Sat, 13 Sep 275760 00:00:01 GMT", + "-271821-04-20T00:00:00Z", + "Jan 1 9999999 GMT", + "2024-01-01T10:00:00\u0000junk", + "Jan 1
2024", + "\uFEFFJan 1 2024", + "Jan 1 2024 été", + "0", + "12", + "2024", + "20240", + "", + "garbage", +]; +// A deterministic generator of date-like strings over V8's token classes. +let seed = 20240101; +function next(n: number): number { + seed = (seed * 1103515245 + 12345) % 2147483648; + return seed % n; +} +const pieces = ["Mon", "Tue,", "Wednesday", "Jan", "january", "FEB", "Sept", "dec", "ma", "1", "01", "2", "12", "13", "24", "31", "32", "59", "60", "99", "100", "2024", "1970", "0", "000", "0000", "123456", "-", "+", ":", "::", ".", ",", "/", " ", "\t", "(", ")", "(x)", "GMT", "UTC", "UT", "Z", "T", "t", "PST", "edt", "AM", "pm", "+0100", "-0800", "+01:00", "-8", "GMT+2", "GMT-0530", "999", "1000000", "x", "10:00", "23:59:59", "24:00", "12:30:45.123", "2024-01-15", "2024-01-15T10:00", "T10:00:00Z", "Jan1", "1Jan"]; +const generated: string[] = []; +for (let i = 0; i < 1500; i++) { + let s = ""; + const n = 1 + next(6); + for (let j = 0; j < n; j++) s += pieces[next(pieces.length)]! + (next(3) === 0 ? " " : ""); + generated.push(s); +} +for (const zone of ["UTC", "America/New_York", "Asia/Kolkata", "Australia/Lord_Howe"]) { + process.env.TZ = zone; + console.log("zone", zone); + for (const s of fixed) console.log(JSON.stringify(s), Date.parse(s), new Date(s).getTime()); + let valid = 0; + let sum = 0; + let hash = 0; + for (const s of generated) { + const t = Date.parse(s); + if (Number.isNaN(t)) continue; + valid++; + sum = (sum + (t % 1000003)) % 1000000007; + hash = (hash * 31 + (t % 65521)) % 2147483647; + } + console.log("generated", generated.length, "valid", valid, sum, hash); +} +// An HTTP conditional GET (the fresh package's comparison). +const lastModified = "Sun, 31 Dec 2023 00:00:00 GMT"; +const ifModifiedSince = "Mon, 01 Jan 2024 00:00:00 GMT"; +console.log("fresh", Date.parse(lastModified) <= Date.parse(ifModifiedSince)); diff --git a/tests/corpus/6231-date-local-fields.ts b/tests/corpus/6231-date-local-fields.ts new file mode 100644 index 000000000..6ff18c964 --- /dev/null +++ b/tests/corpus/6231-date-local-fields.ts @@ -0,0 +1,36 @@ +// new Date(year, monthIndex, date, hours, minutes, seconds, ms) builds a +// LOCAL time: fields roll over through MakeDay/MakeTime, years 0-99 mean +// 1900-1999, and the result converts to UTC in the host zone. A time in a +// DST gap reads with the offset before the transition; a time in an +// overlap answers the earlier instant. +const zones = ["UTC", "America/New_York", "Europe/Berlin", "Australia/Lord_Howe", "Asia/Kolkata", "America/St_Johns", "Pacific/Apia"]; +const years = [1800, 1969, 1970, 2000, 2024, 2038, 2100, 0, 49, 99, 100, -1, -271821, 275760]; +for (const zone of zones) { + process.env.TZ = zone; + const out: string[] = []; + for (const y of years) { + for (const m of [0, 2, 9, 11, 12, -1]) { + for (const d of [1, 31, 0]) out.push(`${y}/${m}/${d}=${new Date(y, m, d, 1, 30, 15, 500).getTime()}`); + } + } + console.log(zone, out.join(" ")); + // Every quarter hour across a spring-forward and a fall-back day. + const y = 2024; + const m = zone === "America/New_York" || zone === "Europe/Berlin" ? 2 : 9; + const d = zone === "America/New_York" ? 10 : zone === "Europe/Berlin" ? 31 : 6; + const day: string[] = []; + for (let minute = 0; minute < 24 * 60; minute += 15) { + const spring = new Date(y, m, d, 0, minute); + const fall = new Date(y, m + 7, d, 0, minute); + day.push(`${spring.getTime()}:${spring.getHours()}:${fall.getTime()}:${fall.getMinutes()}`); + } + console.log(zone, day.join(" ")); + const date = new Date(2020, 0, 15, 12, 30, 45, 123); + console.log(zone, date.getTime(), date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds(), date.getTimezoneOffset()); +} +process.env.TZ = "America/New_York"; +console.log(new Date(2020, 0).getTime(), new Date(2020, 0, 1, 0, 0, 0, 0.9).getTime(), new Date(2020.7, 0.9).getTime(), new Date(-0.5, 0).getTime()); +console.log(new Date(1e6, 0).getTime(), new Date(1e6 + 0.5, 0).getTime(), new Date(2020, 1e7).getTime(), new Date(2020, 1e7 + 1).getTime()); +console.log(new Date(NaN, 0).getTime(), new Date(2020, 0, Infinity).getTime(), new Date(2020, 0, 1, -Infinity).getTime()); +console.log(new Date(275760, 8, 13).getTime(), new Date(275760, 8, 12, 20).getTime(), new Date(-271821, 3, 19, 20).getTime()); +console.log(new Date(2021, 6, 4, 9, 5).toISOString(), new Date(99, 11, 31, 23, 59, 59, 999).toISOString()); diff --git a/tests/corpus/6232-date-fields-js.cjs b/tests/corpus/6232-date-fields-js.cjs new file mode 100644 index 000000000..4ae8d94d2 --- /dev/null +++ b/tests/corpus/6232-date-fields-js.cjs @@ -0,0 +1,50 @@ +// JavaScript callers of the Date field constructor and the date-string +// parser: arguments pass through ToNumber in order (valueOf runs, strings +// and booleans convert, a throwing conversion stops the rest), arguments +// past the seventh are evaluated but never converted, and RFC 1123 dates +// parse the way HTTP caching middleware reads them. +process.env.TZ = "Europe/Berlin"; +const log = []; +const arg = (name, value) => ({ valueOf() { log.push(name); return value; } }); +const d = new Date(arg("y", 2024), arg("m", 1), arg("d", 29), arg("h", 13), arg("mi", 5), arg("s", 6), arg("ms", 7), arg("extra", 8)); +console.log(d.getTime(), d.toISOString(), log.join(",")); +console.log(new Date("2024", "0", "15").getTime(), new Date(2024, true, null).getTime(), new Date(2024, undefined).getTime()); +console.log(new Date(2024, 0, "x").getTime(), new Date(99, 0).getFullYear(), new Date(2024, 0, 1, 2, 3).getHours()); +try { + new Date(2024, arg("m", 0), { valueOf() { throw new Error("conversion stopped"); } }, arg("h", 1)); +} catch (e) { + console.log(e.message, log.slice(-1).join(",")); +} +function isFresh(reqHeaders, resHeaders) { + const modifiedSince = reqHeaders["if-modified-since"]; + const lastModified = resHeaders["last-modified"]; + if (!modifiedSince || !lastModified) return false; + const lm = Date.parse(lastModified); + const ms = Date.parse(modifiedSince); + return !(isNaN(lm) || isNaN(ms) || lm > ms); +} +console.log(isFresh({ "if-modified-since": "Mon, 01 Jan 2024 00:00:00 GMT" }, { "last-modified": "Sun, 31 Dec 2023 00:00:00 GMT" })); +console.log(isFresh({ "if-modified-since": "Sun, 31 Dec 2023 00:00:00 GMT" }, { "last-modified": "Mon, 01 Jan 2024 00:00:00 GMT" })); +console.log(isFresh({ "if-modified-since": "not a date" }, { "last-modified": "Mon, 01 Jan 2024 00:00:00 GMT" })); +const stamps = ["Tue, 15 Nov 1994 08:12:31 GMT", "Tuesday, 15-Nov-94 08:12:31 GMT", "Tue Nov 15 08:12:31 1994", "2024-03-31 02:30"]; +for (const s of stamps) console.log(s, Date.parse(s), new Date(s).getTime()); +// Date#toUTCString's IMF-fixdate (cookie Expires, Last-Modified) round-trips +// through the parser. Where toUTCString has no native lowering yet the same +// string comes from fields; `same` checks that string against Node's. +const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; +const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; +const pad = (n, w) => String(n).padStart(w, "0"); +function imf(d) { + const y = d.getUTCFullYear(); + const year = y < 0 ? "-" + pad(-y, 4) : pad(y, 4); + return days[d.getUTCDay()] + ", " + pad(d.getUTCDate(), 2) + " " + months[d.getUTCMonth()] + " " + year + " " + + pad(d.getUTCHours(), 2) + ":" + pad(d.getUTCMinutes(), 2) + ":" + pad(d.getUTCSeconds(), 2) + " GMT"; +} +const stampsMs = [0, 1445412480000, 951782400000, -1, -62135596800000, -61851600000000, -59011459200000, 253402300799000, 1709251199999, -2208988800001, 8640000000000000, -8640000000000000]; +for (const ms of stampsMs) { + const d = new Date(ms); + const method = d[["toUTC", "String"].join("")]; + const s = typeof method === "function" ? method.call(d) : imf(d); + console.log(JSON.stringify(s), Date.parse(s), Date.parse(s) === ms - (ms % 1000 + 1000) % 1000, s === imf(d)); +} +console.log(Date.parse("Wed, 21 Oct 2015 07:28:00 GMT"), new Date("Wed, 21 Oct 2015 07:28:00 GMT").getTime()); diff --git a/tests/corpus/6233-object-identity-dynamic.cjs b/tests/corpus/6233-object-identity-dynamic.cjs new file mode 100644 index 000000000..41701d96b --- /dev/null +++ b/tests/corpus/6233-object-identity-dynamic.cjs @@ -0,0 +1,105 @@ +// Plain objects, arrays, functions and the builtin collections inherit +// Object.prototype's constructor and methods: `o.constructor === Object`, +// `o.valueOf === Object.prototype.valueOf` and the isPlainObject and +// deep-equal idioms built on them (fast-deep-equal, is-plain-obj, lodash) +// answer exactly what Node answers. +function equal(a, b) { + if (a === b) return true; + if (a && b && typeof a == "object" && typeof b == "object") { + if (a.constructor !== b.constructor) return false; + var length, i, keys; + if (Array.isArray(a)) { + length = a.length; + if (length != b.length) return false; + for (i = length; i-- !== 0; ) if (!equal(a[i], b[i])) return false; + return true; + } + if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; + if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); + if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); + keys = Object.keys(a); + length = keys.length; + if (length !== Object.keys(b).length) return false; + for (i = length; i-- !== 0; ) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + for (i = length; i-- !== 0; ) { + var key = keys[i]; + if (!equal(a[key], b[key])) return false; + } + return true; + } + return a !== a && b !== b; +} +class Point { + constructor(x) { + this.x = x; + } +} +console.log(equal({ a: [1, { b: 2 }] }, { a: [1, { b: 2 }] }), equal({ a: 1 }, { a: "1" }), equal(new Date(0), new Date(0)), equal(new Date(0), new Date(1))); +console.log(equal(/a/g, /a/g), equal(/a/g, /a/i), equal([1, 2], [1, 2, 3]), equal(new Map(), new Set()), equal(new Point(1), new Point(1)), equal(new Point(1), { x: 1 })); +console.log(equal(JSON.parse('{"x":{"y":[1,2]}}'), { x: { y: [1, 2] } }), equal(NaN, NaN), equal(null, {})); + +const values = { + plain: { a: 1 }, + parsed: JSON.parse('{"x":{"y":1}}').x, + array: [1, 2], + date: new Date(0), + regexp: /a/g, + map: new Map([[1, 2]]), + set: new Set([1]), + fn: function () {}, + arrow: () => 1, + error: new TypeError("x"), + promise: Promise.resolve(1), + nullProto: Object.create(null), + created: Object.create({ inherited: 1 }), + instance: new Point(2), +}; +const ctors = { Object: Object, Array: Array, Date: Date, RegExp: RegExp, Map: Map, Set: Set, Function: Function, TypeError: TypeError, Promise: Promise, Point: Point }; +for (const name of Object.keys(values)) { + const v = values[name]; + const c = v.constructor; + const hits = Object.keys(ctors).filter((k) => c === ctors[k]); + console.log(name, hits.join("|") || "-", v.valueOf === Object.prototype.valueOf, + name === "regexp" ? "-" : v.toString === Object.prototype.toString, + v.hasOwnProperty === Object.prototype.hasOwnProperty, + v.propertyIsEnumerable === Object.prototype.propertyIsEnumerable, + Object.prototype.toString.call(v)); +} +const o = { a: 1 }; +console.log(typeof Object, typeof Date, typeof Map, Object.name, Date.name, o.constructor.name, typeof o.constructor); +console.log(o.toString(), o.valueOf() === o, Object(o) === o, typeof Object(), Object(null).constructor === Object); +const { Object: O, Map: M } = globalThis; +console.log(O === Object, M === Map, globalThis.Object === Object, O.keys({ z: 1 }).join()); +const shadow = { constructor: "mine", valueOf() { return 7; }, toString: 3 }; +console.log(shadow.constructor, shadow.valueOf === Object.prototype.valueOf, shadow.toString, shadow.valueOf()); + +function isPlainObj(value) { + if (typeof value !== "object" || value === null) return false; + const prototype = Object.getPrototypeOf(value); + return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value); +} +function isPlainTagged(value) { + if (value == null || Object.prototype.toString.call(value) != "[object Object]") return false; + const proto = Object.getPrototypeOf(value); + return proto === null || proto.constructor === Object; +} +function isPlainCtor(value) { + return !!value && typeof value === "object" && (value.constructor === Object || value.constructor === undefined); +} +for (const name of ["plain", "parsed", "array", "nullProto", "created", "instance"]) { + console.log(name, isPlainObj(values[name]), isPlainTagged(values[name]), isPlainCtor(values[name])); +} +// Primitives answer their wrapper constructors (dequal compares +// `(ctor = a.constructor) === b.constructor` before anything else). +function ctorOf(v) { + return v.constructor; +} +for (const v of [1, "s", true, 10n]) { + const c = ctorOf(v); + console.log(typeof v, c === Number, c === String, c === Boolean, c === BigInt, c === Object, typeof c); +} +function sameCtor(a, b) { + let c; + return !!(a && b && (c = a.constructor) === b.constructor) && (c === Number || c === String); +} +console.log(sameCtor(1, 2), sameCtor(1, "1"), sameCtor("a", "b"), sameCtor({}, {})); diff --git a/tests/corpus/6234-object-prototype-values.cjs b/tests/corpus/6234-object-prototype-values.cjs new file mode 100644 index 000000000..17dc6ff91 --- /dev/null +++ b/tests/corpus/6234-object-prototype-values.cjs @@ -0,0 +1,25 @@ +// Object.prototype as a value: destructured and aliased methods, the +// prototype's own constructor and members, `{}.valueOf`, and the +// `Object.prototype.toString.call(x)` tag idiom (acorn, ini, qs, +// safe-stable-stringify, rfdc). +const { hasOwnProperty, propertyIsEnumerable } = Object.prototype; +const toStr = Object.prototype.toString; +const has = Object.prototype.hasOwnProperty; +const o = { a: 1 }; +console.log(hasOwnProperty.call(o, "a"), hasOwnProperty.call(o, "b"), has.call(o, "a"), propertyIsEnumerable.call(o, "a")); +console.log(toStr.call([]), toStr.call(null), toStr.call(undefined), toStr.call(new Date(0)), toStr.call(/x/), toStr.call(1), toStr.call("s"), toStr.call(true), toStr.call(o), toStr.call(new Error("e")), toStr.call(new RangeError("r")), toStr.call(() => 1), toStr.call(new Map())); +console.log({}.valueOf === Object.prototype.valueOf, ({}).toString === toStr, typeof Object.prototype.toString, Object.prototype.constructor === Object); +console.log(hasOwnProperty === Object.prototype.hasOwnProperty, o.hasOwnProperty === hasOwnProperty, [].hasOwnProperty === has); +function kind(value) { + switch (toStr.call(value)) { + case "[object Date]": return "date:" + value.getTime(); + case "[object Array]": return "array:" + value.length; + case "[object RegExp]": return "regexp:" + value.source; + case "[object Error]": return "error:" + value.message; + case "[object Object]": return "object:" + Object.keys(value).join(","); + default: return "other"; + } +} +console.log([new Date(5), [1, 2], /ab/, new Error("boom"), { p: 1, q: 2 }, 3].map(kind).join(" ")); +const config = JSON.parse('{"name":"app","db":{"host":"h"}}'); +for (const key in config) if (has.call(config, key)) console.log(key, typeof config[key], toStr.call(config[key])); diff --git a/tests/corpus/6235-object-identity-typed.ts b/tests/corpus/6235-object-identity-typed.ts new file mode 100644 index 000000000..70386d17f --- /dev/null +++ b/tests/corpus/6235-object-identity-typed.ts @@ -0,0 +1,24 @@ +// TypeScript records and class instances read their inherited +// Object.prototype members as values, so the identity tests agree with +// Node: `o.constructor === Object`, `o.valueOf === Object.prototype.valueOf`. +interface Shape { + kind: string; + size: number; +} +class Box { + width = 2; + describe(): string { + return `box ${this.width}`; + } +} +const record: Shape = { kind: "square", size: 3 }; +const box = new Box(); +const empty = {}; +console.log(record.constructor === Object, record.valueOf === Object.prototype.valueOf, record.toString === Object.prototype.toString); +console.log(record.hasOwnProperty === Object.prototype.hasOwnProperty, empty.valueOf === Object.prototype.valueOf, typeof record.constructor); +console.log(box.valueOf === Object.prototype.valueOf, box.toString === Object.prototype.toString, box.describe()); +const parsed: unknown = JSON.parse('{"a":1}'); +if (typeof parsed === "object" && parsed !== null) { + console.log(parsed.constructor === Object, Object.getPrototypeOf(parsed) === Object.prototype); +} +console.log(Object.getPrototypeOf(record) === Object.prototype, record.toString(), record.constructor === empty.constructor); diff --git a/tests/corpus/6236-builtin-function-values.cjs b/tests/corpus/6236-builtin-function-values.cjs new file mode 100644 index 000000000..703495b0f --- /dev/null +++ b/tests/corpus/6236-builtin-function-values.cjs @@ -0,0 +1,55 @@ +// Builtin timer functions stored as values (bcryptjs's `nextTick` feature +// probe, async libraries' scheduler selection): conditional, logical and +// assigned values are real functions — typeof answers "function", identity +// holds across spellings, and calls schedule with Node's argument contract +// and ordering. +var nextTick = + typeof setImmediate === "function" + ? setImmediate + : typeof queueMicrotask === "function" + ? queueMicrotask + : setTimeout; +const pick = process.argv.length > 100; +const later = pick ? setImmediate : process.nextTick; +const timeout = pick ? setImmediate : setTimeout; +const micro = pick || queueMicrotask; +let assigned = setTimeout; +assigned = pick ? assigned : setImmediate; +const { setInterval: every, clearInterval: stop } = globalThis; +console.log(typeof nextTick, typeof later, typeof timeout, typeof micro, typeof assigned, typeof every); +console.log(nextTick === setImmediate, later === process.nextTick, timeout === setTimeout, micro === queueMicrotask, assigned === globalThis.setImmediate, every === setInterval, stop === clearInterval); +console.log(nextTick.name, later.name, timeout.name, setTimeout.length, queueMicrotask.length); + +const order = []; +nextTick(function () { order.push("immediate:" + arguments.length); }); +later((a, b) => order.push("tick:" + a + b), 1, 2); +// Microtask and nextTick queues order differently in CommonJS and ESM +// entry evaluation, so the microtask reports through its own flag. +let microRan = false; +micro(() => { microRan = true; }); +timeout((x) => order.push("timeout:" + x), 60, "x"); +assigned(() => order.push("assigned")); +const cancelled = timeout(() => order.push("cancelled"), 1); +(pick ? clearImmediate : clearTimeout)(cancelled); +// The interval counts its runs apart from `order`, and the report waits until +// it has stopped, so slow machines print the same line. +let count = 0; +const handle = every(() => { + count++; + if (count === 2) { + stop(handle); + timeout(() => console.log(order.join(" "), "interval:" + count, "micro:" + microRan), 120); + } +}, 20); + +for (const bad of [undefined, 1, "fn"]) { + try { + later(bad); + } catch (e) { + console.log("rejected", typeof bad, e.code, e instanceof TypeError); + } +} +const log = pick ? console.error : console.log; +log("log", typeof log); +const mx = pick ? Math.min : Math.max; +console.log(mx(1, 3, 2), typeof mx); diff --git a/tests/corpus/6341-throw-array-identity.ts b/tests/corpus/6341-throw-array-identity.ts new file mode 100644 index 000000000..1fc78afd7 --- /dev/null +++ b/tests/corpus/6341-throw-array-identity.ts @@ -0,0 +1,56 @@ +// A thrown array reaches the catch binding as the same array: inspection, +// String(), Array.isArray, typed views and mutation see the thrown object, +// including when the thrown value is a union of a record and an array, and +// after a rethrow. +const shared = [1, 2]; +try { + throw shared; +} catch (e) { + console.log(e === shared, Array.isArray(e), String(e)); + (e as number[]).push(3); +} +console.log(shared); + +function pick(k: number): number[] | { a: number } { + return k ? [k, k * 2] : { a: k }; +} +for (const k of [0, 2]) { + try { + throw pick(k); + } catch (e) { + console.log(e, Array.isArray(e), typeof e); + } +} + +try { + try { + throw [7, [8]]; + } catch (e) { + throw e; + } +} catch (e) { + console.log("rethrown", e); +} + +const grid: string[][] = [["x"], ["y", "z"]]; +try { + throw grid; +} catch (e) { + const rows = e as string[][]; + console.log(JSON.stringify(e), rows.length, rows[1]![1]); +} + +try { + throw [] as number[]; +} catch (e) { + console.log(e, String(e) === "", (e as number[]).length); +} + +function fail(items: { id: number }[]): never { + throw items; +} +try { + fail([{ id: 1 }, { id: 2 }]); +} catch (e) { + console.log((e as { id: number }[]).map((item) => item.id)); +} diff --git a/tests/corpus/6342-throw-builtin-identity.ts b/tests/corpus/6342-throw-builtin-identity.ts new file mode 100644 index 000000000..457b94138 --- /dev/null +++ b/tests/corpus/6342-throw-builtin-identity.ts @@ -0,0 +1,56 @@ +// Thrown Maps, Sets, typed arrays, Buffers and functions reach the catch +// binding as the same objects: inspection, String(), typeof, instanceof +// and typed views see the thrown value. +function report(label: string, f: () => void): void { + try { + f(); + } catch (e) { + console.log(label, e, typeof e, e instanceof Map, e instanceof Set, ArrayBuffer.isView(e)); + } +} + +const shared = new Map([["a", 1]]); +try { + throw shared; +} catch (e) { + console.log(e === shared, String(e)); + (e as Map).set("b", 2); +} +console.log(shared); + +report("set", () => { + throw new Set([1, 2]); +}); +report("u8", () => { + throw new Uint8Array([1, 2, 3]); +}); +report("f64", () => { + throw new Float64Array([1.5, -0]); +}); +report("buffer", () => { + throw Buffer.from("hi"); +}); +report("function", () => { + throw function named(): number { + return 1; + }; +}); +const twice = (x: number): number => x * 2; +try { + throw twice; +} catch (e) { + console.log(e, typeof e, (e as (x: number) => number)(21)); +} + +function pick(k: number): Map | number[] { + return k ? new Map([["k", k]]) : [k]; +} +for (const k of [0, 3]) report(`union ${k}`, () => { + throw pick(k); +}); + +try { + throw new Set(["x"]); +} catch (e) { + console.log([...(e as Set)].join(","), (e as Set).has("x")); +} diff --git a/tests/corpus/6345-inspect-dyn-holes.ts b/tests/corpus/6345-inspect-dyn-holes.ts new file mode 100644 index 000000000..60ecbfa73 --- /dev/null +++ b/tests/corpus/6345-inspect-dyn-holes.ts @@ -0,0 +1,29 @@ +// Holey arrays reached through `unknown` print their holes as Node does: +// runs of holes as `` entries, which count toward the +// 100-entry limit, inside nested values too. +function show(label: string, value: unknown): void { + console.log(label, value); +} +const a: number[] = []; +a[2] = 1; +show("lead", a); +const t: string[] = ["x"]; +t[4] = "y"; +t.length = 7; +show("runs", t); +const one: number[] = [1]; +one[2] = 3; +show("single", one); +const wide: number[] = []; +for (let i = 0; i < 150; i += 2) wide[i] = i; +show("wide", wide); +const dense: number[] = []; +for (let i = 0; i < 120; i++) dense[i] = i; +dense[130] = 1; +show("dense", dense); +show("nested", { rows: [a, one] }); +const made: unknown = new Array(3); +show("ctor", made); +const holes: number[] = []; +holes.length = 3; +show("length", holes); diff --git a/tests/corpus/6346-inspect-dyn-dataview.ts b/tests/corpus/6346-inspect-dyn-dataview.ts new file mode 100644 index 000000000..ff4acfafd --- /dev/null +++ b/tests/corpus/6346-inspect-dyn-dataview.ts @@ -0,0 +1,41 @@ +// A DataView reached through `unknown` keeps its own behavior: Node's +// `DataView { [byteLength], [byteOffset], [buffer] }` inspection, the +// "[object DataView]" string and tag, no length or indexed elements, and +// no typed array methods. +import { inspect } from "node:util"; + +function show(label: string, value: unknown): void { + console.log(label, value); +} + +const buf = new ArrayBuffer(8); +const whole = new DataView(buf); +whole.setUint16(0, 0x1234); +show("whole", whole); +show("offset", new DataView(buf, 2, 4)); +show("empty", new DataView(new ArrayBuffer(0))); +show("big", new DataView(new ArrayBuffer(120))); +show("nested", { view: new DataView(buf, 6) }); +show("shared", new DataView(new SharedArrayBuffer(2))); + +const u: unknown = whole; +console.log(inspect({ a: { u } }, { depth: 1 }), inspect([u], { depth: 0 })); +console.log(String(u), Object.prototype.toString.call(u), `${u}`); + +const view: unknown = new DataView(buf, 1); +const o = view as { [k: string]: unknown }; +console.log(o.length, o[0], o.byteLength, o.byteOffset); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const loose = view as any; +console.log(loose.toString(), loose.valueOf() === view); +for (const method of ["at", "join", "fill", "slice"]) { + try { + loose[method](0); + } catch (e) { + console.log(method, (e as Error).name); + } +} +console.log(view instanceof DataView, ArrayBuffer.isView(view)); +// A typed array through `unknown` is unaffected. +const bytes: unknown = new Uint8Array([1, 2]); +console.log(bytes, String(bytes), `${bytes}`, (bytes as Uint8Array).length); diff --git a/tests/corpus/6347-dataview-methods-js.cjs b/tests/corpus/6347-dataview-methods-js.cjs new file mode 100644 index 000000000..680aaa011 --- /dev/null +++ b/tests/corpus/6347-dataview-methods-js.cjs @@ -0,0 +1,25 @@ +'use strict'; +// A DataView held in an untyped binding answers its own methods and +// Object.prototype's, and none of the typed array methods. +const view = [new DataView(new ArrayBuffer(4))][0]; +function attempt(label, f) { + try { + console.log(label, f()); + } catch (e) { + console.log(label, `${e.name}: ${e.message}`); + } +} +function use(v) { + v.setUint16(0, 0x1234); + attempt('get', () => [v.getUint16(0), v.getUint8(1)].join(' ')); + attempt('toString', () => v.toString()); + attempt('toLocaleString', () => v.toLocaleString()); + attempt('valueOf', () => v.valueOf() === v); + attempt('join', () => v.join('-')); + attempt('at', () => v.at(0)); + attempt('fill', () => v.fill(1)); + attempt('slice', () => v.slice(0, 1)); + attempt('indexOf', () => v.indexOf(0)); + attempt('string', () => `${String(v)} ${v}`); +} +use(view); diff --git a/tests/corpus/6348-errno-code-own-field.ts b/tests/corpus/6348-errno-code-own-field.ts new file mode 100644 index 000000000..02a3d722a --- /dev/null +++ b/tests/corpus/6348-errno-code-own-field.ts @@ -0,0 +1,46 @@ +// `(err as NodeJS.ErrnoException).code` reads an Error subclass's own +// `code` field, as Node reads the own property, and still reads the errno +// code of runtime errors and undefined for plain errors. +import { readFileSync } from "node:fs"; + +class AppError extends Error { + code: string; + constructor(message: string, code = "E_APP") { + super(message); + this.name = "AppError"; + this.code = code; + } +} +class DbError extends AppError { + constructor() { + super("db", "E_DB"); + } +} +class Later extends Error { + code?: string; +} + +function codeOf(err: unknown): string { + if (err instanceof Error) return (err as NodeJS.ErrnoException).code ?? "(none)"; + return "(not an error)"; +} + +console.log(codeOf(new AppError("direct"))); +console.log(codeOf(new DbError())); +const wrapped = new TypeError("wrapped", { cause: new AppError("cause", "E_CAUSE") }); +console.log(codeOf(wrapped.cause), codeOf(wrapped)); +try { + throw new DbError(); +} catch (err) { + console.log(codeOf(err)); +} +const later = new Later("later"); +console.log(codeOf(later)); +later.code = "E_LATER"; +console.log(codeOf(later)); +try { + readFileSync("/nonexistent/scriptc-corpus-6348"); +} catch (err) { + console.log(codeOf(err)); +} +console.log(codeOf(new Error("plain")), codeOf(new RangeError("range")), codeOf("text")); diff --git a/tests/corpus/6356-format-integer-dyn.ts b/tests/corpus/6356-format-integer-dyn.ts new file mode 100644 index 000000000..a4bd53ab0 --- /dev/null +++ b/tests/corpus/6356-format-integer-dyn.ts @@ -0,0 +1,9 @@ +// util.format's %d and %i over checked-dynamic arguments, as Node reads +// them: a bigint prints as `${n}n`, a symbol as NaN, and any other value +// as Number(arg) (%d) or parseInt(String(arg)) (%i). +import { format } from "node:util"; +const values: unknown[] = [{}, [5], [1, 2], "12px", " 0x1f", "", null, undefined, true, 3.9, -0, 10n, Symbol("s"), new Date(0), { valueOf: () => 7 }, NaN, "1e3"]; +for (const v of values) console.log(format("%d|%i", v, v)); +console.log(format("%d and %i", 2n ** 70n, -5n)); +const report: unknown = { actual: 3 }; +console.log("... actual %d.", (report as { actual: unknown }).actual); diff --git a/tests/corpus/6380-variadic-wrapper-arguments-apply.ts b/tests/corpus/6380-variadic-wrapper-arguments-apply.ts new file mode 100644 index 000000000..1a14b6e2f --- /dev/null +++ b/tests/corpus/6380-variadic-wrapper-arguments-apply.ts @@ -0,0 +1,77 @@ +// A generic higher-order wrapper over `(...args: any) => any` that forwards +// `arguments` with `fn.apply(this, arguments)`, and types its options with +// Parameters / ReturnType. Each call site specializes the wrapper to +// the wrapped function's exact signature. +type AnyFn = (...args: any) => any; + +interface TraceOptions { + name?: string; + tags?: (...args: Parameters) => Record; + onResult?: (result: ReturnType) => void; +} + +function trace(fn: F, opts: TraceOptions = {}): F { + return function (this: unknown) { + const args = arguments as unknown as Parameters; + const tags = opts.tags ? opts.tags(...args) : {}; + console.log("enter", opts.name ?? fn.name, JSON.stringify(tags)); + const result: ReturnType = fn.apply(this, arguments as any); + opts.onResult?.(result); + console.log("exit", opts.name ?? fn.name); + return result; + } as F; +} + +function add(a: number, b: number): number { + return a + b; +} +const tracedAdd = trace(add, { + tags: (a, b) => ({ a: String(a), b: String(b) }), + onResult: (r) => console.log("result", r), +}); +console.log(tracedAdd(2, 3)); +console.log(tracedAdd(10, -4)); + +// A wrapped function with an optional parameter: the omitted position +// arrives as undefined, in the wrapper and its tags callback alike. +function titled(name: string, title?: string): string { + return title ? `${title} ${name}` : name; +} +const tracedTitled = trace(titled, { tags: (name, title) => ({ name, title: title ?? "-" }) }); +console.log(tracedTitled("ada")); +console.log(tracedTitled("ada", "dr")); + +const greet = (who: string, punct: string): string => `hi ${who}${punct}`; +const tracedGreet = trace(greet); +console.log(tracedGreet("ada", "!")); + +const tick = trace(function tick(): number { + return 42; +}); +console.log(tick()); + +interface Point { + x: number; + y: number; +} +const norm = trace((p: Point, scale: number): Point => ({ x: p.x * scale, y: p.y * scale }), { + name: "norm", + tags: (p) => ({ x: String(p.x) }), +}); +console.log(JSON.stringify(norm({ x: 1, y: 2 }, 3))); + +// The arguments tuple spread straight into an optional callback. +function audit(fn: F, check?: (...args: Parameters) => boolean): F { + return function (this: unknown) { + console.log("audit", check?.(...(arguments as unknown as Parameters))); + return fn.apply(this, arguments as any); + } as F; +} +const audited = audit(add, (a, b) => a < b); +console.log(audited(1, 2), audited(5, 3)); +console.log(audit(add)(7, 8)); + +const logOnly = trace((msg: string): void => { + console.log("log:", msg); +}); +logOnly("done"); diff --git a/tests/corpus/6381-variadic-wrapper-async.ts b/tests/corpus/6381-variadic-wrapper-async.ts new file mode 100644 index 000000000..200454a5e --- /dev/null +++ b/tests/corpus/6381-variadic-wrapper-async.ts @@ -0,0 +1,81 @@ +// Variadic generic wrappers around async functions: the wrapper forwards +// `arguments`, awaits the wrapped promise and reports its settled value +// through an Awaited> callback; rejections propagate. +type AnyFn = (...args: any) => any; + +interface Opts { + name?: string; + tags?: (...args: Parameters) => Record; + onSettled?: (value: Awaited>) => void; +} + +function traceAsync Promise>(fn: F, opts: Opts = {}): F { + return async function (this: unknown) { + const args = arguments as unknown as Parameters; + const tags = opts.tags ? opts.tags(...args) : {}; + console.log("start", opts.name ?? fn.name, JSON.stringify(tags)); + try { + const value: Awaited> = await fn.apply(this, arguments as any); + opts.onSettled?.(value); + console.log("ok", opts.name ?? fn.name); + return value; + } catch (e) { + console.log("failed", opts.name ?? fn.name, (e as Error).message); + throw e; + } + } as F; +} + +// The settled result spelled as a conditional over ReturnType. +type Settled = ReturnType extends PromiseLike ? U : ReturnType; + +function summarized Promise>( + fn: F, + summarize: (value: Settled) => string, +): F { + return async function (this: unknown) { + const value: Settled = await fn.apply(this, arguments as any); + console.log("summary", summarize(value)); + return value; + } as F; +} + +async function fetchUser(id: number, prefix: string): Promise { + await Promise.resolve(); + if (id < 0) throw new Error(`bad id ${id}`); + return `${prefix}${id}`; +} + +const getUser = traceAsync(fetchUser, { + tags: (id) => ({ id: String(id) }), + onSettled: (v) => console.log("settled", v.toUpperCase()), +}); + +const sleepless = traceAsync(async (): Promise => 7, { name: "seven" }); + +interface Report { + ok: boolean; + items: string[]; +} +const report = summarized( + async (count: number): Promise => ({ ok: count > 0, items: ["a", "b"].slice(0, count) }), + (r) => `${r.ok}:${r.items.join("+")}`, +); +const counted = summarized(async (n: number): Promise => n * 2, (n) => `n=${n + 1}`); + +async function main(): Promise { + console.log(await getUser(1, "u-")); + console.log(await sleepless()); + console.log(JSON.stringify(await report(1))); + console.log(await counted(4)); + try { + await getUser(-2, "u-"); + } catch (e) { + console.log("caught", (e as Error).message); + } + const pending = getUser(3, "a-"); + const second = await getUser(4, "b-"); + console.log(second, await pending); +} + +main().then(() => console.log("main done")); diff --git a/tests/corpus/6382-variadic-wrapper-methods-this.ts b/tests/corpus/6382-variadic-wrapper-methods-this.ts new file mode 100644 index 000000000..54e043207 --- /dev/null +++ b/tests/corpus/6382-variadic-wrapper-methods-this.ts @@ -0,0 +1,46 @@ +// Variadic generic wrappers on methods: the wrapper passes its own `this` +// through `fn.apply(this, arguments)`, so a wrapped `function (this: C)` +// stored as a class property sees the receiver it was called on. +type AnyFn = (...args: any) => any; + +function logged(fn: F, label: string): F { + return function (this: unknown) { + console.log(`[${label}] call with ${arguments.length} args`); + return fn.apply(this, arguments as any); + } as F; +} + +class Counter { + count = 0; + step: number; + constructor(step: number) { + this.step = step; + } + bump = logged(function (this: Counter, times: number): number { + this.count += this.step * times; + return this.count; + }, "bump"); + reset = logged((): void => { + this.count = 0; + }, "reset"); +} + +const c = new Counter(5); +console.log(c.bump(1)); +console.log(c.bump(2)); +c.reset(); +console.log(c.count); + +class Repo { + base: string; + constructor(base: string) { + this.base = base; + } + load = logged(async function (this: Repo, id: string): Promise { + await Promise.resolve(); + return `${this.base}/${id}`; + }, "load"); +} + +const repo = new Repo("https://x.test"); +repo.load("7").then((url) => console.log("loaded", url)); diff --git a/tests/corpus/6383-variadic-wrapper-rest-tuple.ts b/tests/corpus/6383-variadic-wrapper-rest-tuple.ts new file mode 100644 index 000000000..64f1cbacc --- /dev/null +++ b/tests/corpus/6383-variadic-wrapper-rest-tuple.ts @@ -0,0 +1,54 @@ +// The type-safe variadic wrapper: rest tuples instead of `arguments`. +// `...args: A` with A bound to the wrapped function's parameter tuple +// flattens to that fixed signature; `fn.apply(this, args)`, `fn(...args)` +// and `opts.tags(...args)` forward the tuple's positions. +interface Opts { + name?: string; + tags?: (...args: A) => Record; + onResult?: (r: R) => void; +} + +function wrap(fn: (...args: A) => R, opts: Opts = {}): (...args: A) => R { + return function (this: unknown, ...args: A): R { + const tags = opts.tags ? opts.tags(...args) : {}; + console.log("enter", opts.name ?? fn.name, JSON.stringify(tags)); + const r = fn.apply(this, args); + opts.onResult?.(r); + return r; + }; +} + +function viaSpread(fn: (...args: A) => R): (...args: A) => R { + return (...args: A): R => { + console.log("spread", args.length); + return fn(...args); + }; +} + +function mul(a: number, b: number): number { + return a * b; +} +const m = wrap(mul, { tags: (a, b) => ({ a: String(a), b: String(b) }), onResult: (r) => console.log("r", r) }); +console.log(m(6, 7)); + +const concat = wrap((s: string, n: number, flag: boolean) => `${s}:${n}:${flag}`, { name: "concat" }); +console.log(concat("x", 1, true)); + +const opt = wrap((s: string, n?: number) => `${s}:${n ?? "none"}`, { tags: (s, n) => ({ s, n: String(n) }) }); +console.log(opt("a", undefined), opt("b", 2)); + +const zero = wrap(() => "nothing"); +console.log(zero()); + +const spreadMul = viaSpread(mul); +console.log(spreadMul(3, 3)); + +// A concrete tuple rest parameter has the same fixed signature. +function pair(...args: [string, number]): string { + return `${args[0]}=${args[1]}`; +} +console.log(pair("k", 9)); +const asValue: (k: string, v: number) => string = pair; +console.log(asValue("v", 1)); +const t: [string, number] = ["t", 2]; +console.log(pair(...t)); diff --git a/tests/corpus/6384-variadic-wrapper-nested.ts b/tests/corpus/6384-variadic-wrapper-nested.ts new file mode 100644 index 000000000..76f714402 --- /dev/null +++ b/tests/corpus/6384-variadic-wrapper-nested.ts @@ -0,0 +1,37 @@ +// Nested variadic wrappers: wrapping an already wrapped function, mixing +// the `arguments` form and the rest-tuple form. The inner wrapper's +// function binding gives the outer wrapper its `fn.name`. +type AnyFn = (...args: any) => any; + +function trace(fn: F, label?: string): F { + const wrapped = function (this: unknown) { + console.log("trace>", label ?? fn.name); + const out = fn.apply(this, arguments as any); + console.log("trace<", label ?? fn.name); + return out; + }; + return wrapped as unknown as F; +} + +function timed(fn: (...args: A) => R): (...args: A) => R { + return function (this: unknown, ...args: A): R { + console.log("timed>", fn.name, args.length); + return fn.apply(this, args); + }; +} + +function area(w: number, h: number): number { + return w * h; +} + +const twice = trace(trace(area)); +console.log(twice(2, 3)); + +const labelled = trace(trace(area, "inner"), "outer"); +console.log(labelled(4, 5)); + +const mixed = timed(trace(area)); +console.log(mixed(6, 7)); + +const mixed2 = trace(timed(area)); +console.log(mixed2(1, 9)); diff --git a/tests/corpus/6385-variadic-wrapper-shared-local-names.ts b/tests/corpus/6385-variadic-wrapper-shared-local-names.ts new file mode 100644 index 000000000..20712bc69 --- /dev/null +++ b/tests/corpus/6385-variadic-wrapper-shared-local-names.ts @@ -0,0 +1,75 @@ +// Variadic wrappers whose bodies declare the same local names: one only +// forwards its `arguments` (or its alias of them), the other also reads an +// element, so its tuple exists. Each wrapper forwards its own arguments, also +// when a block-local alias shadows an outer one in the same function. +type AnyFn = (...args: any) => any; + +function forward(fn: F): F { + return function (this: unknown) { + return fn.apply(this, arguments as any); + } as F; +} + +function logFirst(fn: F): F { + return function (this: unknown) { + console.log("first:", arguments[0], "of", arguments.length); + return fn.apply(this, arguments as any); + } as F; +} + +function forwardAlias(fn: F): F { + return function (this: unknown) { + const args = arguments as any; + return fn.apply(this, args); + } as F; +} + +function logFirstAlias(fn: F): F { + return function (this: unknown) { + const args = arguments as any; + console.log("alias first:", args[0]); + return fn.apply(this, args); + } as F; +} + +function shadowed(fn: F): F { + return function (this: unknown) { + const args = arguments as any; + console.log("outer first:", args[0]); + if (args.length > 1) { + const args = arguments as any; + return fn.apply(this, args); + } + return fn.apply(this, args); + } as F; +} + +function add3(a: number, b: number, c: number): number { + return a + b + c; +} + +function greet(name: string): string { + return "hi " + name; +} + +const sum = forward(add3); +console.log(sum(1, 2, 3)); +const hello = logFirst(greet); +console.log(hello("ada")); + +const sumAlias = forwardAlias(add3); +console.log(sumAlias(4, 5, 6)); +const helloAlias = logFirstAlias(greet); +console.log(helloAlias("grace")); + +// The other order: the wrapper that reads an element first. +const hello2 = logFirst(greet); +console.log(hello2("linus")); +const sum2 = forward(add3); +console.log(sum2(7, 8, 9)); + +// A block-local alias with the same name as the outer one. +const sumShadowed = shadowed(add3); +console.log(sumShadowed(10, 20, 30)); +const helloShadowed = shadowed(greet); +console.log(helloShadowed("ken")); diff --git a/tests/corpus/6390-catch-binding-predicate.ts b/tests/corpus/6390-catch-binding-predicate.ts new file mode 100644 index 000000000..82c8797b5 --- /dev/null +++ b/tests/corpus/6390-catch-binding-predicate.ts @@ -0,0 +1,42 @@ +// A catch binding narrowed by a user type predicate reads its fields. +interface ErrorLike { + message: string; + name: string; + code?: string; +} +function isErrorLike(value: unknown): value is ErrorLike { + return typeof value === "object" && value !== null && "message" in value && "name" in value; +} +interface Coded { + code: string; +} +function isCoded(value: unknown): value is Coded { + return typeof value === "object" && value !== null && typeof (value as Coded).code === "string"; +} +function report(fn: () => void): void { + try { + fn(); + } catch (err) { + if (isErrorLike(err)) { + console.log("error", { message: err ? err.message : "none", name: err.name, code: err.code ?? "-" }); + if (err.message.includes("disk")) console.log("disk problem"); + } else { + console.log("not error-like", String(err)); + } + if (isCoded(err) && err.code === "ENOSPC") console.log("coded", err.code); + } +} +report(() => { + throw new Error("disk full"); +}); +report(() => { + const e = new Error("no space") as Error & { code?: string }; + e.code = "ENOSPC"; + throw e; +}); +report(() => { + throw "plain"; +}); +report(() => { + throw { message: "obj", name: "Custom", code: "ENOSPC" }; +}); diff --git a/tests/corpus/6391-nullish-function-default.ts b/tests/corpus/6391-nullish-function-default.ts new file mode 100644 index 000000000..6721d97bf --- /dev/null +++ b/tests/corpus/6391-nullish-function-default.ts @@ -0,0 +1,13 @@ +// `??` with a function fallback whose signature differs from the optional +// callback's (fewer parameters, narrower result). +interface Opts { + tags?: (a: number, b: number) => Record; + label?: (n: number) => string; +} +function run(o: Opts, x: number, y: number): string { + const tags = o.tags ?? (() => ({})); + const label = o.label ?? ((n: number) => `#${n}`); + return `${label(x)} ${JSON.stringify(tags(x, y))}`; +} +console.log(run({}, 1, 2)); +console.log(run({ tags: (a, b) => ({ a, b }), label: (n) => `n${n}` }, 3, 4)); diff --git a/tests/corpus/6392-optional-chain-protocol-calls.ts b/tests/corpus/6392-optional-chain-protocol-calls.ts new file mode 100644 index 000000000..a08a4ee85 --- /dev/null +++ b/tests/corpus/6392-optional-chain-protocol-calls.ts @@ -0,0 +1,47 @@ +// Optional calls on an interface-typed method chain (a do-nothing tracer). +interface Span { + addTags(tags: Record): Span; +} +interface Scope { + active(): Span | null; +} +interface Tracer { + scope(): Scope; +} +class NoopSpan implements Span { + addTags(tags: Record): Span { + console.log("tags", JSON.stringify(tags)); + return this; + } +} +class NoopScope implements Scope { + span: Span | null; + constructor(span: Span | null) { + this.span = span; + } + active(): Span | null { + return this.span; + } +} +class NoopTracer implements Tracer { + s: Scope; + constructor(s: Scope) { + this.s = s; + } + scope(): Scope { + return this.s; + } +} +const quiet: Tracer = new NoopTracer(new NoopScope(null)); +const loud: Tracer = new NoopTracer(new NoopScope(new NoopSpan())); +function tagActiveSpan(tracer: Tracer, tags: Record): void { + tracer.scope().active()?.addTags(tags); +} +tagActiveSpan(quiet, { a: 1 }); +tagActiveSpan(loud, { b: 2 }); + +// Narrowed locals and non-null assertions reach the same protocol calls. +const scope2: Scope = new NoopScope(new NoopSpan()); +const current = scope2.active(); +current?.addTags({ c: 3 }); +scope2.active()!.addTags({ d: 4 }); diff --git a/tests/corpus/6393-function-types-any-params.ts b/tests/corpus/6393-function-types-any-params.ts new file mode 100644 index 000000000..ec7799c9d --- /dev/null +++ b/tests/corpus/6393-function-types-any-params.ts @@ -0,0 +1,8 @@ +// Function values whose parameters are typed any (optional or not). +function build(limit?: any): string { + return `limit=${limit ?? "none"}`; +} +const fns: Array<(limit?: any) => string> = [build]; +console.log(fns[0]!(), fns[0]!(5)); +const take: (v: any) => number = (v) => (typeof v === "number" ? v : -1); +console.log(take(3), take("x")); diff --git a/tests/corpus/6394-in-key-error-subclasses.ts b/tests/corpus/6394-in-key-error-subclasses.ts new file mode 100644 index 000000000..5ade6bcd1 --- /dev/null +++ b/tests/corpus/6394-in-key-error-subclasses.ts @@ -0,0 +1,31 @@ +// `'key' in err` on Error receivers, including subclasses that declare it. +class HttpError extends Error { + status: number; + constructor(message: string, status: number) { + super(message); + this.status = status; + } +} +function describe(err: Error): string { + if ("status" in err) return `status ${(err as HttpError).status}`; + if ("code" in err) return "has code"; + return `plain ${err.message}`; +} +console.log(describe(new Error("x"))); +console.log(describe(new HttpError("nf", 404))); +class Timeout extends HttpError { + get retryable(): boolean { + return true; + } +} +function retryable(err: Error): boolean { + return "retryable" in err; +} +console.log(retryable(new Error("x")), retryable(new HttpError("a", 500)), retryable(new Timeout("t", 504))); +console.log(describe(new Timeout("t", 504))); +// Keys every Error has: its own stack, and Error.prototype's and +// Object.prototype's members. +function inherent(err: Error): boolean[] { + return ["stack" in err, "toString" in err, "constructor" in err, "hasOwnProperty" in err]; +} +console.log(inherent(new Error("s")), inherent(new HttpError("h", 400)), inherent(new TypeError("t"))); diff --git a/tests/corpus/6395-generic-object-entries.ts b/tests/corpus/6395-generic-object-entries.ts new file mode 100644 index 000000000..772367a3a --- /dev/null +++ b/tests/corpus/6395-generic-object-entries.ts @@ -0,0 +1,7 @@ +// Object.entries over a generic record parameter. +function maskSecrets>(tags: T): Record { + return Object.fromEntries( + Object.entries(tags).map(([k, v]) => [k, k.includes("secret") ? "[redacted]" : v]), + ); +} +console.log(JSON.stringify(maskSecrets({ user: "ada", secretToken: "x", n: 1 }))); diff --git a/tests/corpus/6396-generic-any-instantiation.ts b/tests/corpus/6396-generic-any-instantiation.ts new file mode 100644 index 000000000..c3c9da060 --- /dev/null +++ b/tests/corpus/6396-generic-any-instantiation.ts @@ -0,0 +1,18 @@ +// A generic method instantiated at `any` from an untyped forwarding call. +type AnyFn = (...args: any) => any; +class Tracer { + trace(name: string, fn: () => T): T { + console.log("span", name); + return fn(); + } +} +const tracer = new Tracer(); +function traced(name: string, fn: F): F { + return function (this: unknown) { + const args = arguments; + const result = tracer.trace(name, () => fn.apply(this, args as any)); + return result; + } as F; +} +const add = traced("add", (a: number, b: number) => a + b); +console.log(add(1, 2)); diff --git a/tests/corpus/6397-event-loop-metrics.ts b/tests/corpus/6397-event-loop-metrics.ts new file mode 100644 index 000000000..8477dcc48 --- /dev/null +++ b/tests/corpus/6397-event-loop-metrics.ts @@ -0,0 +1,70 @@ +// Event-loop monitoring under @types/node: monitorEventLoopDelay's histogram, +// eventLoopUtilization deltas, and an unref'd interval typed NodeJS.Timeout. +import { monitorEventLoopDelay, performance } from "node:perf_hooks"; +import * as perfHooks from "node:perf_hooks"; +import { monitorEventLoopDelay as eld } from "node:perf_hooks"; + +const histogram = monitorEventLoopDelay({ resolution: 10 }); +console.log("enable", histogram.enable(), histogram.enable()); +const first = performance.eventLoopUtilization(); +console.log("before loop", first.idle, first.active, first.utilization); + +let last = performance.eventLoopUtilization(); +let ticks = 0; +// The unref'd interval keeps nothing alive; this timer holds the loop open +// until the third sample. +const keepAlive = setTimeout(() => console.log("never"), 10_000); +const interval = setInterval(() => { + const now = performance.eventLoopUtilization(); + const delta = performance.eventLoopUtilization(now, last); + last = now; + ticks++; + const p50 = histogram.percentile(50); + const p99 = histogram.percentile(99); + console.log( + "tick", + ticks, + delta.utilization >= 0 && delta.utilization <= 1, + delta.idle >= 0, + p50 > 0, + p99 >= p50, + histogram.max >= p99, + histogram.min <= p50, + histogram.count > 0, + ); + histogram.reset(); + console.log("after reset", histogram.count, histogram.max, histogram.min, Number.isNaN(histogram.mean)); + if (ticks === 3) { + clearInterval(interval); + clearTimeout(keepAlive); + console.log("disable", histogram.disable(), histogram.disable()); + const since = performance.eventLoopUtilization(first); + console.log("since", since.utilization > 0 && since.utilization <= 1, since.idle > 0); + try { + histogram.percentile(0); + } catch (e) { + console.log((e as Error).message, (e as { code?: string }).code); + } + } +}, 50); +interval.unref(); +console.log("hasRef", interval.hasRef()); + +// Other import spellings, and Node's resolution validation. +const aliased = eld(); +const namespaced = perfHooks.monitorEventLoopDelay({ resolution: 20 }); +console.log("spellings", aliased.enable(), namespaced.enable(), aliased.disable(), namespaced.disable()); +for (const resolution of [0, -0, -1, 1.5, NaN, Infinity, 1e16, 1]) { + try { + eld({ resolution }); + console.log("created", resolution); + } catch (e) { + console.log((e as Error).name, (e as { code?: string }).code, (e as Error).message); + } +} +// An undefined resolution takes Node's default. +function withResolution(resolution?: number): boolean { + const h = monitorEventLoopDelay({ resolution }); + return h.enable() && h.disable(); +} +console.log("defaults", withResolution(), withResolution(20), eld({ resolution: undefined }).enable()); diff --git a/tests/corpus/6398-conditional-fixed-arity.ts b/tests/corpus/6398-conditional-fixed-arity.ts new file mode 100644 index 000000000..b98bc146a --- /dev/null +++ b/tests/corpus/6398-conditional-fixed-arity.ts @@ -0,0 +1,66 @@ +// Conditional types over function types with fixed `any` and `any[]` +// parameters, as a generic wrapper's specialization sees them: a function +// that needs more arguments than the pattern passes takes the false +// branch, including through a tuple rest parameter, and a positional +// `any[]` parameter matches only functions whose parameter there accepts +// an array. +type FirstResult = F extends (a: any) => infer R ? R : "takes more"; +type NoArgResult = F extends () => infer R ? R : "takes some"; +type ArrayResult = F extends (items: any[]) => infer R ? R : "not array"; + +function label any>(fn: F, value: FirstResult): string { + const local: FirstResult = value; + return `${fn.name}: ${typeof local} ${String(local)}`; +} + +function labelNoArgs any>(fn: F, value: NoArgResult): string { + const local: NoArgResult = value; + return `${fn.name}: ${typeof local} ${String(local)}`; +} + +function labelArray any>(fn: F, value: ArrayResult): string { + const local: ArrayResult = value; + return `${fn.name}: ${typeof local} ${String(local)}`; +} + +function none(): boolean { + return true; +} +function one(a: string): number { + return a.length; +} +function two(a: string, b: string): number { + return a.length + b.length; +} +function optional(a: string, b?: string): number { + return a.length + (b?.length ?? 0); +} +function rest(a: string, ...more: string[]): number { + return a.length + more.length; +} +function pair(...args: [string, number]): number { + return args[0].length + args[1]; +} +function count(items: string[]): number { + return items.length; +} +function anything(value: unknown): string { + return typeof value; +} + +console.log(label(none, true)); +console.log(label(one, 3)); +console.log(label(two, "takes more")); +console.log(label(optional, 4)); +console.log(label(rest, 5)); +console.log(label(pair, "takes more")); +console.log(labelNoArgs(none, false)); +console.log(labelNoArgs(one, "takes some")); +console.log(labelNoArgs(optional, "takes some")); +console.log(labelNoArgs(pair, "takes some")); +console.log(labelArray(none, true)); +console.log(labelArray(one, "not array")); +console.log(labelArray(rest, "not array")); +console.log(labelArray(count, 7)); +console.log(labelArray(anything, "object")); +console.log(labelArray(two, "not array")); diff --git a/tests/corpus/6400-dyn-error-stack-property-js.js b/tests/corpus/6400-dyn-error-stack-property-js.js new file mode 100644 index 000000000..f0f666103 --- /dev/null +++ b/tests/corpus/6400-dyn-error-stack-property-js.js @@ -0,0 +1,148 @@ +// In JavaScript sources, errors have an own `stack` like Node's: a +// non-enumerable accessor that every error shares. The first read formats +// "Name: message" from the error's name and message at that point, and +// later reads answer the same string. Assigning a value replaces it, +// `delete` removes it, Object.keys, spread, Object.assign and +// JSON.stringify leave it out, `in` and Object.hasOwn find it, objects +// that inherit from an error read the error's stack. Typed and dynamic +// reads answer the same string, and a stack read through `any` captures +// frames. Only the first line is printed: frame lines differ between +// runtimes. + +class ParseError extends Error { + constructor(message) { + super(message); + this.name = "ParseError"; + } +} + +class PlainSubclass extends Error {} + +function dyn(value) { + return value; +} + +function firstLine(value) { + return typeof value === "string" ? JSON.stringify(value.split("\n")[0]) : `(${typeof value} ${String(value)})`; +} + +function describe(label, error) { + console.log( + label, + "|", + typeof error.stack, + firstLine(error.stack), + "|", + Object.keys(error).join(","), + JSON.stringify(error), + "|", + "stack" in error, + Object.hasOwn(error, "stack"), + error.propertyIsEnumerable("stack"), + ); +} + +try { + throw new RangeError("thrown"); +} catch (error) { + describe("caught", error); +} + +const unknownError = new TypeError("bad thing"); +describe("unknown", unknownError); +describe("plain", dyn(new Error("plain"))); +describe("no message", dyn(new SyntaxError())); +describe("subclass", dyn(new ParseError("line 3"))); +describe("plain subclass", dyn(new PlainSubclass("inherited name"))); + +const coded = dyn(new Error("with code")); +coded.code = "E_CODE"; +describe("own property", coded); + +const descriptor = dyn(Object.getOwnPropertyDescriptor(dyn(new Error("a")), "stack")); +const other = dyn(Object.getOwnPropertyDescriptor(dyn(new TypeError("b")), "stack")); +console.log("descriptor", Object.keys(descriptor).join(","), descriptor.enumerable, descriptor.configurable); +console.log("shared accessors", typeof descriptor.get, descriptor.get === other.get, descriptor.set === other.set); +console.log("foreign receiver", descriptor.get.call({}), firstLine(descriptor.get.call(dyn(new TypeError("b"))))); +const subclassDescriptor = dyn(Object.getOwnPropertyDescriptor(dyn(new PlainSubclass("s")), "stack")); +console.log("subclass descriptor", Object.keys(subclassDescriptor).join(","), subclassDescriptor.get === descriptor.get); + +const renamed = dyn(new Error("before")); +renamed.message = "after"; +renamed.name = "Renamed"; +console.log("first read", firstLine(renamed.stack)); +renamed.message = "later"; +console.log("later read", firstLine(renamed.stack)); +const subclassRenamed = dyn(new PlainSubclass("before")); +subclassRenamed.message = "after"; +console.log("subclass first read", firstLine(subclassRenamed.stack)); + +const replaced = dyn(new Error("replaced")); +replaced.stack = "custom stack\n at nowhere"; +describe("assigned", replaced); +console.log("assigned descriptor", Object.keys(Object.getOwnPropertyDescriptor(replaced, "stack")).join(",")); +const subclassReplaced = dyn(new ParseError("replaced")); +subclassReplaced.stack = "subclass stack"; +console.log("subclass assigned", subclassReplaced.stack); +const typedReplaced = new ParseError("typed write"); +typedReplaced.stack = "typed subclass stack"; +console.log("typed subclass assigned", typedReplaced.stack, dyn(typedReplaced).stack); +const subclassNumbered = dyn(new PlainSubclass("numbered")); +subclassNumbered.stack = 7; +console.log("subclass number", subclassNumbered.stack, typeof subclassNumbered.stack); +subclassNumbered.stack = "string again"; +console.log("subclass string again", subclassNumbered.stack); +const numbered = dyn(new Error("numbered")); +numbered.stack = 5; +console.log("number", numbered.stack, typeof numbered.stack); +const removed = dyn(new Error("removed")); +delete removed.stack; +console.log("deleted", removed.stack, "stack" in removed, Object.hasOwn(removed, "stack")); + +const spreadSource = dyn(new Error("spread")); +console.log("spread", Object.keys({ ...spreadSource }).length, Object.keys(Object.assign({}, spreadSource)).length); +console.log("inherited", firstLine(Object.create(dyn(new RangeError("prototype"))).stack)); + +const typed = new Error("typed"); +const view = dyn(typed); +console.log("typed and dynamic", view.stack === typed.stack, firstLine(typed.stack)); +const typedSubclass = new ParseError("typed subclass"); +console.log("typed subclass", firstLine(dyn(typedSubclass).stack), dyn(typedSubclass).stack === typedSubclass.stack); +const assignedThroughView = new Error("typed"); +dyn(assignedThroughView).stack = "through the view"; +console.log("assigned through view", assignedThroughView.stack); + +function inner() { + return dyn(new Error("deep")); +} +function outer() { + return inner(); +} +const deep = outer().stack; +console.log("frames", deep.split("\n").length > 1, deep.includes("at inner"), deep.includes("at outer")); +function makeSubclass() { + return dyn(new PlainSubclass("deep")); +} +console.log("subclass frames", makeSubclass().stack.includes("at makeSubclass")); + +const formatted = dyn(new Error("formatted")); +console.log("formatted before clone", firstLine(formatted.stack)); +console.log("clone keeps a formatted stack", firstLine(dyn(structuredClone(formatted)).stack)); + +class GrumpyError extends Error { + /** @returns {string} */ + get name() { + throw new Error("no name today"); + } +} +try { + console.log("throwing name getter", firstLine(dyn(new GrumpyError("grumpy")).stack)); +} catch (error) { + console.log("throwing name getter threw", error.message); +} + +function readKey(target, key) { + return target[key]; +} +const computed = readKey(inner(), ["sta", "ck"].join("")); +console.log("computed key", firstLine(computed), computed.includes("at inner")); diff --git a/tests/corpus/6400-override-any-base.ts b/tests/corpus/6400-override-any-base.ts new file mode 100644 index 000000000..72b050425 --- /dev/null +++ b/tests/corpus/6400-override-any-base.ts @@ -0,0 +1,35 @@ +// Overrides that narrow an `any`-typed base result: a typed async override +// over an untyped base method, and a sync override returning a union. +class Pool { + name = "pool"; + acquire(): any { + return Promise.resolve("base"); + } + describe(): any { + return this.name; + } +} +class MeteredPool extends Pool { + waits = 0; + override async acquire(): Promise { + this.waits++; + await super.acquire(); + } + override describe(): string | undefined { + return this.waits > 1 ? `metered ${this.waits}` : undefined; + } +} +async function use(pool: Pool, label: string): Promise { + const got = await pool.acquire(); + console.log(label, got === undefined ? "undefined" : got, String(pool.describe())); +} +async function main(): Promise { + const metered = new MeteredPool(); + await use(new Pool(), "plain"); + await use(metered, "metered"); + await metered.acquire(); + console.log(metered.waits, metered.describe()); + const pools: Pool[] = [new Pool(), metered]; + for (const p of pools) console.log(typeof p.describe()); +} +main(); diff --git a/tests/corpus/6401-dyn-error-own-property-names.ts b/tests/corpus/6401-dyn-error-own-property-names.ts new file mode 100644 index 000000000..1020cf014 --- /dev/null +++ b/tests/corpus/6401-dyn-error-own-property-names.ts @@ -0,0 +1,44 @@ +// The own property names of errors held as `any` or `unknown` match +// Node's: Object.getOwnPropertyNames, Reflect.ownKeys and +// Object.getOwnPropertyDescriptors list `stack`, `message` and the +// properties the error has, in order, and nothing else. + +class PlainSubclass extends Error {} + +function dyn(value: unknown): any { + return value; +} + +function names(label: string, error: any): void { + console.log( + label, + "|", + Object.getOwnPropertyNames(error).join(","), + "|", + Reflect.ownKeys(error).map(String).join(","), + "|", + Object.keys(Object.getOwnPropertyDescriptors(error)).join(","), + ); +} + +names("error", dyn(new Error("plain"))); +names("type error", dyn(new TypeError("bad thing"))); +names("no message", dyn(new RangeError())); +names("cause", dyn(new Error("outer", { cause: new Error("inner") }))); +names("subclass", dyn(new PlainSubclass("sub"))); + +const unknownError: unknown = new SyntaxError("unexpected"); +names("unknown", unknownError); + +const coded = dyn(new Error("with code")); +coded.code = "E_CODE"; +coded.detail = 1; +names("own properties", coded); + +try { + throw new URIError("thrown"); +} catch (error) { + names("caught", error); +} + +console.log("marker", Object.getOwnPropertyNames(dyn(new Error("x"))).some((name) => name.startsWith("%"))); diff --git a/tests/corpus/6401-override-param-variance.ts b/tests/corpus/6401-override-param-variance.ts new file mode 100644 index 000000000..997207af8 --- /dev/null +++ b/tests/corpus/6401-override-param-variance.ts @@ -0,0 +1,33 @@ +// Overrides whose parameters differ from the base: a typed parameter over +// an `any` slot, a wider (contravariant) parameter, and a narrowed result. +class Handler { + handle(input: any): any { + return `base:${input}`; + } + score(value: string): number | undefined { + return value.length; + } +} +class TypedHandler extends Handler { + override handle(input: string): string { + return `typed:${input.toUpperCase()}`; + } + override score(value: string | number): number { + return typeof value === "string" ? value.length * 10 : value; + } +} +function run(h: Handler): void { + console.log(h.handle("abc"), h.score("xy")); +} +run(new Handler()); +run(new TypedHandler()); +const typed = new TypedHandler(); +console.log(typed.handle("q"), typed.score(7)); +try { + run2(new TypedHandler()); +} catch (e) { + console.log("caught", (e as Error).name); +} +function run2(h: Handler): void { + console.log(h.handle(42)); +} diff --git a/tests/corpus/6402-dyn-error-own-property-names-js.js b/tests/corpus/6402-dyn-error-own-property-names-js.js new file mode 100644 index 000000000..6f6ca7fb7 --- /dev/null +++ b/tests/corpus/6402-dyn-error-own-property-names-js.js @@ -0,0 +1,44 @@ +// In JavaScript sources, the own property names of errors match +// Node's: Object.getOwnPropertyNames, Reflect.ownKeys and +// Object.getOwnPropertyDescriptors list `stack`, `message` and the +// properties the error has, in order, and nothing else. + +class PlainSubclass extends Error {} + +function dyn(value) { + return value; +} + +function names(label, error) { + console.log( + label, + "|", + Object.getOwnPropertyNames(error).join(","), + "|", + Reflect.ownKeys(error).map(String).join(","), + "|", + Object.keys(Object.getOwnPropertyDescriptors(error)).join(","), + ); +} + +names("error", dyn(new Error("plain"))); +names("type error", dyn(new TypeError("bad thing"))); +names("no message", dyn(new RangeError())); +names("cause", dyn(new Error("outer", { cause: new Error("inner") }))); +names("subclass", dyn(new PlainSubclass("sub"))); + +const unknownError = new SyntaxError("unexpected"); +names("unknown", unknownError); + +const coded = dyn(new Error("with code")); +coded.code = "E_CODE"; +coded.detail = 1; +names("own properties", coded); + +try { + throw new URIError("thrown"); +} catch (error) { + names("caught", error); +} + +console.log("marker", Object.getOwnPropertyNames(dyn(new Error("x"))).some((name) => name.startsWith("%"))); diff --git a/tests/corpus/6402-override-js-base/main.ts b/tests/corpus/6402-override-js-base/main.ts new file mode 100644 index 000000000..6821f09dd --- /dev/null +++ b/tests/corpus/6402-override-js-base/main.ts @@ -0,0 +1,26 @@ +// TypeScript overrides of an untyped JavaScript base: a typed async +// override over an untyped method, and a narrowed toString. +import { Pool } from "./pool.js"; + +class MeteredPool extends Pool { + waits = 0; + override async acquire(): Promise { + this.waits++; + const n: number = await super.acquire(); + return n * 10; + } + override toString(): string { + return `Metered(${this.waits})`; + } +} +async function main(): Promise { + const pools: Pool[] = [new Pool(2), new MeteredPool(3)]; + for (const pool of pools) { + const got = await pool.acquire(); + console.log(String(got), pool.toString(), `${pool}`); + pool.release(); + } + const metered = new MeteredPool(1); + console.log(await metered.acquire(), metered.waits); +} +main(); diff --git a/tests/corpus/6402-override-js-base/pool.js b/tests/corpus/6402-override-js-base/pool.js new file mode 100644 index 000000000..d1a337882 --- /dev/null +++ b/tests/corpus/6402-override-js-base/pool.js @@ -0,0 +1,17 @@ +// An untyped JavaScript base class, the shape of a small npm semaphore. +export class Pool { + constructor(size) { + this.size = size; + this.taken = 0; + } + async acquire() { + this.taken++; + return this.taken; + } + release() { + this.taken--; + } + toString() { + return `Pool(${this.taken}/${this.size})`; + } +} diff --git a/tests/corpus/6403-error-code-write.ts b/tests/corpus/6403-error-code-write.ts new file mode 100644 index 000000000..3d6efc2a0 --- /dev/null +++ b/tests/corpus/6403-error-code-write.ts @@ -0,0 +1,39 @@ +// Own properties written on Error values typed with an intersection. +const coded = new Error("c") as Error & { code?: string }; +coded.code = "E1"; +console.log(coded.code, "code" in coded); +function has(err: Error): boolean { + return "code" in err; +} +console.log(has(coded), has(new Error("plain"))); +try { + throw coded; +} catch (e) { + console.log((e as Error & { code?: string }).code); +} + +// System-error members first, then the code: reads stay consistent. +type SysError = Error & { code?: string; errno?: number; syscall?: string; path?: string }; +function sysError(message: string, code: string | undefined): SysError { + const err = new Error(message) as SysError; + err.errno = -2; + err.syscall = "open"; + err.path = "/missing"; + err.code = code; + return err; +} +const enoent = sysError("no such file", "ENOENT"); +console.log(enoent.code, enoent.errno, enoent.syscall, enoent.path, "code" in enoent); +console.log(enoent.message, enoent instanceof Error); +const uncoded = sysError("no code", undefined); +console.log(uncoded.code, uncoded.syscall); + +// A code that is only known at runtime. +function withCode(err: Error & { code?: string }, code: unknown): void { + err.code = code as string; +} +const late = new Error("late") as Error & { code?: string }; +withCode(late, "ELATE"); +console.log(late.code); +late.code = "EAGAIN"; +console.log(late.code, late.message); diff --git a/tests/corpus/6403-error-own-name-order.ts b/tests/corpus/6403-error-own-name-order.ts new file mode 100644 index 000000000..d1a5200c4 --- /dev/null +++ b/tests/corpus/6403-error-own-name-order.ts @@ -0,0 +1,108 @@ +// An error's own `name` is listed where it was created, as in Node: +// after `message` and `cause`, after the fields created before it, and +// before the fields created after it. A `name` field takes its place among +// the fields, `this.name = ...` in a constructor follows that class's +// fields, and a name assigned after construction follows every other own +// property. Object.keys, JSON.stringify and Object.getOwnPropertyNames +// list the same order, for typed values and values held as `any`. + +class AssignedName extends Error { + extra = 1; + constructor(message: string) { + super(message); + this.name = "AssignedName"; + } +} + +class NameField extends Error { + override name = "NameField"; + extra = 1; +} + +class LaterNameField extends Error { + extra = 1; + override name = "LaterNameField"; + last = 2; +} + +class CodeThenName extends Error { + code: string; + constructor(message: string) { + super(message); + this.code = "E_CODE"; + this.name = "CodeThenName"; + } +} + +class NamedBase extends Error { + constructor(message: string) { + super(message); + this.name = "NamedBase"; + } +} + +class ChildOfNamed extends NamedBase { + child = true; +} + +class FieldBase extends Error { + base = "base"; +} + +class NamedChild extends FieldBase { + child = "child"; + constructor(message: string) { + super(message); + this.name = "NamedChild"; + } +} + +class QuotedName extends Error { + override "name" = "QuotedName"; + "quoted-extra" = 1; +} + +class Unnamed extends Error { + extra = 1; +} + +function dyn(value: unknown): any { + return value; +} + +function show(label: string, error: Error): void { + console.log( + label, + "|", + Object.keys(dyn(error)).join(","), + "|", + JSON.stringify(dyn(error)), + "|", + Object.getOwnPropertyNames(dyn(error)).join(","), + ); +} + +show("assigned", new AssignedName("a")); +show("field", new NameField("b")); +show("later field", new LaterNameField("c")); +show("code then name", new CodeThenName("d")); +show("child of named", new ChildOfNamed("e")); +show("named child", new NamedChild("f")); +show("quoted name field", new QuotedName("q")); + +const late = new Unnamed("g"); +late.name = "Late"; +show("assigned later", late); + +const builtin = new TypeError("h", { cause: 1 }); +builtin.name = "Renamed"; +show("builtin", builtin); + +const viaAny = dyn(new RangeError("i")); +viaAny.code = "E_RANGE"; +viaAny.name = "Custom"; +viaAny.after = true; +console.log("through any", Object.keys(viaAny).join(","), JSON.stringify(viaAny)); + +const typed = new AssignedName("j"); +console.log("typed keys", Object.keys(typed).join(","), JSON.stringify({ error: typed })); diff --git a/tests/corpus/6404-error-in-and-assign.ts b/tests/corpus/6404-error-in-and-assign.ts new file mode 100644 index 000000000..a9bb6d086 --- /dev/null +++ b/tests/corpus/6404-error-in-and-assign.ts @@ -0,0 +1,144 @@ +// `'key' in err` on Error values of every receiver form, and +// Object.assign onto a fresh Error. +class HttpError extends Error { + status = 404; + get retryable(): boolean { + return this.status >= 500; + } + describe(): string { + return `${this.status}`; + } +} + +// A receiver narrowed from unknown by instanceof. +function statusOf(value: unknown): unknown { + if (value instanceof Error) { + return "status" in value ? value.status : "none"; + } + return "not an error"; +} +console.log(statusOf(new HttpError("not found")), statusOf(new Error("plain")), statusOf("text")); + +// Parameters, call results and caught values; members a subclass declares +// as fields, accessors and methods; Error.prototype's own members. +function keys(err: Error): string { + return [ + "status" in err, + "retryable" in err, + "describe" in err, + "toString" in err, + "missing" in err, + "message" in err, + ].join(" "); +} +console.log(keys(new HttpError("h"))); +console.log(keys(new Error("e"))); +console.log(keys(new TypeError("t"))); +function latest(): Error { + return new HttpError("latest"); +} +console.log("status" in latest(), "message" in latest()); +function caught(fn: () => void): string { + try { + fn(); + return "ok"; + } catch (e) { + if (e instanceof Error && "status" in e) return `status ${String(e.status)}`; + return "plain"; + } +} +console.log( + caught(() => { + throw new HttpError("t"); + }), + caught(() => { + throw new Error("u"); + }), +); + +// Properties set at runtime are own properties too. +const expando = new Error("x"); +(expando as unknown as { status: number }).status = 1; +console.log(keys(expando)); + +// Object.assign copies the source's own properties onto the error and +// answers the error itself. +interface ErrorLike { + message: string; + status?: number; + code?: string; +} +function toError(error: ErrorLike | string): Error { + return typeof error === "string" + ? new Error(error) + : Object.assign(new Error(error.message), error); +} +const assigned = toError({ message: "boom", status: 500, code: "EBOOM" }); +console.log(assigned.message, (assigned as Error & { code?: string }).code, "status" in assigned); +console.log(statusOf(assigned), assigned instanceof Error, keys(assigned)); +const renamed = toError({ message: "renamed" }); +console.log(renamed.message, "status" in renamed, toError("text").message); +const fresh = new Error("before"); +console.log(Object.assign(fresh, { message: "after" }) === fresh, fresh.message); + +// An Error intersection carrying extra fields: Object.assign's result, +// read and written through its declared members. +interface ApiErrorFields { + status: number; + action: string | undefined; + code: string | undefined; + link: string | undefined; +} +function apiError(message: string, fields: ApiErrorFields) { + const error = Object.assign(new Error(message), fields); + return error; +} +const gateway = apiError("bad gateway", { status: 502, action: undefined, code: "E502", link: "https://example.com" }); +console.log(gateway.message, gateway.status, gateway.code, gateway.link, "action" in gateway, gateway.action); +gateway.status = 503; +console.log(gateway.status, gateway instanceof Error); +Object.assign(gateway, { status: 504, code: "E504" }); +console.log(gateway.status, gateway.code); +function describeApiError(error: Error & { status?: number }): string { + return `${error.message}:${error.status ?? "none"}`; +} +console.log(describeApiError(gateway), describeApiError(new Error("plain"))); + +// Object.assign into a record whose fields are wider than the source's. +interface BuildFailure { + message: string; + bail?: boolean; + code?: string; +} +function failure(message: string, bail: boolean): BuildFailure { + const err: BuildFailure = { message }; + if (bail) Object.assign(err, { bail: true }); + return Object.assign(err, { code: "E_BUILD" }); +} +for (const f of [failure("stop", true), failure("go", false)]) + console.log(f.message, f.bail, f.code); + +// A record given a member its type lacks and thrown at once. +interface FailureRecord { + from: string; + isGraceful: boolean; + errorValue: unknown; +} +function classifyFailure(err: unknown, from: string): FailureRecord { + return { from, isGraceful: from === "user", errorValue: err }; +} +function handleFailure(err: unknown, from: string): string { + const failure = classifyFailure(err, from); + if (failure.from === "build") { + throw Object.assign(failure, { bail: true }); + } + return `${failure.from} ${failure.isGraceful}`; +} +for (const from of ["build", "user"]) { + try { + console.log("handled", handleFailure(new Error("x"), from)); + } catch (e) { + const thrownRecord = e as { from: string; bail?: boolean; isGraceful: boolean }; + console.log("thrown", thrownRecord.from, thrownRecord.bail, thrownRecord.isGraceful); + } +} diff --git a/tests/corpus/6404-error-own-name-order-js.js b/tests/corpus/6404-error-own-name-order-js.js new file mode 100644 index 000000000..b5f9b4d32 --- /dev/null +++ b/tests/corpus/6404-error-own-name-order-js.js @@ -0,0 +1,94 @@ +// In JavaScript sources, an error's own `name` is listed where it was +// created, as in Node: among the properties a constructor assigns, in +// assignment order, after the fields declared before it, and after every +// other own property when it is assigned after construction. + +class AssignedBetween extends Error { + constructor(message) { + super(message); + this.code = "E_CODE"; + this.name = "AssignedBetween"; + this.detail = { at: 1 }; + } +} + +class AssignedFirst extends Error { + constructor(message) { + super(message); + this.name = "AssignedFirst"; + this.status = 500; + } +} + +class DeclaredField extends Error { + kind = "declared"; + constructor(message) { + super(message); + this.name = "DeclaredField"; + this.after = true; + } +} + +class NameField extends Error { + name = "NameField"; + extra = 1; +} + +class Base extends Error { + constructor(message) { + super(message); + this.base = 1; + } +} + +class Derived extends Base { + constructor(message) { + super(message); + this.name = "Derived"; + this.derived = 2; + } +} + +class ElementName extends Error { + constructor(message) { + super(message); + this.first = 1; + this["name"] = "ElementName"; + this.second = 2; + } +} + +class Unnamed extends Error { + constructor(message) { + super(message); + this.extra = 1; + } +} + +function show(label, error) { + console.log( + label, + "|", + Object.keys(error).join(","), + "|", + JSON.stringify(error), + "|", + Object.getOwnPropertyNames(error).join(","), + ); +} + +show("between", new AssignedBetween("a")); +show("first", new AssignedFirst("b")); +show("declared field", new DeclaredField("c")); +show("name field", new NameField("d")); +show("derived", new Derived("e")); +show("element name", new ElementName("g")); + +const late = new Unnamed("f"); +late.name = "Late"; +show("assigned later", late); + +const builtin = new Error("g", { cause: "reason" }); +builtin.name = "Renamed"; +builtin.code = "E_AFTER"; +show("builtin", builtin); diff --git a/tests/corpus/6405-wrapped-default-parameters.ts b/tests/corpus/6405-wrapped-default-parameters.ts new file mode 100644 index 000000000..f607eb885 --- /dev/null +++ b/tests/corpus/6405-wrapped-default-parameters.ts @@ -0,0 +1,67 @@ +// Lambdas whose parameters take their type from an `any` default, passed +// through generic wrappers that forward their arguments. +function trace any>(name: string, fn: F): F { + return ((...args: Parameters) => { + console.log("span", name); + return fn(...args); + }) as F; +} +function traceArgs(name: string, fn: (...args: A) => R): (...args: A) => R { + return (...args: A) => { + console.log("span", name); + return fn(...args); + }; +} +function lookup(key: string): any { + return key === "set" ? 7 : undefined; +} + +const doubled = trace("doubled", (n = lookup("unset") ?? 5) => n * 2); +console.log(doubled(), doubled(4)); +const sum = trace("sum", (n = lookup("set") ?? 5, m = 1) => n * 4 + m); +console.log(sum(), sum(2), sum(2, 3)); +const label = trace("label", (prefix = lookup("unset") ?? "item", count: number = 0) => `${prefix}#${count}`); +console.log(label(), label("row"), label(undefined, 3)); +const tripled = traceArgs("tripled", (n: number = lookup("unset") ?? 5) => n * 3); +console.log(tripled(), tripled(2)); + +// A wrapper that also reports tags computed from the wrapped function's +// result, handing its untyped result on through `as never`. +function traceTagged any>( + name: string, + fn: F, + resultTags?: ( + result: ReturnType extends PromiseLike ? U : ReturnType, + ) => Record, +): F { + return ((...args: Parameters) => { + const result: unknown = fn(...args); + if (resultTags) console.log(name, JSON.stringify(resultTags(result as never))); + return result; + }) as F; +} +const summarize = traceTagged( + "summarize", + (limit = lookup("unset") ?? 2) => ({ kept: Number(limit), dropped: 1 }), + (r) => ({ total: r.kept + r.dropped }), +); +console.log(summarize().kept, summarize(5).kept); + +// The same through a function expression whose result is untyped. +function traceTaggedAny any>( + name: string, + fn: F, + resultTags: (result: ReturnType) => Record, +): F { + return function (this: unknown, ...args: Parameters) { + const result = fn.apply(this, args); + console.log(name, JSON.stringify(resultTags(result as never))); + return result; + } as F; +} +const measure = traceTaggedAny( + "measure", + (width = lookup("set") ?? 1, height = 2) => ({ area: Number(width) * height }), + (r) => ({ area: r.area }), +); +console.log(measure().area, measure(3, 4).area); diff --git a/tests/corpus/6406-npm-instance-receiver/main.ts b/tests/corpus/6406-npm-instance-receiver/main.ts new file mode 100644 index 000000000..3dc893548 --- /dev/null +++ b/tests/corpus/6406-npm-instance-receiver/main.ts @@ -0,0 +1,7 @@ +// A package's constructor function, compiled per call site, reads its own +// receiver while the constructor that reached it has a receiver too. +import { create } from "corked-writer"; +const w = create({ highWaterMark: 4 }); +console.log(w.uncork()); +console.log(w.uncork()); +console.log(create().uncork()); diff --git a/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.d.ts b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.d.ts new file mode 100644 index 000000000..1de006a3d --- /dev/null +++ b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.d.ts @@ -0,0 +1,4 @@ +export interface Writable { + uncork(): string; +} +export function create(options?: { highWaterMark?: number }): Writable; diff --git a/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.js b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.js new file mode 100644 index 000000000..6a7e75541 --- /dev/null +++ b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/index.js @@ -0,0 +1,45 @@ +'use strict'; + +// Constructor functions in the shape of a stream implementation: a request +// object built inside another constructor keeps its receiver in a var for +// a callback, and the writer is also exported as a value. + +function CorkedRequest(state) { + var _this = this; + this.next = null; + this.entry = null; + this.finish = function () { + onCorkedFinish(_this, state); + }; +} +module.exports = { + create: function (options) { + return new Writable(options); + }, + Writable: Writable, +}; +Writable.WritableState = WritableState; +function WritableState(options, stream) { + this.highWaterMark = options.highWaterMark || 16; + this.pending = 2; + this.stream = stream; + this.corkedRequestsFree = new CorkedRequest(this); +} +WritableState.prototype.getBuffer = function getBuffer() { + return this.pending; +}; +function Writable(options) { + this._writableState = new WritableState(options || {}, this); + this.label = "w"; +} +Writable.prototype.uncork = function () { + var state = this._writableState; + var req = state.corkedRequestsFree; + req.finish(); + state.corkedRequestsFree = new CorkedRequest(state); + return this.label + " " + req.next + " " + state.highWaterMark; +}; +function onCorkedFinish(corkReq, state) { + state.pending--; + corkReq.next = "finished:" + state.pending; +} diff --git a/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/package.json b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/package.json new file mode 100644 index 000000000..94a00403d --- /dev/null +++ b/tests/corpus/6406-npm-instance-receiver/node_modules/corked-writer/package.json @@ -0,0 +1 @@ +{ "name": "corked-writer", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/6407-dyn-bytes-in.ts b/tests/corpus/6407-dyn-bytes-in.ts new file mode 100644 index 000000000..e08eff8bc --- /dev/null +++ b/tests/corpus/6407-dyn-bytes-in.ts @@ -0,0 +1,28 @@ +// `in` on typed arrays, Buffers and DataViews held as `any` matches Node's. +// On a typed array a canonical numeric key answers by bounds (integral, +// not -0, below the length), and "1.5", "-0", "NaN", "Infinity", "01" and +// "1e0" answer false. Other keys name members of %TypedArray%.prototype, +// Buffer.prototype for a Buffer, each constructor's BYTES_PER_ELEMENT, or +// Object.prototype. A DataView has no indexed elements and answers its own +// prototype's members. + +const bytes: any = new Uint8Array([1, 2, 255]); +const floats: any = new Float64Array(2); +const buf: any = Buffer.from("ab"); +for (const [label, v] of [["u8", bytes], ["f64", floats], ["buffer", buf]] as [string, any][]) { + const keys: any[] = [0, 1, 2, 3, -1, "0", "1.5", "-0", "length", "byteLength", "buffer", "map", "set", "toString", "nope", "write"]; + console.log(label, keys.map((k) => `${String(k)}:${k in v}`).join(" ")); +} +const k: any = JSON.parse("1"); +console.log("computed", k in bytes, "length" in bytes, "map" in bytes); +const view: any = new DataView(new ArrayBuffer(4)); +console.log("dataview", [0, "getInt8", "setUint16", "byteLength", "buffer", "length", "map", "toString"].map((k) => `${String(k)}:${k in view}`).join(" ")); +const clamped: any = new Uint8ClampedArray(3); + +console.log("clamped", 2 in clamped, 3 in clamped, "BYTES_PER_ELEMENT" in clamped); +const sub: any = new Uint8Array([1, 2, 3, 4]).subarray(1, 3); +console.log("subarray", 0 in sub, 1 in sub, 2 in sub); +console.log("hasOwn", Object.hasOwn(bytes, 0), Object.hasOwn(bytes, 3), Object.hasOwn(bytes, "length"), bytes.hasOwnProperty(1)); +console.log("symbol", Symbol.iterator in bytes); +const empty: any = new Uint8Array(0); +console.log("empty", 0 in empty, "Infinity" in bytes, "NaN" in bytes, "1e0" in bytes, "01" in bytes); diff --git a/tests/corpus/6408-dyn-bytes-in-js.js b/tests/corpus/6408-dyn-bytes-in-js.js new file mode 100644 index 000000000..9677c094b --- /dev/null +++ b/tests/corpus/6408-dyn-bytes-in-js.js @@ -0,0 +1,35 @@ +// In JavaScript sources, `in` on typed arrays, Buffers and DataViews +// matches Node's. On a typed array a canonical numeric key answers by +// bounds (integral, not -0, below the length), and "1.5", "-0", "NaN", +// "Infinity", "01" and "1e0" answer false. Other keys name members of +// %TypedArray%.prototype, Buffer.prototype for a Buffer, each +// constructor's BYTES_PER_ELEMENT, or Object.prototype. A DataView has no +// indexed elements and answers its own prototype's members. + +function dyn(value) { + return value; +} + +const bytes = dyn(new Uint8Array([1, 2, 255])); +const floats = dyn(new Float64Array(2)); +const buf = dyn(Buffer.from("ab")); +function row(label, v) { + const keys = [0, 1, 2, 3, -1, "0", "1.5", "-0", "length", "byteLength", "buffer", "map", "set", "toString", "nope", "write"]; + console.log(label, keys.map((k) => `${String(k)}:${k in v}`).join(" ")); +} +row("u8", bytes); +row("f64", floats); +row("buffer", buf); +const k = dyn(JSON.parse("1")); +console.log("computed", k in bytes, "length" in bytes, "map" in bytes); +const view = dyn(new DataView(new ArrayBuffer(4))); +console.log("dataview", [0, "getInt8", "setUint16", "byteLength", "buffer", "length", "map", "toString"].map((k) => `${String(k)}:${k in view}`).join(" ")); +const clamped = dyn(new Uint8ClampedArray(3)); + +console.log("clamped", 2 in clamped, 3 in clamped, "BYTES_PER_ELEMENT" in clamped); +const sub = dyn(new Uint8Array([1, 2, 3, 4]).subarray(1, 3)); +console.log("subarray", 0 in sub, 1 in sub, 2 in sub); +console.log("hasOwn", Object.hasOwn(bytes, 0), Object.hasOwn(bytes, 3), Object.hasOwn(bytes, "length"), bytes.hasOwnProperty(1)); +console.log("symbol", Symbol.iterator in bytes); +const empty = dyn(new Uint8Array(0)); +console.log("empty", 0 in empty, "Infinity" in bytes, "NaN" in bytes, "1e0" in bytes, "01" in bytes); diff --git a/tests/corpus/6409-catch-binding-shapes.ts b/tests/corpus/6409-catch-binding-shapes.ts new file mode 100644 index 000000000..930c41f38 --- /dev/null +++ b/tests/corpus/6409-catch-binding-shapes.ts @@ -0,0 +1,69 @@ +// Catch bindings typed any, multi-statement guards over catch bindings, +// and writes into a caught error. +interface SafeFetchError extends Error { + kind: "safe-fetch"; + host: string; +} +function isSafeFetchError(e: unknown): e is SafeFetchError { + if (!(e instanceof Error)) return false; + return "kind" in e && e.kind === "safe-fetch"; +} +function blocked(host: string): Error { + return Object.assign(new Error(`blocked ${host}`), { kind: "safe-fetch", host }); +} +function run(fn: () => void): string { + try { + fn(); + return "ok"; + } catch (err) { + if (isSafeFetchError(err)) return `guarded ${err.host} ${err.message}`; + return `other ${err instanceof Error ? err.message : String(err)}`; + } +} +console.log(run(() => {})); +console.log(run(() => { + throw blocked("example.com"); +})); +console.log(run(() => { + throw new Error("plain"); +})); +console.log(run(() => { + throw "text"; +})); + +function annotate(fn: () => void): void { + try { + fn(); + } catch (e: any) { + e.message = `config: ${e.message}`; + if (e.code === "ENOPE") e.hint = "check the file"; + throw e; + } +} +for (const code of ["ENOPE", "EOTHER"]) { + try { + annotate(() => { + throw Object.assign(new Error("missing"), { code }); + }); + } catch (e) { + const err = e as Error & { code?: string; hint?: string }; + console.log(err.message, err.code, err.hint, err instanceof Error); + } +} + +// An assertion on a catch binding typed any. +function codeOf(fn: () => void): string { + try { + fn(); + return "none"; + } catch (e: any) { + const err = e as Error & { code?: string }; + return `${err.message}:${err.code ?? "-"}`; + } +} +console.log( + codeOf(() => { + throw Object.assign(new Error("coded"), { code: "E1" }); + }), + codeOf(() => {}), +); diff --git a/tests/corpus/6410-json-mixed-scalar-tables/extra.json b/tests/corpus/6410-json-mixed-scalar-tables/extra.json new file mode 100644 index 000000000..7af99c22f --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/extra.json @@ -0,0 +1 @@ +[["x", 1, "two", 3], [4, "five", null], ["six"], [null]] diff --git a/tests/corpus/6410-json-mixed-scalar-tables/main.ts b/tests/corpus/6410-json-mixed-scalar-tables/main.ts new file mode 100644 index 000000000..c60f962dc --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/main.ts @@ -0,0 +1,26 @@ +// JSON tables whose rows mix strings and numbers, the shape of a +// character-set table (`[["8140", 12288, 65292], ...]`) and of a +// code-point range map (`[[[0, 44], "disallowed"], [[65, 65], "mapped", +// [97]], ...]`). A package requires them at load or inside a function, and +// an ES module imports one; each row keeps its strings and numbers. +import { lookup, rowCount } from "code-table"; +import { mapChars, status } from "range-map"; +import extra from "./extra.json" with { type: "json" }; + +console.log(lookup("8150"), lookup("a1"), lookup("zzzz"), rowCount()); +const mapped = mapChars("AB-c9"); +console.log(mapped.text, mapped.error); +console.log(JSON.stringify(mapChars("a,b")), JSON.stringify(mapChars("{"))); +console.log(status(10), status(46), status(66), status(1000)); + +for (const row of extra) { + const head = row[0]; + const rest = row.slice(1); + console.log( + typeof head, + head, + rest.length, + rest.map((v) => (v === null ? "null" : typeof v === "number" ? v + 1 : v.toUpperCase())).join(","), + ); +} +console.log(JSON.stringify(extra)); diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.d.ts b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.d.ts new file mode 100644 index 000000000..ae7db1d98 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.d.ts @@ -0,0 +1,2 @@ +export declare function lookup(code: string): number | string; +export declare function rowCount(): number; diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.js b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.js new file mode 100644 index 000000000..dc10bb817 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/index.js @@ -0,0 +1,10 @@ +'use strict'; +const table = require('./table.json'); +exports.lookup = function (code) { + for (const row of table) if (row[0] === code) return row[1]; + return -1; +}; +exports.rowCount = function () { + // A second require of the same document answers the same rows. + return require('./table.json').length; +}; diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/package.json b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/package.json new file mode 100644 index 000000000..e15622d87 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/package.json @@ -0,0 +1 @@ +{ "name": "code-table", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/table.json b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/table.json new file mode 100644 index 000000000..5abaa6772 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/code-table/table.json @@ -0,0 +1 @@ +[["8140", 12288, 65292], ["8150", 65507, 65340], ["a1", "。", 62]] diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.d.ts b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.d.ts new file mode 100644 index 000000000..322bd20e1 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.d.ts @@ -0,0 +1,2 @@ +export declare function mapChars(s: string): { text: string; error: boolean }; +export declare function status(cp: number): string; diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.js b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.js new file mode 100644 index 000000000..481d93ad4 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/index.js @@ -0,0 +1,37 @@ +'use strict'; +var table = require('./table.json'); +function findStatus(val) { + var start = 0; + var end = table.length - 1; + while (start <= end) { + var mid = Math.floor((start + end) / 2); + var target = table[mid]; + if (target[0][0] <= val && target[0][1] >= val) { + return target; + } else if (target[0][0] > val) { + end = mid - 1; + } else { + start = mid + 1; + } + } + return null; +} +exports.status = function (cp) { + var found = findStatus(cp); + return found === null ? 'unknown' : found[1]; +}; +exports.mapChars = function (s) { + var error = false; + var out = ''; + for (var i = 0; i < s.length; ++i) { + var cp = s.codePointAt(i); + var found = findStatus(cp); + if (found === null) { error = true; continue; } + switch (found[1]) { + case 'disallowed': error = true; out += String.fromCodePoint(cp); break; + case 'mapped': out += String.fromCodePoint.apply(String, found[2]); break; + case 'valid': out += String.fromCodePoint(cp); break; + } + } + return { text: out, error: error }; +}; diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/package.json b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/package.json new file mode 100644 index 000000000..bc877e1e8 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/package.json @@ -0,0 +1 @@ +{ "name": "range-map", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/table.json b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/table.json new file mode 100644 index 000000000..900f9e316 --- /dev/null +++ b/tests/corpus/6410-json-mixed-scalar-tables/node_modules/range-map/table.json @@ -0,0 +1 @@ +[[[0,44],"disallowed"],[[45,46],"valid"],[[47,47],"disallowed"],[[48,57],"valid"],[[65,65],"mapped",[97]],[[66,67],"mapped",[98,99]],[[97,122],"valid"]] diff --git a/tests/corpus/6410-wrapper-untyped-results.ts b/tests/corpus/6410-wrapper-untyped-results.ts new file mode 100644 index 000000000..f8df7683a --- /dev/null +++ b/tests/corpus/6410-wrapper-untyped-results.ts @@ -0,0 +1,50 @@ +// Generic wrappers returning their untyped result `as F`: async functions, +// records with unknown fields, and rejections passing through. +function trace any>(name: string, fn: F): F { + return ((...args: Parameters) => { + console.log("span", name); + return fn(...args); + }) as F; +} +function traceThis any>(name: string, fn: F): F { + return function (this: unknown, ...args: Parameters) { + const result: unknown = fn.apply(this, args); + console.log("span", name, typeof result); + return result; + } as F; +} +function lookup(key: string): any { + return key === "set" ? 7 : undefined; +} +const fetchCount = trace("fetchCount", async (base: number): Promise => { + await null; + return base + 1; +}); +const fetchUser = traceThis("fetchUser", async (id: string): Promise<{ id: string; admin: boolean }> => ({ + id, + admin: id === "root", +})); +const fails = trace("fails", async (why: string): Promise => { + throw new Error(why); +}); +const summarize = trace("summarize", (limit = lookup("unset") ?? 2) => ({ kept: limit, dropped: 1 })); +const measure = traceThis("measure", (n: number) => ({ size: n, label: lookup("set") })); +async function main(): Promise { + console.log(await fetchCount(1)); + const user = await fetchUser("root"); + console.log(user.id, user.admin); + try { + await fails("nope"); + } catch (e) { + console.log("caught", e instanceof Error ? e.message : String(e)); + } + const s = summarize(); + console.log(s.kept, s.dropped, summarize(5).kept); + const m = measure(3); + console.log(m.size, m.label); + const pending = fetchCount(10); + console.log("before"); + console.log(await pending); +} +main(); +export {}; diff --git a/tests/corpus/6411-prototype-reset-constructors/issues.js b/tests/corpus/6411-prototype-reset-constructors/issues.js new file mode 100644 index 000000000..1058d7188 --- /dev/null +++ b/tests/corpus/6411-prototype-reset-constructors/issues.js @@ -0,0 +1,27 @@ +// An Error subclass in the shape of a validation library's: the constructor +// resets the prototype to new.target's, as transpiled code does. +export class IssueError extends Error { + constructor(issues) { + super(); + this.issues = []; + const actualProto = new.target.prototype; + if (Object.setPrototypeOf) { + Object.setPrototypeOf(this, actualProto); + } else { + this.__proto__ = actualProto; + } + this.name = "IssueError"; + this.issues = issues; + } + get message() { + return JSON.stringify(this.issues); + } + get isEmpty() { + return this.issues.length === 0; + } +} +export class StrictIssueError extends IssueError { + get strict() { + return true; + } +} diff --git a/tests/corpus/6411-prototype-reset-constructors/main.ts b/tests/corpus/6411-prototype-reset-constructors/main.ts new file mode 100644 index 000000000..65f155e8d --- /dev/null +++ b/tests/corpus/6411-prototype-reset-constructors/main.ts @@ -0,0 +1,33 @@ +// Constructors that reset the instance prototype, the transpiled idiom for +// Error subclasses. +import { IssueError, StrictIssueError } from "./issues.js"; +class AppError extends Error { + constructor(message: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + this.name = "AppError"; + } + describe(): string { + return `${this.name}: ${this.message}`; + } +} +class NotFound extends AppError { + constructor(what: string) { + super(`${what} not found`); + Object.setPrototypeOf(this, NotFound.prototype); + this.name = "NotFound"; + } +} +const app = new AppError("boom"); +const missing = new NotFound("user"); +console.log(app.describe(), app instanceof AppError, app instanceof Error); +console.log(missing.describe(), missing instanceof NotFound, missing instanceof AppError); +const issues = new IssueError([{ path: "a" }]); +console.log(issues.message, issues.isEmpty, issues instanceof IssueError, issues.name); +const strict = new StrictIssueError([]); +console.log(strict.isEmpty, strict.strict, strict instanceof IssueError, String(strict instanceof StrictIssueError)); +try { + throw new NotFound("page"); +} catch (e) { + console.log(e instanceof NotFound ? e.describe() : "other"); +} diff --git a/tests/corpus/6411-union-array-tuple-destructuring.ts b/tests/corpus/6411-union-array-tuple-destructuring.ts new file mode 100644 index 000000000..f62eba996 --- /dev/null +++ b/tests/corpus/6411-union-array-tuple-destructuring.ts @@ -0,0 +1,42 @@ +// Array destructuring of a value whose type is a union of array and tuple +// types: a regex match or a default row (`re.exec(s) || ["", "", ""]`), and +// functions answering either a fixed tuple or a list. Each position reads +// the arm the value holds; positions past a tuple's end and past an array's +// end read undefined, and defaults fire exactly there. +function scan(text: string): string { + const [match, head, word] = /(\w*)-(\w+)/.exec(text) || ["", "", ""]; + return `${match}|${head}|${word.toUpperCase()}`; +} +console.log(scan("ab-cd"), scan("none"), scan("-x")); + +function pick(flag: boolean): [string, number] | string[] { + return flag ? ["a", 1] : ["b", "c"]; +} +const [p, q] = pick(true); +const [r, s = "dflt", t = "z"] = pick(false); +const [u, , w = "w"] = pick(true); +console.log(p, q, r, s, t, u, w); +console.log(typeof q, typeof s); + +function pair(n: number): number[] | [number, number] { + return n > 1 ? [n, n * 2] : [n]; +} +const [x, y] = pair(7); +const [x1, y1 = -1] = pair(1); +console.log(x, y, x1, y1); + +function row(kind: string): [string, boolean] | [string, boolean, number] { + return kind === "long" ? [kind, true, 3] : [kind, false]; +} +for (const k of ["long", "short"]) { + const [name, flag, count = 0] = row(k); + console.log(name, flag, count); +} + +// A match or an empty string (`re.exec(s) || ""`): a string destructures +// by code point, so the empty string binds undefined. +function firstHit(text: string): string { + const [hit] = /\d+/.exec(text) || ""; + return hit ? `hit ${hit}` : "none"; +} +console.log(firstHit("ab12"), firstHit("ab")); diff --git a/tests/corpus/6412-js-error-tostring-override/errors.js b/tests/corpus/6412-js-error-tostring-override/errors.js new file mode 100644 index 000000000..839b57a67 --- /dev/null +++ b/tests/corpus/6412-js-error-tostring-override/errors.js @@ -0,0 +1,22 @@ +// JavaScript Error subclasses whose toString overrides infer results that +// differ from Error.prototype.toString's string. +export class IssueError extends Error { + constructor(issues) { + super("invalid"); + this.issues = issues; + this.name = "IssueError"; + } + /** @returns {string | undefined} */ + toString() { + return this.issues.length > 0 ? this.issues.join("; ") : undefined; + } +} +export class LabelError extends Error { + constructor(label, detail) { + super(detail); + this.label = label; + } + toString() { + return this.label; + } +} diff --git a/tests/corpus/6412-js-error-tostring-override/main.ts b/tests/corpus/6412-js-error-tostring-override/main.ts new file mode 100644 index 000000000..15a857efd --- /dev/null +++ b/tests/corpus/6412-js-error-tostring-override/main.ts @@ -0,0 +1,9 @@ +// TypeScript callers of JavaScript toString overrides whose inferred +// results differ from the inherited string. +import { IssueError, LabelError } from "./errors.js"; +const issues = new IssueError(["a is required", "b must be a number"]); +console.log(issues.toString()); +console.log(String(issues), `${new IssueError([])}`); +const errors: Error[] = [issues, new LabelError("db", "down"), new Error("plain")]; +for (const e of errors) console.log(e.toString(), e instanceof IssueError); +console.log(errors.map((e) => String(e)).join(" | ")); diff --git a/tests/corpus/6413-override-js-async-base/lock.js b/tests/corpus/6413-override-js-async-base/lock.js new file mode 100644 index 000000000..1ea00239d --- /dev/null +++ b/tests/corpus/6413-override-js-async-base/lock.js @@ -0,0 +1,22 @@ +// An untyped JavaScript lock whose async acquire resolves to an untyped +// ticket. +export class Lock { + constructor() { + this.held = false; + this.log = []; + this.queue = makeQueue(this.log); + } + async acquire() { + const ticket = await this.queue.wait(); + this.held = true; + this.log.push("base acquire"); + return ticket; + } + release() { + this.held = false; + } +} + +function makeQueue(log) { + return { wait: () => new Promise((resolve) => setTimeout(() => resolve(log.length), 1)) }; +} diff --git a/tests/corpus/6413-override-js-async-base/main.ts b/tests/corpus/6413-override-js-async-base/main.ts new file mode 100644 index 000000000..da0c0b183 --- /dev/null +++ b/tests/corpus/6413-override-js-async-base/main.ts @@ -0,0 +1,30 @@ +// TypeScript overrides returning Promise over an untyped JavaScript +// async method: an async override and a promise-returning one. +import { Lock } from "./lock.js"; +class TimedLock extends Lock { + waits = 0; + async acquire(): Promise { + this.waits++; + await super.acquire(); + this.log.push("timed acquire"); + } +} +class EagerLock extends Lock { + acquire(): Promise { + this.log.push("eager"); + return Promise.resolve(); + } +} +async function main(): Promise { + const locks: Lock[] = [new TimedLock(), new EagerLock(), new Lock()]; + for (const lock of locks) { + await lock.acquire(); + console.log(lock.held, lock.log.join(",")); + } + const timed = new TimedLock(); + await timed.acquire(); + console.log(timed.waits, timed.held); + const viaBase: Lock = new TimedLock(); + console.log(await new Lock().acquire(), await viaBase.acquire()); +} +main(); diff --git a/tests/corpus/6414-catch-values-roundtrip.ts b/tests/corpus/6414-catch-values-roundtrip.ts new file mode 100644 index 000000000..ebf62980e --- /dev/null +++ b/tests/corpus/6414-catch-values-roundtrip.ts @@ -0,0 +1,114 @@ +// Thrown values of every kind read back through catch bindings: primitives, +// arrays, plain objects, Errors and subclasses, undefined and null, through +// un-narrowed reads and through typeof, instanceof, Array.isArray and +// predicate narrowing. +class AppError extends Error { + code: string; + constructor(message: string, code: string) { + super(message); + this.name = "AppError"; + this.code = code; + } +} +interface Shaped { + kind: string; + size: number; +} +function isShaped(value: unknown): value is Shaped { + return typeof value === "object" && value !== null && "kind" in value && "size" in value; +} +// Each value is thrown at its own static type (values thrown from `unknown` +// storage are covered separately). +const letters = ["a", "b", "c"]; +const none: string[] = []; +const shapedValue: Shaped = { kind: "box", size: 3 }; +const emptyObject = {}; +const plainError = new Error("plain"); +const typeError = new TypeError("typed"); +const appError = new AppError("app", "E_APP"); +const thrown: unknown[] = [ + 5, + 0, + -1.5, + "text", + "", + true, + false, + letters, + none, + shapedValue, + emptyObject, + plainError, + typeError, + appError, + undefined, + null, +]; +function throwAt(index: number): never { + switch (index) { + case 0: + throw 5; + case 1: + throw 0; + case 2: + throw -1.5; + case 3: + throw "text"; + case 4: + throw ""; + case 5: + throw true; + case 6: + throw false; + case 7: + throw letters; + case 8: + throw none; + case 9: + throw shapedValue; + case 10: + throw emptyObject; + case 11: + throw plainError; + case 12: + throw typeError; + case 13: + throw appError; + case 14: + throw undefined; + default: + throw null; + } +} +function describe(index: number): string { + try { + throwAt(index); + } catch (e) { + const parts: string[] = [String(e === thrown[index])]; + if (typeof e === "number") parts.push(`number ${e + 1}`); + else if (typeof e === "string") parts.push(`string ${e.length}`); + else if (typeof e === "boolean") parts.push(`boolean ${!e}`); + else if (Array.isArray(e)) parts.push(`array ${e.length} ${e.join("+")}`); + else if (e instanceof AppError) parts.push(`app ${e.code} ${e.message}`); + else if (e instanceof Error) parts.push(`error ${e.name} ${e.message}`); + else if (isShaped(e)) parts.push(`shaped ${e.kind} ${e.size}`); + else if (e === undefined) parts.push("undefined"); + else if (e === null) parts.push("null"); + else parts.push(`other ${typeof e}`); + return parts.join(" "); + } +} +for (let i = 0; i < thrown.length; i++) console.log(i, describe(i)); +for (let i = 0; i < 10; i++) { + try { + if (i === 5) throw i; + } catch (e) { + console.log("loop", e === 5, e); + } +} +try { + throw ["x", "y"]; +} catch (e) { + const list = e as string[]; + console.log(list[0], list[1], list.length); +} diff --git a/tests/corpus/6415-error-code-dynamic-writes.ts b/tests/corpus/6415-error-code-dynamic-writes.ts new file mode 100644 index 000000000..f4c4d5628 --- /dev/null +++ b/tests/corpus/6415-error-code-dynamic-writes.ts @@ -0,0 +1,64 @@ +// An Error's code written through a dynamic view (a `catch (e: any)` +// binding), copied onto an Error subclass with Object.assign, set to +// undefined, or deleted is what typed reads and `'code' in err` see, on +// Errors and on Error subclasses. +class CodedError extends Error {} + +const original = new Error("a") as NodeJS.ErrnoException; +try { + throw original; +} catch (e: any) { + e.code = "E_ANY"; +} +console.log("any write", original.code, "code" in original); + +try { + throw original; +} catch (e: any) { + delete e.code; +} +console.log("any delete", original.code, "code" in original); + +function codeOf(err: NodeJS.ErrnoException): string | undefined { + return err.code; +} +const sub = new CodedError("b"); +Object.assign(sub, { code: "E_ASSIGN" }); +console.log("subclass assign", codeOf(sub), "code" in sub); + +const plain = new Error("c") as NodeJS.ErrnoException; +Object.assign(plain, { code: "E_PLAIN" }); +console.log("assign", plain.code, "code" in plain); + +const cleared = new Error("d") as NodeJS.ErrnoException; +cleared.code = undefined; +console.log("undefined", cleared.code, "code" in cleared); +const fresh = new Error("e") as NodeJS.ErrnoException; +console.log("fresh", fresh.code, "code" in fresh); + +const subCleared = new CodedError("f") as NodeJS.ErrnoException; +subCleared.code = undefined; +console.log("subclass undefined", subCleared.code, "code" in subCleared); +try { + throw subCleared; +} catch (e: any) { + console.log("subclass undefined through any", e.code, "code" in e); +} +subCleared.code = "E_SUB"; +console.log("subclass string", subCleared.code, "code" in subCleared); +try { + throw subCleared; +} catch (e: any) { + e.code = undefined; +} +console.log("subclass any undefined", subCleared.code, "code" in subCleared); +try { + throw subCleared; +} catch (e: any) { + delete e.code; +} +console.log("subclass delete", subCleared.code, "code" in subCleared); + +const subAssigned = new CodedError("g"); +Object.assign(subAssigned, { code: undefined }); +console.log("subclass assign undefined", codeOf(subAssigned), "code" in subAssigned); diff --git a/tests/corpus/6416-tls-session-reused.ts b/tests/corpus/6416-tls-session-reused.ts new file mode 100644 index 000000000..a30f4f1b0 --- /dev/null +++ b/tests/corpus/6416-tls-session-reused.ts @@ -0,0 +1,33 @@ +// TLSSocket.isSessionReused() on both ends of a fresh TLS connection, +// before and after the handshake: a first handshake never resumes a +// session, so every answer is false. +import * as tls from "node:tls"; +import { readFileSync } from "node:fs"; + +const cert = readFileSync("tests/fixtures/server/certs/localhost.pem"); +const key = readFileSync("tests/fixtures/server/certs/localhost-key.pem"); +const ca = readFileSync("tests/fixtures/server/certs/ca.pem"); + +let serverLine: string | null = null; +let clientLine: string | null = null; +function both(): void { + if (serverLine === null || clientLine === null) return; + console.log(serverLine); + console.log(clientLine); +} + +const server = tls.createServer({ key, cert }, (socket) => { + serverLine = `server reused ${socket.isSessionReused()}`; + both(); + socket.end("hi"); +}); +server.listen(0, "127.0.0.1", () => { + const port = (server.address() as { port: number }).port; + const client = tls.connect({ port, host: "127.0.0.1", servername: "localhost", ca }, () => { + clientLine = `client reused ${client.isSessionReused()}`; + both(); + }); + console.log("before handshake", client.isSessionReused()); + client.on("data", (d: Buffer) => console.log("data", d.toString())); + client.on("close", () => server.close()); +}); diff --git a/tests/corpus/6417-object-assign-class-target.ts b/tests/corpus/6417-object-assign-class-target.ts new file mode 100644 index 000000000..4c2746693 --- /dev/null +++ b/tests/corpus/6417-object-assign-class-target.ts @@ -0,0 +1,36 @@ +// Object.assign with a class instance as the target and a plain record as +// the source: the record's fields land on the instance's own fields (also +// readonly ones, which only the checker guards), optional source fields +// only when present, and the call answers the instance itself. +class Settings { + retries = 1; + readonly name: string = "default"; + verbose = false; + label?: string; + describe(): string { + return `${this.name}:${this.retries}:${this.verbose}:${this.label ?? "-"}`; + } +} + +const s = new Settings(); +const same = Object.assign(s, { retries: 3, verbose: true }); +console.log(same === s, s.describe()); + +function patch(target: Settings, update: { label?: string }): Settings { + return Object.assign(target, update); +} +console.log(patch(s, { label: "x" }).describe()); +console.log(patch(s, {}).describe()); +console.log(patch(s, { label: undefined }).describe(), Object.assign(s, { retries: 0 }).describe()); + +class Point { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} +const p = new Point(1, 2); +Object.assign(p, { y: 5 }); +console.log(p.x, p.y, JSON.stringify(p), p instanceof Point); diff --git a/tests/corpus/6418-nullish-assign-index-record.ts b/tests/corpus/6418-nullish-assign-index-record.ts new file mode 100644 index 000000000..97918cd98 --- /dev/null +++ b/tests/corpus/6418-nullish-assign-index-record.ts @@ -0,0 +1,29 @@ +// Short-circuit assignment through a string-keyed index-signature record +// (`timings[phase] ??= value`): a missing key takes the right side, a +// present one keeps its value, and `||=` and `&&=` test truthiness. +class Telemetry { + private readonly timings: Record = {}; + private readonly labels: { [key: string]: string } = {}; + mark(phase: string, at: number): number { + return (this.timings[phase] ??= at); + } + label(key: string, value: string): string { + this.labels[key] ||= value; + this.labels[key] &&= this.labels[key].toUpperCase(); + return this.labels[key]; + } + dump(): string { + return JSON.stringify(this.timings) + " " + JSON.stringify(this.labels); + } +} + +const t = new Telemetry(); +console.log(t.mark("dns", 5), t.mark("tcp", 7), t.mark("dns", 9)); +console.log(t.label("a", "first"), t.label("a", "second"), t.label("", "empty")); +console.log(t.dump()); + +const counts: Record = { zero: 0 }; +const keys = ["zero", "one", "zero"]; +for (const k of keys) counts[k] ??= 10; +for (const k of keys) counts[k] ||= 20; +console.log(JSON.stringify(counts)); diff --git a/tests/corpus/6420-process-send-presence.ts b/tests/corpus/6420-process-send-presence.ts new file mode 100644 index 000000000..59773ab44 --- /dev/null +++ b/tests/corpus/6420-process-send-presence.ts @@ -0,0 +1,24 @@ +// process.send exists only with an IPC channel: presence checks in every +// truthiness position, typeof, and guarded calls. +const viaIpc = Boolean(process.send); +console.log("Boolean", viaIpc); +console.log("not", !process.send, !!process.disconnect); +console.log("typeof", typeof process.send, typeof process.disconnect); +console.log("ternary", process.send ? "ipc" : "stdout"); +if (process.send) { + process.send({ kind: "log", line: "never" }); +} else { + console.log("if: no channel"); +} +let polls = 0; +while (process.send && polls < 3) polls++; +console.log("while", polls); +process.send && process.send({ unused: true }); +const sink = !process.send || !process.disconnect ? "console" : "ipc"; +console.log("or", sink); +console.log("optional", process.send?.({ value: 1 })); +try { + process.send!({ value: 2 }); +} catch (e) { + console.log("direct", (e as Error).name, (e as Error).message); +} diff --git a/tests/corpus/6421-process-send-presence-fork/main.ts b/tests/corpus/6421-process-send-presence-fork/main.ts new file mode 100644 index 000000000..11a634672 --- /dev/null +++ b/tests/corpus/6421-process-send-presence-fork/main.ts @@ -0,0 +1,13 @@ +// process.send and process.disconnect exist in a forked child (an IPC +// channel) and not in the parent; the child's presence checks pick the +// channel and its guarded send reaches the parent. +import { fork } from "node:child_process"; + +console.log("parent", Boolean(process.send), typeof process.send, typeof process.disconnect); +const child = fork(new URL("./worker.ts", import.meta.url), [], { + stdio: ["ignore", "inherit", "inherit", "ipc"], +}); +child.on("message", (message: { sink: string; send: string; disconnect: string }) => { + console.log("child says", message.sink, message.send, message.disconnect); +}); +child.on("exit", (code) => console.log("child exit", code)); diff --git a/tests/corpus/6421-process-send-presence-fork/worker.ts b/tests/corpus/6421-process-send-presence-fork/worker.ts new file mode 100644 index 000000000..81e8288a7 --- /dev/null +++ b/tests/corpus/6421-process-send-presence-fork/worker.ts @@ -0,0 +1,6 @@ +const sink = process.send ? "ipc" : "stdout"; +if (process.send) { + process.send({ sink, send: typeof process.send, disconnect: typeof process.disconnect }); +} +if (!process.send) console.log("unreachable"); +process.disconnect(); diff --git a/tests/corpus/6422-process-exitcode-read.ts b/tests/corpus/6422-process-exitcode-read.ts new file mode 100644 index 000000000..c320b4a0d --- /dev/null +++ b/tests/corpus/6422-process-exitcode-read.ts @@ -0,0 +1,22 @@ +// @exit: 7 +// process.exitCode: unset reads undefined, assignments (including 0) read back, +// and process.exit(n) assigns the code before 'exit' listeners run. +console.log("initial", process.exitCode); +process.on("exit", (code) => { + console.log("exit listener", code, process.exitCode); +}); +process.exitCode = 0; +console.log("after 0", process.exitCode); +process.exitCode = 4; +const seen = process.exitCode; +console.log("after 4", seen, typeof process.exitCode); +process.exitCode = process.exitCode ?? 9; +console.log("kept", process.exitCode); +try { + process.exitCode = 1.5; +} catch (e) { + console.log("rejected", (e as Error).message, process.exitCode); +} +setTimeout(() => { + process.exit(7); +}, 1); diff --git a/tests/corpus/6423-process-exitcode-default.ts b/tests/corpus/6423-process-exitcode-default.ts new file mode 100644 index 000000000..5dbc72294 --- /dev/null +++ b/tests/corpus/6423-process-exitcode-default.ts @@ -0,0 +1,10 @@ +// @exit: 2 +// The exit status follows process.exitCode when the program ends normally. +function settle(failed: boolean): void { + if (failed) process.exitCode = process.exitCode ?? 2; +} +console.log("start", process.exitCode === undefined); +settle(true); +settle(true); +console.log("code", process.exitCode); +process.on("exit", () => console.log("exiting with", process.exitCode)); diff --git a/tests/corpus/6424-process-exit-keeps-exitcode.ts b/tests/corpus/6424-process-exit-keeps-exitcode.ts new file mode 100644 index 000000000..e5a0b86d2 --- /dev/null +++ b/tests/corpus/6424-process-exit-keeps-exitcode.ts @@ -0,0 +1,5 @@ +// @exit: 5 +// process.exit() without an argument keeps process.exitCode. +process.exitCode = 5; +process.on("exit", (code) => console.log("exit", code, process.exitCode)); +process.exit(); diff --git a/tests/corpus/6425-stream-hook-wider-chunk.ts b/tests/corpus/6425-stream-hook-wider-chunk.ts new file mode 100644 index 000000000..be46b18be --- /dev/null +++ b/tests/corpus/6425-stream-hook-wider-chunk.ts @@ -0,0 +1,46 @@ +// Stream hooks may declare a supertype of what Node passes: chunks typed +// Buffer | string still arrive as Buffers. +import { Transform, Writable } from "node:stream"; + +class LineSink extends Writable { + private rest = ""; + readonly lines: string[] = []; + _write(chunk: Buffer | string, encoding: BufferEncoding, cb: (err?: Error | null) => void): void { + console.log("chunk", typeof chunk, Buffer.isBuffer(chunk), encoding); + const parts = (this.rest + chunk.toString()).split("\n"); + this.rest = parts.pop() ?? ""; + this.lines.push(...parts); + cb(); + } + _final(cb: (err?: Error | null) => void): void { + if (this.rest !== "") this.lines.push(this.rest); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: string | Buffer, _enc: string, cb: (err?: Error | null, data?: string) => void): void { + cb(null, chunk.toString().toUpperCase()); + } +} + +class Failing extends Writable { + _write(chunk: Uint8Array | string, _enc: BufferEncoding, cb: (err?: Error | null) => void): void { + cb(new Error(`refused ${chunk.toString().length} bytes`)); + } +} + +const sink = new LineSink(); +sink.write("a\nb"); +sink.write(Buffer.from("c\nd")); +sink.end("e"); +sink.on("finish", () => console.log("lines", JSON.stringify(sink.lines))); + +const up = new Upper(); +up.on("data", (d: Buffer) => console.log("upper", d.toString())); +up.write("hello "); +up.end("world"); + +const bad = new Failing(); +bad.on("error", (e) => console.log("error", e.message)); +bad.write("xyz"); diff --git a/tests/corpus/6426-promise-race-void-entry.ts b/tests/corpus/6426-promise-race-void-entry.ts new file mode 100644 index 000000000..b33f3b664 --- /dev/null +++ b/tests/corpus/6426-promise-race-void-entry.ts @@ -0,0 +1,31 @@ +// Promise.race over Promise and Promise: the void +// entry fulfills the race with undefined. +interface Msg { id: string } +function delay(ms: number, value: T): Promise { + return new Promise((resolve) => setTimeout(() => resolve(value), ms)); +} +function poll(ms: number, id: string | null): Promise { + return delay(ms, id === null ? undefined : { id }); +} +function exited(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +async function main(): Promise { + const a = await Promise.race([poll(5, "m1"), exited(50)]); + console.log("message first:", a ? a.id : "none"); + const b = await Promise.race([poll(50, "m2"), exited(5)]); + console.log("exit first:", b === undefined, b ? b.id : "none"); + const c = await Promise.race([exited(5), poll(1, null)]); + console.log("empty poll first:", c); + const settled = Promise.resolve(); + const d = await Promise.race([settled, poll(0, "m3")]); + console.log("settled void entry wins:", d); + const n = await Promise.race([delay(20, 42), exited(1)]); + console.log("number or void:", n); + try { + await Promise.race([poll(20, "late"), Promise.reject(new Error("boom"))]); + } catch (e) { + console.log("rejected:", (e as Error).message); + } +} +void main(); diff --git a/tests/corpus/6427-http-request-options-type.ts b/tests/corpus/6427-http-request-options-type.ts new file mode 100644 index 000000000..17cb2d855 --- /dev/null +++ b/tests/corpus/6427-http-request-options-type.ts @@ -0,0 +1,10 @@ +// http.RequestOptions as a declared type: a checked-dynamic option record. +import type http from "node:http"; +const socket = process.env.NO_SUCH_SOCKET_VAR; +const base: http.RequestOptions = socket ? { socketPath: socket } : { host: "127.0.0.1", port: 8080 }; +const full: http.RequestOptions = { ...base, path: "/x", method: "POST", headers: { "x-a": "1" } }; +console.log(JSON.stringify(base), JSON.stringify(full)); +function describe(o: http.RequestOptions): string { + return `${String(o.host)}:${String(o.port)}${String(o.path ?? "/")}`; +} +console.log(describe(full), describe({ host: "h" })); diff --git a/tests/corpus/6428-promisify-callback-functions.ts b/tests/corpus/6428-promisify-callback-functions.ts new file mode 100644 index 000000000..b899dd292 --- /dev/null +++ b/tests/corpus/6428-promisify-callback-functions.ts @@ -0,0 +1,80 @@ +// util.promisify over typed callback-only functions (bound methods, one with +// an optional callback, a function declaration, an inline arrow, a function +// that returns a value): the error argument rejects, the value fulfills, the +// return value is ignored, a synchronous throw rejects, and settlement is +// async. +import { promisify } from "node:util"; + +class Connection { + private closed = false; + private readonly name: string; + constructor(name: string) { + this.name = name; + } + close(callback: (error?: Error) => void): void { + setTimeout(() => { + if (this.closed) { + callback(new Error(`${this.name} already closed`)); + return; + } + this.closed = true; + callback(); + }, 1); + } + flush(callback?: (error?: Error) => void): void { + setImmediate(() => callback?.(this.closed ? new Error("flushed after close") : undefined)); + } + stats(callback: (error: Error | null, open: number) => void): void { + setImmediate(() => (this.closed ? callback(new RangeError("closed"), 0) : callback(null, 3))); + } +} + +function boom(callback: (error: Error | null) => void): void { + if (callback !== undefined) throw new TypeError("synchronous failure"); +} + +let pending = 0; +function size(callback: (error: Error | null, size: number) => void): number { + setImmediate(() => callback(null, 4)); + return ++pending; +} + +async function main(): Promise { + const c = new Connection("db"); + const stats = promisify(c.stats.bind(c)); + console.log("open", await stats()); + await promisify(c.flush.bind(c))(); + console.log("flushed"); + const closeAsync = promisify(c.close.bind(c)); + await closeAsync(); + console.log("closed once"); + try { + await closeAsync(); + } catch (e) { + console.log("second close:", (e as Error).message); + } + try { + await stats(); + } catch (e) { + console.log("stats:", (e as Error).name, (e as Error).message); + } + await promisify(c.flush.bind(c))().catch((e) => console.log("flush:", (e as Error).message)); + try { + await promisify(boom)(); + } catch (e) { + console.log("sync throw:", (e as Error).name, (e as Error).message); + } + const order: string[] = []; + const tick = promisify((done: (error?: Error) => void) => { + order.push("called"); + done(); + }); + const p = tick().then(() => order.push("settled")); + order.push("returned"); + await p; + console.log(order.join(", ")); + await promisify(new Connection("inline").close.bind(new Connection("inline")))(); + console.log("inline done"); + console.log("size", await promisify(size)(), pending); +} +void main(); diff --git a/tests/corpus/6429-esm-js-dirname.mjs b/tests/corpus/6429-esm-js-dirname.mjs new file mode 100644 index 000000000..2ecc4088a --- /dev/null +++ b/tests/corpus/6429-esm-js-dirname.mjs @@ -0,0 +1,15 @@ +// A JavaScript ES module has no __dirname or __filename (Node throws a +// ReferenceError where they are read); import.meta carries the location. +// TypeScript files keep the CommonJS globals as the source path. +try { + console.log(__dirname); +} catch (error) { + console.log("no __dirname:", error instanceof Error); +} +console.log(import.meta.filename.endsWith("6429-esm-js-dirname.mjs")); +console.log(typeof __dirname, typeof __filename); +try { + console.log(__filename.length); +} catch (error) { + console.log(error.name, error.message); +} diff --git a/tests/corpus/6430-npm-constructor-function-class/main.ts b/tests/corpus/6430-npm-constructor-function-class/main.ts new file mode 100644 index 000000000..6098c1e19 --- /dev/null +++ b/tests/corpus/6430-npm-constructor-function-class/main.ts @@ -0,0 +1,19 @@ +// Constructing a CommonJS constructor-function "class" imported by a named +// member alias (`exports = module.exports = Client; exports.StatsD = +// Client`) through a re-exporting package index: prototype methods, a child +// client linked with util.inherits, and a callback-style close. +import { StatsD } from "metrics-client"; + +const client = new StatsD({ prefix: "app." }); +client.onSent((line: string) => console.log("event", line)); +console.log(client.increment("boot")); +console.log(client.gauge("heap", 42)); +console.log(typeof client.increment, typeof client.onSent); +const child = client.childClient({ prefix: "db." }); +console.log(child.increment("query", 3)); +console.log(child.gauge("pool", 1), child.sent.length, client.sent.length); +const bare = new StatsD(); +console.log(bare.increment("x"), bare.sent.length); +client.close((error: Error | null, count: number) => { + console.log("closed", error, count); +}); diff --git a/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/index.d.ts b/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/index.d.ts new file mode 100644 index 000000000..c53bdd8ae --- /dev/null +++ b/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/index.d.ts @@ -0,0 +1,14 @@ +export interface ClientOptions { + prefix?: string; + suffix?: string; +} +export declare class StatsD { + constructor(options?: ClientOptions); + prefix: string; + sent: string[]; + onSent(listener: (line: string) => void): this; + increment(stat: string, value?: number): string; + gauge(stat: string, value: number): string; + close(callback: (error: Error | null, count: number) => void): void; + childClient(options?: ClientOptions): StatsD; +} diff --git a/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/index.js b/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/index.js new file mode 100644 index 000000000..eb7384718 --- /dev/null +++ b/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/index.js @@ -0,0 +1 @@ +module.exports = require("./lib/client"); diff --git a/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/lib/client.js b/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/lib/client.js new file mode 100644 index 000000000..7dd5968c9 --- /dev/null +++ b/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/lib/client.js @@ -0,0 +1,50 @@ +"use strict"; +// A constructor-function "class" in the style of statsd clients: a function +// expression, prototype methods, a child client linked with util.inherits, +// and the whole export plus a named alias. +const util = require("util"); + +const Client = function (options) { + const opts = options || {}; + this.prefix = opts.prefix || ""; + this.suffix = opts.suffix || ""; + this.sent = []; + this.listeners = []; +}; + +Client.prototype.send = function (stat, value, type) { + const line = this.prefix + stat + this.suffix + ":" + value + "|" + type; + this.sent.push(line); + for (const listener of this.listeners) listener(line); + return line; +}; +Client.prototype.onSent = function (listener) { + this.listeners.push(listener); + return this; +}; +Client.prototype.increment = function (stat, value) { + return this.send(stat, value === undefined ? 1 : value, "c"); +}; +Client.prototype.gauge = function (stat, value) { + return this.send(stat, value, "g"); +}; +Client.prototype.close = function (callback) { + const count = this.sent.length; + setImmediate(() => callback(null, count)); +}; +Client.prototype.childClient = function (options) { + return new ChildClient(this, options); +}; + +const ChildClient = function (parent, options) { + Client.call(this, { + prefix: parent.prefix + ((options && options.prefix) || ""), + suffix: parent.suffix, + }); + this.parent = parent; +}; +util.inherits(ChildClient, Client); + +exports = module.exports = Client; +exports.StatsD = Client; +exports.ChildClient = ChildClient; diff --git a/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/package.json b/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/package.json new file mode 100644 index 000000000..af4ede9ee --- /dev/null +++ b/tests/corpus/6430-npm-constructor-function-class/node_modules/metrics-client/package.json @@ -0,0 +1 @@ +{ "name": "metrics-client", "version": "1.0.0", "main": "index.js", "types": "index.d.ts" } diff --git a/tests/corpus/6431-cjs-exports-rebinding/extended.cjs b/tests/corpus/6431-cjs-exports-rebinding/extended.cjs new file mode 100644 index 000000000..d07438ee9 --- /dev/null +++ b/tests/corpus/6431-cjs-exports-rebinding/extended.cjs @@ -0,0 +1,3 @@ +"use strict"; +module.exports = require("./lib.cjs"); +module.exports.extra = "added"; diff --git a/tests/corpus/6431-cjs-exports-rebinding/lib.cjs b/tests/corpus/6431-cjs-exports-rebinding/lib.cjs new file mode 100644 index 000000000..52b6b8c9b --- /dev/null +++ b/tests/corpus/6431-cjs-exports-rebinding/lib.cjs @@ -0,0 +1,10 @@ +"use strict"; +function format(value) { + return "[" + value + "]"; +} +function shout(value) { + return String(value).toUpperCase(); +} +exports = module.exports = format; +exports.shout = shout; +exports.version = "1.2.3"; diff --git a/tests/corpus/6431-cjs-exports-rebinding/main.cjs b/tests/corpus/6431-cjs-exports-rebinding/main.cjs new file mode 100644 index 000000000..cfeec742b --- /dev/null +++ b/tests/corpus/6431-cjs-exports-rebinding/main.cjs @@ -0,0 +1,9 @@ +// `exports = module.exports = value` rebinds the wrapper's exports binding +// too, so members attached through `exports` afterwards are exported. +const format = require("./lib.cjs"); +const { shout, version } = require("./lib.cjs"); +console.log(format("a"), shout("b"), version); +console.log(typeof format, typeof shout); +// A module that re-exports another module's exports and adds a member. +const { extra, shout: loud } = require("./extended.cjs"); +console.log(extra, loud("c")); diff --git a/tests/corpus/6440-http-socket-path.ts b/tests/corpus/6440-http-socket-path.ts new file mode 100644 index 000000000..f782cf2a4 --- /dev/null +++ b/tests/corpus/6440-http-socket-path.ts @@ -0,0 +1,109 @@ +// HTTP over a Unix domain socket: http.createServer().listen(path) and +// http.request / http.get with a socketPath option, from an object literal, +// from a RequestOptions variable, and from a literal that spreads one. The +// Host header defaults to localhost. A missing socket fails the request +// with ENOENT, a second listen on a bound path fails with EADDRINUSE, and +// closing the server removes the socket file. +import * as http from "node:http"; +import { existsSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const sock = join(tmpdir(), `scriptc-http-${process.pid}.sock`); +const shown = (text: string): string => text.split(sock).join(""); + +const server = http.createServer((req, res) => { + let body = ""; + req.on("data", (chunk: Buffer) => { + body += chunk.toString(); + }); + req.on("end", () => { + res.setHeader("content-type", "text/plain"); + res.end(`${req.method} ${req.url} host=${req.headers.host} x=${req.headers["x-n"]} body=${body}`); + }); +}); + +function collect(res: http.IncomingMessage, label: string, done: () => void): void { + let text = ""; + res.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + res.on("end", () => { + console.log(label, res.statusCode, res.headers["content-type"], text); + done(); + }); +} + +function literal(): Promise { + return new Promise((resolve) => { + const req = http.request( + { socketPath: sock, path: "/literal", method: "POST", headers: { "x-n": "1" } }, + (res) => collect(res, "literal", resolve), + ); + req.end("one"); + }); +} + +const base: http.RequestOptions = { socketPath: sock }; + +function fromVariable(): Promise { + return new Promise((resolve) => { + const headers: Record = { "x-n": 2, "content-type": "text/plain" }; + const options: http.RequestOptions = { ...base, path: "/variable", method: "PUT", headers, timeout: 5000 }; + const req = http.request(options, (res) => collect(res, "variable", resolve)); + req.write("two"); + req.end(); + }); +} + +function spread(): Promise { + return new Promise((resolve) => { + const req = http.request({ ...base, path: "/spread", method: "POST", headers: { "x-n": "3" } }, (res) => + collect(res, "spread", resolve), + ); + req.end("three"); + }); +} + +function get(): Promise { + return new Promise((resolve) => { + const options: http.RequestOptions = { socketPath: sock, path: "/get?q=4" }; + http.get(options, (res) => collect(res, "get", resolve)); + }); +} + +function missing(): Promise { + return new Promise((resolve) => { + const options: http.RequestOptions = { socketPath: `${sock}.missing`, path: "/" }; + const req = http.request(options, () => console.log("unexpected response")); + req.on("error", (err: NodeJS.ErrnoException) => { + console.log("missing", err.code, shown(err.message)); + resolve(); + }); + req.end(); + }); +} + +function busy(): Promise { + return new Promise((resolve) => { + const other = http.createServer(); + other.on("error", (err: NodeJS.ErrnoException) => { + console.log("busy", err.code, shown(err.message)); + resolve(); + }); + other.listen(sock); + }); +} + +server.listen(sock, async () => { + console.log("listening", existsSync(sock)); + await literal(); + await fromVariable(); + await spread(); + await get(); + await missing(); + await busy(); + server.close(() => { + console.log("closed", existsSync(sock)); + }); +}); diff --git a/tests/corpus/6441-http-request-options-record.ts b/tests/corpus/6441-http-request-options-record.ts new file mode 100644 index 000000000..89a9abc29 --- /dev/null +++ b/tests/corpus/6441-http-request-options-record.ts @@ -0,0 +1,79 @@ +// http.request options read at run time: a RequestOptions value chosen by +// a runtime condition (a TCP endpoint with a string port, or a socket +// path), spread into an options object with a timeout and an optional +// signal; agent: false; an aborted signal; a flat [name, value, ...] +// headers array; and Node's validation errors for the method, port, host, +// timeout, path, protocol and agent options and for flat header names and +// values. +import * as http from "node:http"; + +const server = http.createServer((req, res) => { + const host = (req.headers.host ?? "").replace(/:\d+$/, ":"); + res.end(`${req.method} ${req.url} host=${host} connection=${req.headers.connection} n=${req.headers["x-n"]}`); +}); + +function send(label: string, options: http.RequestOptions): Promise { + return new Promise((resolve) => { + const req = http.request(options, (res) => { + let text = ""; + res.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + res.on("end", () => { + console.log(label, res.statusCode, text); + resolve(); + }); + }); + req.on("error", (err: NodeJS.ErrnoException) => { + console.log(label, "error", err.name, err.code, err.message); + resolve(); + }); + req.end(); + }); +} + +function invalid(label: string, options: http.RequestOptions): void { + try { + const req = http.request(options); + req.on("error", () => {}); + req.destroy(); + console.log(label, "accepted"); + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log(label, e.name, e.code, e.message); + } +} + +server.listen(0, "127.0.0.1", async () => { + const port = (server.address() as { port: number }).port; + const endpoint = process.env.NO_SUCH_ENDPOINT_VAR ?? `http://127.0.0.1:${port}`; + const url = new URL(endpoint); + const location: http.RequestOptions = process.env.NO_SUCH_SOCKET_VAR + ? { socketPath: process.env.NO_SUCH_SOCKET_VAR } + : { port: url.port, host: url.hostname }; + const headers: Record = { "x-n": 1 }; + const controller: AbortController | undefined = undefined; + const options = { ...location, path: "/rpc", method: "post", headers, timeout: 2000, signal: controller }; + // The flat [name, value, ...] headers form; @types/node types headers as an object. + const flat = (path: string, list: unknown[]): http.RequestOptions => + ({ ...location, path, headers: list }) as unknown as http.RequestOptions; + await send("spread", options); + await send("agent false", { ...location, path: "/close", agent: false }); + const aborted = new AbortController(); + aborted.abort(); + await send("aborted", { ...location, path: "/never", signal: aborted.signal }); + await send("flat headers", flat("/flat", ["host", "flat.test", "x-n", 2])); + invalid("method", { ...location, method: "g t" }); + invalid("port", { host: "127.0.0.1", port: "http" }); + invalid("host", { hostname: 5 } as unknown as http.RequestOptions); + invalid("timeout", { ...location, timeout: -1 }); + invalid("path", { ...location, path: "/a b" }); + invalid("protocol", { ...location, protocol: "https:" }); + invalid("agent", { ...location, agent: 5 } as unknown as http.RequestOptions); + invalid("flat name", flat("/", ["x n", "1"])); + invalid("flat number name", flat("/", [5, "1"])); + invalid("flat crlf value", flat("/", ["x-a", "ok\r\nx-b: injected"])); + invalid("flat crlf array value", flat("/", ["x-a", ["ok", "b\r\nx-c: injected"]])); + invalid("flat odd length", flat("/", ["x-a"])); + server.close(); +}); diff --git a/tests/corpus/6442-https-request-options-record.ts b/tests/corpus/6442-https-request-options-record.ts new file mode 100644 index 000000000..4148e015b --- /dev/null +++ b/tests/corpus/6442-https-request-options-record.ts @@ -0,0 +1,61 @@ +// https.request with options read at run time: a RequestOptions value +// spread with the CA, over TCP and over a Unix socket, an unverifiable +// certificate with and without rejectUnauthorized, and the protocol check. +import * as https from "node:https"; +import { readFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const ca = readFileSync("tests/fixtures/server/certs/ca.pem"); +const sock = join(tmpdir(), `scriptc-https-${process.pid}.sock`); + +const server = https.createServer( + { + cert: readFileSync("tests/fixtures/server/certs/localhost.pem"), + key: readFileSync("tests/fixtures/server/certs/localhost-key.pem"), + }, + (req, res) => res.end(`${req.method} ${req.url} host=${(req.headers.host ?? "").replace(/:\d+$/, ":")}`), +); +const unix = https.createServer( + { + cert: readFileSync("tests/fixtures/server/certs/localhost.pem"), + key: readFileSync("tests/fixtures/server/certs/localhost-key.pem"), + }, + (req, res) => res.end(`unix ${req.method} ${req.url} host=${req.headers.host}`), +); + +function send(label: string, options: https.RequestOptions): Promise { + return new Promise((resolve) => { + const req = https.request(options, (res) => { + let body = ""; + res.on("data", (c: Buffer) => (body += c.toString())); + res.on("end", () => { + console.log(label, res.statusCode, body); + resolve(); + }); + }); + req.on("error", (err: NodeJS.ErrnoException) => { + console.log(label, "error", err.code); + resolve(); + }); + req.end(); + }); +} + +server.listen(0, () => { + const port = (server.address() as { port: number }).port; + unix.listen(sock, async () => { + const tcp: https.RequestOptions = { host: "localhost", port }; + await send("tcp", { ...tcp, path: "/a", ca, agent: false }); + await send("unix", { socketPath: sock, path: "/b", ca, agent: false }); + await send("unverified", { ...tcp, path: "/c", agent: false }); + await send("unchecked", { ...tcp, path: "/d", rejectUnauthorized: false, agent: false }); + try { + https.request({ ...tcp, protocol: "http:" }); + } catch (err) { + console.log("protocol", (err as NodeJS.ErrnoException).code, (err as Error).message); + } + server.close(); + unix.close(); + }); +}); diff --git a/tests/corpus/6443-net-lookup-http-close-text.ts b/tests/corpus/6443-net-lookup-http-close-text.ts new file mode 100644 index 000000000..ba445b117 --- /dev/null +++ b/tests/corpus/6443-net-lookup-http-close-text.ts @@ -0,0 +1,75 @@ +// A socket's 'lookup' event for a host name (none for an IP address), +// string 'data' listeners after setEncoding on both sides of an HTTP +// exchange, res.end() of a string or undefined, and the close callback of +// a server that never listened. +import * as http from "node:http"; +import * as net from "node:net"; + +function lookups(): Promise { + return new Promise((resolve) => { + const tcp = net.createServer((s) => s.end("hi")); + tcp.listen(0, "127.0.0.1", () => { + const port = (tcp.address() as net.AddressInfo).port; + const named = net.connect({ host: "localhost", port }); + let reported = false; + named.on("lookup", (err: Error | null, address: string, family: number, host: string) => { + // macOS also answers ::1; report the IPv4 answer the dial uses. + if (family === 4 && !reported) { + reported = true; + console.log("lookup", err, address, family, host); + } + }); + named.once("connect", () => console.log("connect after lookup", reported)); + named.on("data", () => {}); + named.on("close", () => { + const direct = net.connect({ host: "127.0.0.1", port }); + // A 'lookup' listener that never runs and holds its socket: closing + // the socket drops it. + direct.once("lookup", () => console.log("unexpected lookup", direct.remotePort)); + direct.once("connect", () => console.log("connect without lookup")); + direct.on("data", () => {}); + direct.on("close", () => tcp.close(() => resolve())); + }); + }); + }); +} + +const server = http.createServer((req, res) => { + req.setEncoding("utf8"); + const chunks: string[] = []; + req.on("data", (chunk: string) => chunks.push(chunk)); + req.on("end", () => { + const body = chunks.join(""); + res.writeHead(200, { "content-type": "text/plain" }); + const payload = body ? body.toUpperCase() : undefined; + res.end(payload); + }); +}); + +function post(port: number, body: string): Promise { + return new Promise((resolve) => { + const req = http.request({ host: "127.0.0.1", port, method: "POST", path: "/" }, (res) => { + res.setEncoding("utf8"); + let text = ""; + res.on("data", (part: string) => { + text += part; + }); + res.on("end", () => { + console.log("response", JSON.stringify(body), "->", JSON.stringify(text)); + resolve(); + }); + }); + req.end(body); + }); +} + +lookups().then(() => { + server.listen(0, "127.0.0.1", async () => { + const port = (server.address() as net.AddressInfo).port; + await post(port, "hello"); + await post(port, ""); + server.close(() => console.log("closed")); + const idle = http.createServer(); + idle.close(() => console.log("never listened closed")); + }); +}); diff --git a/tests/corpus/6445-rest-function-fixed-slot.ts b/tests/corpus/6445-rest-function-fixed-slot.ts new file mode 100644 index 000000000..e78e83836 --- /dev/null +++ b/tests/corpus/6445-rest-function-fixed-slot.ts @@ -0,0 +1,49 @@ +// A rest-parameter function in a slot with fixed parameters: the slot's +// arguments arrive in the rest array, as in a JavaScript call. +function log(...messages: string[]): void { + console.log(`log(${messages.length}):`, messages.join(" | ")); +} +function tagged(tag: string, ...values: number[]): number { + console.log(tag, values.join(",")); + return values.length; +} + +class Line { + private readonly sink: (m: string) => void; + constructor(sink: (m: string) => void) { + this.sink = sink; + } + emit(m: string): void { + this.sink(m); + } +} + +new Line(log).emit("one"); +const pair: (a: string, b: string) => void = log; +pair("x", "y"); +const none: () => void = log; +none(); +const count: (tag: string, a: number, b: number) => number = tagged; +console.log("count", count("t", 1, 2)); +["a", "b"].forEach((m) => new Line(log).emit(m)); + +// Nullable and optional slots: the value takes the slot's function arm. +class OptionalLine { + private readonly sink: ((m: string) => void) | null; + constructor(sink: ((m: string) => void) | null) { + this.sink = sink; + } + emit(m: string): void { + if (this.sink) this.sink(m); + else console.log("no sink:", m); + } +} +new OptionalLine(log).emit("nullable"); +new OptionalLine(null).emit("dropped"); +function relay(m: string, sink?: (m: string) => void): void { + sink?.(m); +} +relay("optional", log); +relay("absent"); +const maybe: ((a: string, b: string) => void) | undefined = log; +maybe?.("p", "q"); diff --git a/tests/corpus/6446-rest-function-nested-declaration.ts b/tests/corpus/6446-rest-function-nested-declaration.ts new file mode 100644 index 000000000..1b2d7aa17 --- /dev/null +++ b/tests/corpus/6446-rest-function-nested-declaration.ts @@ -0,0 +1,26 @@ +// Nested rest-parameter function declarations passed to a fixed slot. +class Line { + private readonly sink: (m: string) => void; + constructor(sink: (m: string) => void) { + this.sink = sink; + } + emit(m: string): void { + this.sink(m); + } +} +interface Logger { + line: () => Line; + log: (...messages: string[]) => void; +} +function makeLogger(prefix: string): Logger { + return { + line: () => new Line(log), + log, + }; + function log(...messages: string[]) { + console.log(prefix, messages.join(" ")); + } +} +const logger = makeLogger(">"); +logger.log("a", "b"); +logger.line().emit("c"); diff --git a/tests/corpus/6450-builtin-base-class-values.ts b/tests/corpus/6450-builtin-base-class-values.ts new file mode 100644 index 000000000..0dafa5d82 --- /dev/null +++ b/tests/corpus/6450-builtin-base-class-values.ts @@ -0,0 +1,63 @@ +// Classes extending Node builtin bases (Writable, Transform, EventEmitter) +// used as values: through a variable, a generic factory, an object table and +// an array, with instanceof against the class and its base. +import { EventEmitter } from "node:events"; +import { Readable, Transform, Writable } from "node:stream"; + +class Sink extends Writable { + readonly lines: string[] = []; + private readonly tag: string; + constructor(tag: string) { + super(); + this.tag = tag; + } + _write(chunk: Buffer, _enc: BufferEncoding, cb: (err?: Error | null) => void): void { + this.lines.push(this.tag + chunk.toString()); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: Buffer, _enc: BufferEncoding, cb: (err?: Error | null, data?: string) => void): void { + cb(null, chunk.toString().toUpperCase()); + } +} + +class Ticker extends EventEmitter { + count = 0; + tick(): void { + this.count++; + this.emit("tick", this.count); + } +} + +class Plain extends EventEmitter {} + +function build(ctor: new () => T): T { + return new ctor(); +} + +const SinkClass = Sink; +const s = new SinkClass("> "); +s.write("a"); +s.end("b", () => console.log("sink", JSON.stringify(s.lines), s instanceof Sink, s instanceof Writable)); + +const t = build(Ticker); +t.on("tick", (n: number) => console.log("tick", n)); +t.tick(); +t.tick(); +console.log("ticker", t instanceof Ticker, t instanceof EventEmitter, t.count); + +const p = build(Plain); +p.on("x", () => console.log("plain x")); +p.emit("x"); + +const registry = { upper: Upper }; +const u = new registry.upper(); +u.on("data", (d: Buffer) => console.log("upper", d.toString())); +u.end("hello"); + +const kinds = [Ticker, Ticker]; +console.log("names", kinds.map((k) => k.name).join(","), typeof Sink, Sink.name); +const r = Readable.from(["x"]); +console.log("readable", r instanceof Readable); diff --git a/tests/corpus/6450-promise-race-never-entry.ts b/tests/corpus/6450-promise-race-never-entry.ts new file mode 100644 index 000000000..412c5f817 --- /dev/null +++ b/tests/corpus/6450-promise-race-never-entry.ts @@ -0,0 +1,22 @@ +// Promise.race with a Promise deadline entry under a typed result: +// the never entry can only reject, so it races with any result type. A +// read that settles first wins; a deadline that fires first rejects. +class Deadline extends Error {} +async function readWithDeadline(read: () => Promise<{ done: boolean; value?: Uint8Array }>, ms: number): Promise<{ done: boolean; value?: Uint8Array }> { + let timer: ReturnType | undefined; + try { + return await Promise.race([ + read(), + new Promise((_, reject) => { + timer = setTimeout(() => reject(new Deadline()), ms); + }), + ]); + } catch (error) { + if (error instanceof Deadline) return { done: true }; + throw error; + } finally { + if (timer !== undefined) clearTimeout(timer); + } +} +readWithDeadline(async () => ({ done: false, value: new Uint8Array([1, 2]) }), 1000).then((r) => console.log(r.done, r.value?.length)); +readWithDeadline(() => new Promise((res) => setTimeout(() => res({ done: false }), 200)), 20).then((r) => console.log("deadline", r.done)); diff --git a/tests/corpus/6451-array-length-fill-spread.ts b/tests/corpus/6451-array-length-fill-spread.ts new file mode 100644 index 000000000..03b80c300 --- /dev/null +++ b/tests/corpus/6451-array-length-fill-spread.ts @@ -0,0 +1,15 @@ +// Array(n).fill(v): the checker types the length-constructed array as +// any[], but fill gives every element v's type, so the array (and an +// array literal spreading it) holds strings. +function expand(head: string, tail: string): number[] | null { + const headParts = head ? head.split(":") : []; + const tailParts = tail ? tail.split(":") : []; + const missing = 8 - headParts.length - tailParts.length; + if (missing < 0) return null; + const all = [...headParts, ...Array(missing).fill("0"), ...tailParts]; + if (all.length !== 8) return null; + return all.map((part) => Number.parseInt(part || "0", 16)); +} +console.log(JSON.stringify(expand("2001:db8", "1")), JSON.stringify(expand("fe80", ""))); +const pad: string[] = Array(3).fill("-"); +console.log(pad.join(""), [..."ab", ...new Array(2).fill("c")].join("")); diff --git a/tests/corpus/6451-builtin-base-class-expressions.cjs b/tests/corpus/6451-builtin-base-class-expressions.cjs new file mode 100644 index 000000000..6d9241b97 --- /dev/null +++ b/tests/corpus/6451-builtin-base-class-expressions.cjs @@ -0,0 +1,46 @@ +// Class expressions extending Node builtin bases (a bundler's output shape). +"use strict"; +const { Readable, Transform } = require("stream"); +const { EventEmitter } = require("events"); + +const Wrap = class extends Readable { + constructor(parts) { + super(); + this.parts = parts; + } + _read() { + const next = this.parts.shift(); + this.push(next === undefined ? null : next); + } +}; +const Bus = class extends EventEmitter {}; +const Lower = class extends Transform { + _transform(chunk, _enc, cb) { + cb(null, chunk.toString().toLowerCase()); + } +}; + +const r = new Wrap(["a", "b"]); +const chunks = []; +r.on("data", (c) => chunks.push(c.toString())); +r.on("end", () => console.log("wrap", chunks.join("+"), r instanceof Readable)); +const bus = new Bus(); +bus.on("ping", (n) => console.log("ping", n)); +bus.emit("ping", 1); +const l = new Lower(); +l.on("data", (d) => console.log("lower", d.toString())); +l.end("HeLLo"); +console.log(Wrap.name, Bus.name, typeof Lower); +{ + // A class declared in a block is collected like a class expression. + class Once extends Readable { + _read() { + this.push(null); + } + } + function onReadable() { + console.log("block", stream.read() === null); + } + const stream = new Once(); + stream.once("readable", onReadable); +} diff --git a/tests/corpus/6453-constructor-function-emitter.cjs b/tests/corpus/6453-constructor-function-emitter.cjs new file mode 100644 index 000000000..d07453e14 --- /dev/null +++ b/tests/corpus/6453-constructor-function-emitter.cjs @@ -0,0 +1,114 @@ +// Constructor functions that inherit from EventEmitter the pre-class way: +// util.inherits and EventEmitter.call(this), the call-without-new guard, +// prototype methods that emit, and the listener API over the instance. +"use strict"; +const util = require("util"); +const { EventEmitter } = require("events"); + +util.inherits(Bus, EventEmitter); +function Bus(name) { + if (!(this instanceof Bus)) return new Bus(name); + EventEmitter.call(this); + this.name = name; + this.entries = []; +} +Bus.prototype.add = function (entry) { + this.entries.push(entry); + this.emit("added", entry, this.entries.length); + return this; +}; + +// A second level: Sub inherits Bus, and Bus's constructor runs through call. +function Sub(name) { + Bus.call(this, name); + this.sub = true; +} +util.inherits(Sub, Bus); +Sub.prototype.add = function (entry) { + return Bus.prototype.add.call(this, entry.toUpperCase()); +}; + +// Object.setPrototypeOf instead of util.inherits. +function Legacy() { + EventEmitter.call(this); +} +Object.setPrototypeOf(Legacy.prototype, EventEmitter.prototype); + +function main() { + const bus = Bus("main"); + console.log(bus instanceof Bus, bus instanceof EventEmitter, Bus.super_ === EventEmitter); + console.log(Object.keys(bus).join(","), bus._eventsCount, bus._maxListeners); + bus.on("added", function (entry, count) { + console.log("added", entry, count, this === bus); + }); + bus.once("added", (entry) => console.log("first only", entry)); + bus.prependListener("added", () => console.log("prepended")); + bus.prependOnceListener("added", () => console.log("prepended once")); + bus.add("a").add("b"); + console.log(bus.listenerCount("added"), bus.eventNames(), bus.getMaxListeners()); + + const handler = () => console.log("never"); + bus.on("x", handler); + bus.on("x", () => console.log("kept")); + bus.off("x", handler); + console.log(bus.emit("x"), bus.emit("nothing"), bus.listeners("x").length, bus.rawListeners("added").length); + + try { + bus.emit("error", new Error("boom")); + } catch (err) { + console.log("caught", err.message); + } + try { + bus.emit("error", "plain"); + } catch (err) { + console.log("caught", err.code, err.message); + } + bus.on("error", (err) => console.log("handled", err.message)); + bus.emit("error", new Error("quiet")); + + bus.on("newListener", (event, listener) => console.log("newListener", event, typeof listener)); + bus.on("removeListener", (event) => console.log("removeListener", event)); + bus.on("later", () => {}); + bus.removeAllListeners("added"); + console.log(bus.listenerCount("added"), bus.listenerCount("later")); + bus.removeAllListeners(); + console.log(bus.eventNames().length); + + const sub = new Sub("child"); + sub.on("added", (entry) => console.log("sub added", entry)); + sub.add("c"); + console.log(sub instanceof Sub, sub instanceof Bus, sub instanceof EventEmitter, sub.entries); + + const legacy = new Legacy(); + console.log("legacy", legacy instanceof EventEmitter, legacy.getMaxListeners()); + try { + legacy.setMaxListeners(-1); + } catch (err) { + console.log(err.name, err.code); + } + try { + legacy.on("bad", "not a function"); + } catch (err) { + console.log(err.name, err.code); + } + console.log(new Legacy()); + + // The leak warning goes to stderr with the process id: run it in a child + // and print its output with the id normalized. + const { spawnSync } = require("child_process"); + const child = spawnSync(process.execPath, [process.argv[1], "leak"], { encoding: "utf8" }); + console.log(child.stdout + child.stderr.replace(/\(node:\d+\)/g, "(node:)")); +} + +function leak() { + const legacy = new Legacy(); + legacy.setMaxListeners(1); + legacy.on("many", () => {}); + legacy.on("many", () => {}); + legacy.on("many", () => {}); + console.log("leak", legacy.listenerCount("many")); + setTimeout(() => console.log("leak done"), 1); +} + +if (process.argv[2] === "leak") leak(); +else main(); diff --git a/tests/corpus/6454-constructor-function-stream-bases.cjs b/tests/corpus/6454-constructor-function-stream-bases.cjs new file mode 100644 index 000000000..4b2bb6789 --- /dev/null +++ b/tests/corpus/6454-constructor-function-stream-bases.cjs @@ -0,0 +1,65 @@ +// The legacy Stream constructor as a constructor function's base: its +// EventEmitter state and the legacy pipe between two such streams. The +// stream classes take util.inherits, and instanceof follows the chain. +"use strict"; +const util = require("util"); +const Stream = require("stream"); +const { Readable, Writable, Duplex, Transform, PassThrough } = require("stream"); + +function Source(items) { + Stream.call(this); + this.items = items; + this.readable = true; +} +util.inherits(Source, Stream); +Source.prototype.start = function () { + for (const item of this.items) this.emit("data", item); + this.emit("end"); +}; + +function Sink() { + Stream.call(this); + this.writable = true; + this.chunks = []; +} +util.inherits(Sink, Stream); +Sink.prototype.write = function (chunk) { + this.chunks.push(chunk); + return true; +}; +Sink.prototype.end = function () { + console.log("sink end", this.chunks.join("+")); +}; + +const source = new Source(["a", "b", "c"]); +const sink = new Sink(); +sink.on("pipe", (from) => console.log("piped", from === source)); +console.log(source.pipe(sink) === sink, sink.listenerCount("pipe")); +source.start(); +console.log("after end", source.listenerCount("data"), sink.listenerCount("drain")); + +const kept = new Sink(); +const second = new Source(["x"]); +second.pipe(kept, { end: false }); +second.start(); +console.log("kept open", kept.chunks); + +function Reader() {} +util.inherits(Reader, Readable); +function Both() {} +util.inherits(Both, Duplex); +function Shift() {} +util.inherits(Shift, Transform); +function Through() {} +util.inherits(Through, PassThrough); +function Out() {} +util.inherits(Out, Writable); +console.log(Reader.super_ === Readable, Through.super_ === PassThrough, Out.super_ === Writable); +const reader = Object.create(Reader.prototype); +const through = Object.create(Through.prototype); +const out = Object.create(Out.prototype); +const both = Object.create(Both.prototype); +console.log(reader instanceof Readable, reader instanceof Writable); +console.log(through instanceof Transform, through instanceof Duplex, through instanceof Readable); +console.log(out instanceof Writable, out instanceof Readable, both instanceof Readable); +console.log(typeof Shift.prototype.constructor, Shift.prototype instanceof Duplex); diff --git a/tests/corpus/6460-in-union-optional-slots.ts b/tests/corpus/6460-in-union-optional-slots.ts new file mode 100644 index 000000000..f381217b9 --- /dev/null +++ b/tests/corpus/6460-in-union-optional-slots.ts @@ -0,0 +1,53 @@ +// `"k" in u` over a union of records where some arms declare `k` as an +// optional slot: those arms answer by the value's own property presence +// (an explicit undefined is present, an omitted or deleted key is not), +// the other arms by their tag. +type Cond = { type: string; value?: string }; +type HandleRoute = { handle: string; src?: string; dest?: string; status?: number }; +type SourceRoute = { + src: string; + dest?: string; + headers?: { [key: string]: string }; + middlewarePath?: string; + continue?: boolean; + status?: number; + has?: Cond[]; +}; +type Route = HandleRoute | SourceRoute; + +const routes: Route[] = [ + { handle: "filesystem" }, + { src: "/a", middlewarePath: "/mw" }, + { src: "/b", dest: "/c", status: undefined }, + { handle: "miss", src: "/x", dest: undefined }, + { src: "/d", has: [{ type: "header" }] }, +]; + +for (const r of routes) { + console.log("handle" in r, "middlewarePath" in r, "dest" in r, "status" in r, "has" in r, "toString" in r); +} + +// A deleted optional key leaves the value; a later write brings it back. +const second = routes[1]!; +if ("middlewarePath" in second) { + delete second.middlewarePath; +} +console.log("middlewarePath" in second, JSON.stringify(second)); +if (!("handle" in second)) { + second.middlewarePath = "/again"; +} +console.log("middlewarePath" in second, JSON.stringify(second)); + +// The receiver evaluates once, even when it is a call. +let calls = 0; +function pick(i: number): Route { + calls++; + return routes[i]!; +} +console.log("dest" in pick(2), "dest" in pick(0), "src" in pick(3), calls); + +// `in` as a narrowing test inside a predicate and a filter callback. +function hasMiddleware(route: Route): boolean { + return "middlewarePath" in route && typeof route.middlewarePath === "string"; +} +console.log(routes.filter(hasMiddleware).length, routes.filter((r) => "status" in r).length); diff --git a/tests/corpus/6461-predicate-intersection-narrowing.ts b/tests/corpus/6461-predicate-intersection-narrowing.ts new file mode 100644 index 000000000..7e8ff9d8a --- /dev/null +++ b/tests/corpus/6461-predicate-intersection-narrowing.ts @@ -0,0 +1,70 @@ +// A predicate `r is Route & { path: string }` refines the record arms of +// a union without naming a stored arm: arms that do not declare `path` +// cannot hold such a value, the others are read through their own +// layout. The narrowed value can be read in place, in templates, in +// object literals and from closures. +type HandleRoute = { handle: string; src?: string; dest?: string }; +type SourceRoute = { src: string; dest?: string; path?: string; status?: number }; +type Route = HandleRoute | SourceRoute; + +function isPathRoute(route: Route): route is Route & { path: string } { + return "path" in route && typeof route.path === "string"; +} + +type Output = { path: string; index: number }; + +function collect(result: { names: Set; routes: Route[] }): Map { + const out = new Map(); + for (const route of result.routes) { + if (!isPathRoute(route)) continue; + let name = route.path; + if (!result.names.has(name)) name = name.replace(/^\//, ""); + if (!result.names.has(name)) { + throw new Error(`Mapping ${route.path} not found`); + } + out.set(name, { path: route.path, index: out.get(name)?.index ?? out.size }); + } + return out; +} + +const routes: Route[] = [ + { handle: "filesystem" }, + { src: "/a", path: "/one" }, + { src: "/b" }, + { src: "/c", path: "two" }, + { src: "/d", path: "/one", status: 308 }, +]; +console.log([...collect({ names: new Set(["one", "two"]), routes })]); +try { + collect({ names: new Set(), routes: [{ src: "/z", path: "/missing" }] }); +} catch (e) { + console.log((e as Error).message); +} + +// Closures capture the narrowed binding. +const readers: (() => string)[] = []; +for (const route of routes) { + if (!isPathRoute(route)) continue; + readers.push(() => `${route.src} -> ${route.path}`); +} +console.log(readers.map((read) => read())); + +// Three arms, two of which can hold the refinement: the narrowed value is +// either of them, and their shared members still read. +type A = { kind: string; a: number; tag?: string }; +type B = { kind: string; b: boolean; tag?: string }; +type C = { kind: string; c: string }; +type Item = A | B | C; +function isTagged(item: Item): item is Item & { tag: string } { + return "tag" in item && item.tag !== undefined; +} +const items: Item[] = [ + { kind: "a", a: 1, tag: "x" }, + { kind: "b", b: true }, + { kind: "c", c: "plain" }, + { kind: "b", b: false, tag: "y" }, +]; +for (const item of items) { + if (isTagged(item)) console.log(item.kind, item.tag.toUpperCase()); + else console.log(item.kind, "untagged"); +} diff --git a/tests/corpus/6462-assertion-attached-members.ts b/tests/corpus/6462-assertion-attached-members.ts new file mode 100644 index 000000000..8a2be31b7 --- /dev/null +++ b/tests/corpus/6462-assertion-attached-members.ts @@ -0,0 +1,58 @@ +// `route as Route & { order: number; internal: boolean }` followed by +// writes through the asserted view adds the members to the object the +// original binding names: they appear wherever the route is observed +// later, in the order the writes created them. +type HandleRoute = { handle: string; src?: string; dest?: string; status?: number }; +type SourceRoute = { src: string; dest?: string; path?: string; status?: number }; +type Route = HandleRoute | SourceRoute; + +function markInternal(input: Route[], out: Route[], order: number): void { + for (const route of input) { + const marked = route as Route & { internal: boolean; order: number }; + marked.internal = true; + marked.order = order; + out.unshift(route); + } +} + +const out: Route[] = [{ handle: "filesystem" }, { src: "/plain", dest: "/x" }]; +markInternal([{ src: "/a" }, { handle: "miss" }, { src: "/b", status: 404 }], out, 3); +console.log(JSON.stringify(out)); +console.log(out); +console.log(out.map((r) => ("internal" in (r as Route & { internal?: boolean }) ? "marked" : "plain"))); +console.log(out.map((r) => ({ ...r }))); + +// Values the assertion never reached keep no trace of the members. +const fresh: Route = { src: "/new" }; +console.log(JSON.stringify(fresh), Object.keys(fresh)); + +// A single record shape works the same way, and the asserted view reads +// back what it wrote. +type Job = { id: string; retries?: number }; +const job: Job = { id: "j1" }; +const tracked = job as Job & { startedAt: number }; +tracked.startedAt = 42; +console.log(tracked.startedAt + 1, JSON.stringify(job), Object.keys(job)); + +// An annotated binding of the asserted type keeps the object as well: +// the members appear on it once written, not before. +function stamp(job: Job): Job & { finishedAt: number } { + const done: Job & { finishedAt: number } = job as Job & { finishedAt: number }; + done.finishedAt = 7; + return done; +} +const second: Job = { id: "j2", retries: 1 }; +console.log(stamp(second).finishedAt, JSON.stringify(second), Object.keys(second)); + +const third: Job = { id: "j3" }; +const queued: Job & { queuedAt: number } = third as Job & { queuedAt: number }; +queued.queuedAt = 9; +console.log(queued.queuedAt, JSON.stringify(third), Object.keys(third)); + +function markAnnotated(route: Route): void { + const marked: Route & { internal: boolean } = route as Route & { internal: boolean }; + marked.internal = false; +} +const annotatedRoute: Route = { handle: "hit" }; +markAnnotated(annotatedRoute); +console.log(JSON.stringify(annotatedRoute)); diff --git a/tests/corpus/6463-node-readable-stream-producers.ts b/tests/corpus/6463-node-readable-stream-producers.ts new file mode 100644 index 000000000..4d06fac94 --- /dev/null +++ b/tests/corpus/6463-node-readable-stream-producers.ts @@ -0,0 +1,75 @@ +// NodeJS.ReadableStream is the interface every Node readable implements. +// One helper typed with it consumes a file stream, a Readable subclass, +// Readable.from, a gunzip Transform fed through pipe(), and a child's +// stdout. A program that mixes those producers compiles the interface as +// the node:stream Readable, with the child's pipe bridged into one. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { Readable } from "node:stream"; +import * as zlib from "node:zlib"; + +function drain(label: string, src: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + let text = ""; + let chunks = 0; + src.on("data", (chunk: Buffer | string) => { + chunks++; + text += typeof chunk === "string" ? `[s]${chunk}` : chunk.toString("utf8"); + }); + src.on("error", (err) => reject(err)); + src.on("end", () => { + console.log(label, "end", chunks > 0, src.readable); + resolve(text); + }); + }); +} + +class Countdown extends Readable { + private left = 3; + _read(): void { + this.push(this.left > 0 ? `t-${this.left--};` : null); + } +} + +function openText(file: string): NodeJS.ReadableStream { + return fs.createReadStream(file); +} + +function openDecoded(file: string): NodeJS.ReadableStream { + const stream = fs.createReadStream(file); + stream.setEncoding("utf8"); + return stream; +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-readable-")); + const plain = path.join(dir, "plain.txt"); + const packed = path.join(dir, "packed.gz"); + fs.writeFileSync(plain, "hello from a file\n"); + fs.writeFileSync(packed, zlib.gzipSync(Buffer.from("inflated text\n"))); + + console.log(JSON.stringify(await drain("file", openText(plain)))); + console.log(JSON.stringify(await drain("decoded", openDecoded(plain)))); + console.log(JSON.stringify(await drain("subclass", new Countdown()))); + console.log(JSON.stringify(await drain("from", Readable.from(["a", "b", "c"])))); + const gunzip = fs.createReadStream(packed).pipe(zlib.createGunzip()); + console.log(JSON.stringify(await drain("gunzip", gunzip))); + + const child = spawn("sh", ["-c", "printf 'child line\\n'"], { + stdio: ["ignore", "pipe", "inherit"], + }); + const out = child.stdout; + if (out !== null) console.log(JSON.stringify(await drain("child", out))); + console.log("same stdout", child.stdout === out); + + try { + await drain("missing", openText(path.join(dir, "missing.txt"))); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6464-node-writable-stream-producers.ts b/tests/corpus/6464-node-writable-stream-producers.ts new file mode 100644 index 000000000..bc9986b9b --- /dev/null +++ b/tests/corpus/6464-node-writable-stream-producers.ts @@ -0,0 +1,68 @@ +// NodeJS.WritableStream is the interface every Node writable implements: +// process.stdout/stderr, fs.WriteStream and the node:stream Writable +// family. Helpers typed with it accept all of them, a copy helper pipes a +// file stream into one, and readable.pipe(process.stdout) leaves the +// process stream open afterwards, as in Node. +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { Readable, Writable } from "node:stream"; + +function emit(dst: NodeJS.WritableStream, line: string): boolean { + return dst.write(`${line}\n`); +} + +function copy(src: NodeJS.ReadableStream, dst: NodeJS.WritableStream): Promise { + return new Promise((resolve, reject) => { + src.on("error", reject); + dst.on("error", reject); + dst.on("finish", () => resolve()); + src.pipe(dst); + }); +} + +class Collector extends Writable { + readonly parts: string[] = []; + _write(chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void { + this.parts.push(chunk.toString("utf8")); + callback(); + } +} + +function finished(stream: NodeJS.WritableStream): Promise { + return new Promise((resolve) => { + stream.end(() => resolve()); + }); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-writable-")); + const target = path.join(dir, "out.txt"); + const file = fs.createWriteStream(target); + console.log("file write", emit(file, "to a file")); + console.log("stdout write", emit(process.stdout, "to stdout")); + emit(process.stderr, "to stderr"); + const collector = new Collector(); + emit(collector, "to a collector"); + await finished(file); + await finished(collector); + console.log(JSON.stringify(fs.readFileSync(target, "utf8"))); + console.log(JSON.stringify(collector.parts)); + + const source = path.join(dir, "source.txt"); + const copied = path.join(dir, "copied.txt"); + fs.writeFileSync(source, "copied through pipe\n"); + await copy(fs.createReadStream(source), fs.createWriteStream(copied)); + console.log(JSON.stringify(fs.readFileSync(copied, "utf8"))); + + await new Promise((resolve) => { + const piped = Readable.from(["piped ", "to ", "stdout\n"]); + piped.on("end", () => resolve()); + console.log("pipe answers stdout", piped.pipe(process.stdout) === process.stdout); + }); + // The pipe did not end process.stdout. + emit(process.stdout, "still writable"); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6465-node-stream-js-boundary/consume.js b/tests/corpus/6465-node-stream-js-boundary/consume.js new file mode 100644 index 000000000..ceb8f3abf --- /dev/null +++ b/tests/corpus/6465-node-stream-js-boundary/consume.js @@ -0,0 +1,12 @@ +export function streamToText(stream) { + return new Promise((resolve, reject) => { + const chunks = []; + stream.on("data", (chunk) => chunks.push(Buffer.from(chunk))); + stream.on("error", reject); + stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8"))); + }); +} + +export function describeStream(stream) { + console.log(typeof stream.on, typeof stream.pipe, stream.readable); +} diff --git a/tests/corpus/6465-node-stream-js-boundary/main.ts b/tests/corpus/6465-node-stream-js-boundary/main.ts new file mode 100644 index 000000000..0079c0733 --- /dev/null +++ b/tests/corpus/6465-node-stream-js-boundary/main.ts @@ -0,0 +1,33 @@ +// NodeJS.ReadableStream values crossing into untyped JavaScript and into +// 'unknown': a file stream and a child's stdout share one helper with a +// JavaScript stream consumer, which sees ordinary Node streams. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { describeStream, streamToText } from "./consume.js"; + +function open(file: string): NodeJS.ReadableStream { + return fs.createReadStream(file); +} + +function textOf(stream: NodeJS.ReadableStream): Promise { + return streamToText(stream); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-stream-js-")); + const file = path.join(dir, "input.txt"); + fs.writeFileSync(file, "through javascript\n"); + const held: unknown = open(file); + console.log(typeof held); + describeStream(held); + console.log(JSON.stringify(await textOf(open(file)))); + const child = spawn("sh", ["-c", "printf 'child output\\n'"], { + stdio: ["ignore", "pipe", "inherit"], + }); + if (child.stdout !== null) console.log(JSON.stringify(await textOf(child.stdout))); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6465-optional-members-over-index-signature.ts b/tests/corpus/6465-optional-members-over-index-signature.ts new file mode 100644 index 000000000..df957c8d9 --- /dev/null +++ b/tests/corpus/6465-optional-members-over-index-signature.ts @@ -0,0 +1,73 @@ +// An index signature whose value admits undefined already describes an +// optional member of the same type (`interface Env extends Dict +// { TZ?: string }`, @types/node's ProcessEnv): such a record is one key +// set in insertion order, so spread, delete, `in` and every enumeration +// treat the declared member like any other key. +interface Env { + TZ?: string; + [key: string]: string | undefined; +} + +const base: Env = { A: "1", TZ: "UTC", B: "2" }; +const env = { ...base }; +env.C = "3"; +for (const key of ["A", "TZ", "MISSING"]) { + delete env[key]; +} +console.log(Object.entries(env), Object.keys(base)); +const probe = (key: string): boolean => key in env; +console.log(probe("TZ"), probe("B"), env.TZ, env.B); +env.TZ = "Europe/Berlin"; +console.log(JSON.stringify(env), env); + +const extended: Env = { ...base, D: "4" }; +delete extended.TZ; +extended.TZ = "Asia/Tokyo"; +console.log(extended, Object.keys(extended).join(",")); + +// A declared member written after another key comes after it. +interface Scores { + best?: number; + [name: string]: number | undefined; +} +const scores: Scores = {}; +scores["x"] = 1; +scores.best = 2; +console.log(Object.keys(scores), JSON.stringify(scores)); + +// The process environment has the same shape; a child environment built +// from it keeps the parent's keys in order. +process.env.SCX_FIRST = "first"; +process.env.SCX_DROP = "drop"; +process.env.SCX_LAST = "last"; +const DROP = ["SCX_DROP", "SCX_NOT_SET"]; +function childEnv(): string[] { + const runEnv = { ...process.env }; + runEnv.SCX_ADDED = "added"; + for (const key of DROP) { + delete runEnv[key]; + } + runEnv.SCX_FIRST = runEnv.SCX_FIRST ? `${runEnv.SCX_FIRST}!` : "none"; + return Object.entries(runEnv) + .filter(([key]) => key.startsWith("SCX_")) + .map(([key, value]) => `${key}=${value || ""}`); +} +console.log(childEnv()); + +// A record of declared optional members over a slot without undefined is +// the same key set: merging it over the process environment keeps both. +type Dictionary = { [key: string]: T }; +type BuildEnv = { CI?: string; DEBUG?: string } & Dictionary; +function merged(buildEnv: BuildEnv): string[] { + const env = { ...process.env, ...buildEnv }; + return [ + String(env.CI === "1" || env.SCX_FIRST === "first"), + `${env.DEBUG ?? "-"}`, + Object.keys(env) + .filter((key) => key.startsWith("SCX_") || key === "CI" || key === "DEBUG") + .join(","), + ]; +} +const buildEnv: BuildEnv = { SCX_BUILD: "b", CI: "1" }; +buildEnv.DEBUG = "yes"; +console.log(merged(buildEnv), Object.keys(buildEnv), buildEnv.CI, buildEnv["SCX_BUILD"]); diff --git a/tests/corpus/6466-never-assertion-argument.ts b/tests/corpus/6466-never-assertion-argument.ts new file mode 100644 index 000000000..8d2c1f2bf --- /dev/null +++ b/tests/corpus/6466-never-assertion-argument.ts @@ -0,0 +1,31 @@ +// `value as never` makes an unknown value fit any parameter: the argument +// is checked against the parameter's type like any unknown value the +// checker admits there, and a typed value passes unchanged. +type Out = { routes: { src: string; dest?: string }[]; count: number }; + +function show(tags: Record): void { + console.log(JSON.stringify(tags)); +} + +function report(tag: (result: Out) => Record, value: unknown): void { + show(tag(value as never)); +} + +const describe = (r: Out): Record => ({ count: r.count, first: r.routes[0]?.src }); +report(describe, JSON.parse('{"routes":[{"src":"/a"},{"src":"/b","dest":"/c"}],"count":2}')); + +// Through a promise of unknown, the way a wrapper tags an async result. +function later(value: Out): Promise { + return Promise.resolve(value); +} +later({ routes: [], count: 7 }).then((value) => { + show(describe(value as never)); +}); + +// A typed value keeps its identity through the assertion. +function bump(target: Out): void { + target.count++; +} +const typed: Out = { routes: [{ src: "/x" }], count: 1 }; +bump(typed as never); +console.log(typed.count); diff --git a/tests/corpus/6466-node-stdio-stream-surface.ts b/tests/corpus/6466-node-stdio-stream-surface.ts new file mode 100644 index 000000000..d3726360f --- /dev/null +++ b/tests/corpus/6466-node-stdio-stream-surface.ts @@ -0,0 +1,45 @@ +// The stdio handles reach the full stream surface when a program uses it: +// child.stdout piped to process.stdout, a file stream piped into a +// child's stdin, and an 'error' listener on a NodeJS.WritableStream +// parameter that only receives the process streams. Each of these +// compiles the NodeJS stream interfaces as the node:stream classes. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; + +function report(out: NodeJS.WritableStream, text: string): void { + out.on("error", (err) => console.log("error", err.message)); + out.write(`${text}\n`); +} + +function echo(text: string): Promise { + return new Promise((resolve) => { + const child = spawn("sh", ["-c", `echo ${text}`], { stdio: ["ignore", "pipe", "inherit"] }); + child.stdout?.pipe(process.stdout); + child.on("close", (code) => resolve(code)); + }); +} + +function cat(input: string): Promise { + return new Promise((resolve) => { + const child = spawn("cat", [], { stdio: ["pipe", "pipe", "inherit"] }); + child.stdout?.pipe(process.stdout); + child.on("close", (code) => resolve(code)); + if (child.stdin) fs.createReadStream(input).pipe(child.stdin); + }); +} + +async function main(): Promise { + report(process.stdout, "reported"); + report(process.stderr, "reported to stderr"); + console.log("echo exit", await echo("piped from a child")); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-stdio-")); + const input = path.join(dir, "input.txt"); + fs.writeFileSync(input, "file contents through cat\n"); + console.log("cat exit", await cat(input)); + report(process.stdout, "stdout stays open"); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6467-fromentries-record-assertion.ts b/tests/corpus/6467-fromentries-record-assertion.ts new file mode 100644 index 000000000..cf8f1c1ff --- /dev/null +++ b/tests/corpus/6467-fromentries-record-assertion.ts @@ -0,0 +1,43 @@ +// `Object.fromEntries(...) as T` for a declared record T keeps exactly +// the keys the entries created, in their order, as Node keeps the object +// itself, as long as the entries hold scalars or checked-dynamic values. +type Tags = { "error.type": string; "error.msg": string; "error.cause"?: string }; + +function scrub(input: string): string { + return input.replace(/secret/g, "***"); +} + +function redact(tags: Tags): Tags { + return Object.fromEntries( + Object.entries(tags).map(([key, value]) => [key, typeof value === "string" ? scrub(value) : value]), + ) as Tags; +} + +const e = new Error("a secret here"); +const plain = redact({ "error.type": e.name, "error.msg": e.message }); +console.log(plain, Object.keys(plain), "error.cause" in plain, plain["error.msg"].length); +const caused = redact({ "error.type": "TypeError", "error.msg": "m", "error.cause": "secret cause" }); +console.log(caused, JSON.stringify(caused)); + +// Keys the record does not declare stay with the object. +type Point = { x: number; y: number }; +const p = Object.fromEntries([ + ["y", 2], + ["x", 1], + ["z", 3], +]) as Point; +console.log(p, p.x + p.y, Object.keys(p), JSON.stringify(p)); + +// Entries held as unknown keep their identity: the object's members are +// the same array and record, and writes through either reach both. +type Holder = { list: number[]; point: { x: number } }; +const list = [1, 2]; +const point = { x: 1 }; +const entries: [string, unknown][] = [ + ["list", list], + ["point", point], +]; +const held = Object.fromEntries(entries) as Holder; +held.list.push(3); +held.point.x = 5; +console.log(list.length, point.x, held.list === list, held.point === point); diff --git a/tests/corpus/6467-node-stream-untyped-sources/main.ts b/tests/corpus/6467-node-stream-untyped-sources/main.ts new file mode 100644 index 000000000..5cb3a628d --- /dev/null +++ b/tests/corpus/6467-node-stream-untyped-sources/main.ts @@ -0,0 +1,58 @@ +// Streams that arrive untyped meet NodeJS stream interface slots: an +// untyped JavaScript function's stream returned as NodeJS.ReadableStream, +// a checked cast from 'unknown', pipe() and pipeline() into untyped +// destinations, and 'for await' over a NodeJS.ReadableStream +// parameter fed by a file stream and a child's stdout. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { pipeline } from "node:stream/promises"; +import * as zlib from "node:zlib"; +import { collector, joined, openUntyped } from "./sources.js"; + +function open(file: string): NodeJS.ReadableStream { + return openUntyped(file); +} + +function held(file: string): unknown { + return fs.createReadStream(file); +} + +async function collect(label: string, src: NodeJS.ReadableStream): Promise { + let text = ""; + for await (const chunk of src) text += chunk.toString(); + console.log(label, JSON.stringify(text)); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-stream-untyped-")); + const plain = path.join(dir, "plain.txt"); + const packed = path.join(dir, "packed.gz"); + fs.writeFileSync(plain, "plain text\n"); + fs.writeFileSync(packed, zlib.gzipSync(Buffer.from("packed text\n"))); + + await collect("returned", open(plain)); + await collect("cast", held(plain) as NodeJS.ReadableStream); + const child = spawn("sh", ["-c", "printf 'child text\\n'"], { stdio: ["ignore", "pipe", "inherit"] }); + if (child.stdout) await collect("child", child.stdout); + + await new Promise((resolve) => { + const piped = fs.createReadStream(plain); + piped.on("end", () => resolve()); + piped.pipe(collector()); + }); + console.log("pipe", JSON.stringify(joined())); + + const sink = collector(); + await pipeline(openUntyped(packed), zlib.createGunzip(), sink); + console.log("pipeline", JSON.stringify(joined())); + try { + await pipeline(openUntyped(path.join(dir, "missing.gz")), zlib.createGunzip(), collector()); + } catch (err) { + console.log("pipeline error", (err as NodeJS.ErrnoException).code); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6467-node-stream-untyped-sources/sources.js b/tests/corpus/6467-node-stream-untyped-sources/sources.js new file mode 100644 index 000000000..ce4b5dcb7 --- /dev/null +++ b/tests/corpus/6467-node-stream-untyped-sources/sources.js @@ -0,0 +1,20 @@ +import * as fs from "node:fs"; +import { PassThrough } from "node:stream"; + +const parts = []; + +/** @returns {any} */ +export function openUntyped(file) { + return fs.createReadStream(file); +} + +/** @returns {any} */ +export function collector() { + const sink = new PassThrough(); + sink.on("data", (chunk) => parts.push(chunk.toString("utf8"))); + return sink; +} + +export function joined() { + return parts.splice(0).join(""); +} diff --git a/tests/corpus/6468-generic-unknown-record-entries.ts b/tests/corpus/6468-generic-unknown-record-entries.ts new file mode 100644 index 000000000..f0e57756a --- /dev/null +++ b/tests/corpus/6468-generic-unknown-record-entries.ts @@ -0,0 +1,33 @@ +// Inside a generic body, `Object.entries(tags)` over `T extends +// Record` reads `[string, T[keyof T]]` elements. An +// instance whose T is the unknown-valued record view reads every member +// as unknown; a record instance reads the union of its member types. +function upper(text: string): string { + return text.toUpperCase(); +} + +function scrub(text: string): string { + return text.replace(/secret/g, "***"); +} + +function describe>(tags: T): string[] { + return Object.entries(tags).map( + ([key, value]) => `${key}=${typeof value === "string" ? upper(value) : String(value)}`, + ); +} + +function redact>(tags: T): T { + return Object.fromEntries( + Object.entries(tags).map(([key, value]) => [key, typeof value === "string" ? scrub(value) : value]), + ) as T; +} + +function flatten(value: unknown): Record { + return typeof value === "object" && value !== null ? (value as Record) : { value }; +} + +console.log(describe(flatten({ b: 1, a: "x", c: true }))); +console.log(describe(flatten(5))); +console.log(describe({ name: "n", size: 3 })); +const cleaned = redact(flatten({ token: "secret", count: 2 })); +console.log(cleaned, Object.keys(cleaned)); diff --git a/tests/corpus/6468-node-stdio-writable-slot.ts b/tests/corpus/6468-node-stdio-writable-slot.ts new file mode 100644 index 000000000..a326344d1 --- /dev/null +++ b/tests/corpus/6468-node-stdio-writable-slot.ts @@ -0,0 +1,49 @@ +// process.stdout and process.stderr in NodeJS.WritableStream slots of a +// program that also passes other streams there: every slot holds the same +// stream object, a pipe through such a slot leaves the process stream +// open, and Writable's listener methods return the stream for chaining. +import { Readable, Writable } from "node:stream"; + +class Sink extends Writable { + _write(_chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void { + callback(); + } +} + +function same(a: NodeJS.WritableStream, b: NodeJS.WritableStream): boolean { + return a === b; +} + +function pipeInto(dst: NodeJS.WritableStream, parts: string[]): Promise { + return new Promise((resolve) => { + const src = Readable.from(parts); + src.on("end", () => resolve()); + src.pipe(dst); + }); +} + +function write(dst: NodeJS.WritableStream, text: string): boolean { + return dst.write(text); +} + +async function main(): Promise { + console.log("stdout same", same(process.stdout, process.stdout)); + console.log("stderr same", same(process.stderr, process.stderr)); + console.log("stdout and stderr", same(process.stdout, process.stderr)); + console.log("sink and stdout", same(new Sink(), process.stdout)); + + await pipeInto(process.stdout, ["piped ", "through ", "a slot\n"]); + // The pipe did not end process.stdout: writes through a slot still work. + console.log("write after pipe", write(process.stdout, "still open\n")); + await pipeInto(process.stderr, ["piped to stderr\n"]); + write(process.stderr, "stderr still open\n"); + + // Writable's listener methods return the stream. + const sink = new Sink(); + await new Promise((resolve) => { + sink.on("finish", () => resolve()).end("done"); + }); + console.log("sink finished", sink.writableFinished); +} + +void main(); diff --git a/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts b/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts new file mode 100644 index 000000000..258755f83 --- /dev/null +++ b/tests/corpus/6469-node-stdio-writable-js-pipe/main.ts @@ -0,0 +1,36 @@ +// process.stdout and process.stderr reach an untyped JavaScript helper +// through NodeJS.WritableStream slots, and the helper pipes into them. As +// in Node, the pipe leaves the process stream open, so later writes +// through the same slots still work. +import { Readable, Writable } from "node:stream"; +import { pipeAll } from "./pipe.js"; + +class Sink extends Writable { + readonly parts: string[] = []; + _write(chunk: Buffer, _encoding: string, callback: (error?: Error | null) => void): void { + this.parts.push(chunk.toString("utf8")); + callback(); + } +} + +function into(destination: NodeJS.WritableStream, parts: string[]): Promise { + return new Promise((resolve) => { + pipeAll(Readable.from(parts), destination, () => resolve()); + }); +} + +function write(destination: NodeJS.WritableStream, text: string): boolean { + return destination.write(text); +} + +async function main(): Promise { + const sink = new Sink(); + await into(sink, ["to ", "a sink"]); + console.log("sink", JSON.stringify(sink.parts), sink.writableEnded); + await into(process.stdout, ["piped ", "by javascript\n"]); + console.log("write after pipe", write(process.stdout, "still open\n")); + await into(process.stderr, ["piped to stderr\n"]); + write(process.stderr, "stderr still open\n"); +} + +void main(); diff --git a/tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js b/tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js new file mode 100644 index 000000000..d48d592ad --- /dev/null +++ b/tests/corpus/6469-node-stdio-writable-js-pipe/pipe.js @@ -0,0 +1,4 @@ +export function pipeAll(source, destination, done) { + source.on("end", () => done()); + source.pipe(destination); +} diff --git a/tests/corpus/6470-any-destructuring.ts b/tests/corpus/6470-any-destructuring.ts new file mode 100644 index 000000000..09d7f45c4 --- /dev/null +++ b/tests/corpus/6470-any-destructuring.ts @@ -0,0 +1,124 @@ +// Destructuring `any` values in TypeScript: a function typed to return +// `any` (an untyped command runner), an awaited `any` promise, JSON.parse, +// and an `any` table read by key all destructure through the +// checked-dynamic value, with renames, defaults, nesting, rest, holes and +// Node's TypeError for a null source, including empty patterns, awaited +// sources, element reads, `this` receivers and string, numeric and +// computed first keys. +function run(command: string): any { + return { stdout: ` ${command} output \n`, stderr: "", exitCode: 0, meta: { pid: 42 } }; +} + +const { stdout, stderr } = run("echo"); +console.log(JSON.stringify(stdout.toString().trim()), stderr.length); + +const { exitCode: code, missing, meta: { pid }, ...rest } = run("ls"); +console.log(code, missing, pid, Object.keys(rest).join(",")); + +const { signal = "none", exitCode = 9 } = run("true"); +console.log(signal, exitCode); + +const [first, , third = "default", ...others] = JSON.parse('["a", "b", null, "d", "e"]'); +console.log(first, third, others.length); + +const { list: [head, ...tail] } = JSON.parse('{"list":[1,2,3]}'); +console.log(head, tail); + +async function fetchAny(): Promise { + return { body: "payload", status: 200 }; +} + +async function main(): Promise { + const { body, status } = await fetchAny(); + console.log(body, status); + const pending = fetchAny(); + const { status: again } = await pending; + console.log(again + 1); +} + +const table: any = { a: { mode: 0o755, path: "/bin" }, b: { mode: 0o644, path: "/etc" } }; +for (const key in table) { + const { mode, path } = table[key]; + console.log(key, mode.toString(8), path); +} + +function lookup(found: boolean): any { + return found ? { anything: 1 } : null; +} +for (const found of [true, false]) { + try { + const { anything } = lookup(found); + console.log(anything); + } catch (e) { + console.log(e instanceof TypeError, (e as Error).message); + } +} + +async function nothing(): Promise { + return null; +} + +function nothingSync(): any { + return undefined; +} + +class Holder { + value(): any { + return null; + } + read(): void { + const { a } = this.value(); + console.log(a); + } +} + +const sparse: any = { k: null, list: [null] }; +const key = "k"; + +async function nullSources(): Promise { + const attempts: Array<() => Promise> = [ + async () => { + const {} = await nothing(); + }, + async () => { + const { a } = await nothing(); + console.log(a); + }, + async () => { + const {} = nothingSync(); + }, + async () => { + const {} = (await nothing()) as any; + }, + async () => { + const { "a-b": x } = nothingSync()!; + console.log(x); + }, + async () => { + const { 1.5: x } = sparse[key]; + console.log(x); + }, + async () => { + const { [key]: x } = sparse.list[0]; + console.log(x); + }, + async () => { + new Holder().read(); + }, + async () => { + let a: any; + ({ a } = await nothing()); + console.log(a); + }, + ]; + for (const attempt of attempts) { + try { + await attempt(); + console.log("no error"); + } catch (e) { + console.log(e instanceof TypeError, (e as Error).message); + } + } +} + +main().then(nullSources); diff --git a/tests/corpus/6470-promise-reject-async-return.ts b/tests/corpus/6470-promise-reject-async-return.ts new file mode 100644 index 000000000..4d9a62eab --- /dev/null +++ b/tests/corpus/6470-promise-reject-async-return.ts @@ -0,0 +1,17 @@ +// `return Promise.reject(err)` in an async function returns a promise of the +// function's own result type, which the async function adopts. +type Ctx = { id: string; endpoint: string; meta?: { [key: string]: string } }; +async function context(endpoint: string | undefined): Promise { + if (!endpoint || endpoint.length === 0) { + return Promise.reject(new Error("invalid endpoint")); + } + return Promise.resolve({ id: "c1", endpoint }); +} +function sync(flag: boolean): Promise { + if (flag) return Promise.reject(new TypeError("nope")); + return Promise.resolve(3); +} +context("http://x").then((c) => console.log(c.id, c.endpoint)); +context(undefined).catch((e) => console.log("rejected", String(e))); +sync(false).then((n) => console.log(n)); +sync(true).catch((e) => console.log(e instanceof TypeError, String(e))); diff --git a/tests/corpus/6470-stream-transform-any-data-callback.ts b/tests/corpus/6470-stream-transform-any-data-callback.ts new file mode 100644 index 000000000..cc4935a7e --- /dev/null +++ b/tests/corpus/6470-stream-transform-any-data-callback.ts @@ -0,0 +1,60 @@ +// Transform option callbacks whose completion callback declares an +// any-typed `data` (the @types/node TransformCallback spelling): the data +// passed back may be a Buffer, a string, or omitted. +import { PassThrough, Transform } from "node:stream"; +import { createGunzip, gzipSync } from "node:zlib"; + +type Done = (error?: Error | null, data?: any) => void; + +interface Progress { + bytes: number; + chunks: number; +} + +function counting(progress: Progress): Transform { + return new Transform({ + transform(chunk: Buffer, _encoding: string, callback: Done) { + progress.bytes += chunk.length; + progress.chunks++; + callback(null, chunk); + }, + }); +} + +const upper = new Transform({ + transform(chunk: Buffer, _encoding: string, callback: Done) { + const text = chunk.toString(); + if (text === "skip") { + callback(); + return; + } + callback(null, text.toUpperCase()); + }, + flush(callback: Done) { + callback(null, Buffer.from("!")); + }, +}); +upper.on("data", (d: Buffer) => console.log("upper", JSON.stringify(d.toString()))); +upper.on("end", () => console.log("upper end")); +upper.write("hello"); +upper.write("skip"); +upper.end("world"); + +const failing = new Transform({ + transform(chunk: Buffer, _encoding: string, callback: Done) { + callback(new Error(`refused ${chunk.length} bytes`)); + }, +}); +failing.on("error", (e: Error) => console.log("error", e.message)); +failing.write("abc"); + +const progress: Progress = { bytes: 0, chunks: 0 }; +const counter = counting(progress); +const out = new PassThrough(); +counter.pipe(createGunzip()).pipe(out); +const parts: string[] = []; +out.on("data", (d: Buffer) => parts.push(d.toString())); +out.on("end", () => { + console.log("gunzip", JSON.stringify(parts.join("")), progress.bytes > 0, progress.chunks); +}); +counter.end(gzipSync(Buffer.from("compressed payload"))); diff --git a/tests/corpus/6471-any-method-chains.ts b/tests/corpus/6471-any-method-chains.ts new file mode 100644 index 000000000..3bfce5fba --- /dev/null +++ b/tests/corpus/6471-any-method-chains.ts @@ -0,0 +1,24 @@ +// Method chains over `any`-derived strings: each step that answers a fresh +// native value (split's string[]) keeps chaining through the +// checked-dynamic value — map, filter(Boolean), pop, join — and the result +// checks back into typed slots. +function exec(): any { + return { stdout: " 3f2a\n 9c1b \n\n77de\n", stderr: "warn: shallow\n" }; +} + +const { stdout, stderr } = exec(); +let ids: string[] = []; +ids = stdout + .split("\n") + .map((s: string) => s.trim()) + .filter(Boolean); +console.log(ids.length, ids.join(", ")); + +const last: string = stdout.trim().split("\n").pop() ?? ""; +console.log(JSON.stringify(last)); + +const firstWord: string = stderr.trim().split(":")[0]; +console.log(firstWord, stderr.trim().split(" ").length); + +const words: string[] = stdout.trim().split(/\s+/); +console.log(words.length, stdout.split("\n").length, stdout.trim().split("\n").join("|")); diff --git a/tests/corpus/6471-stream-data-buffer-or-string.ts b/tests/corpus/6471-stream-data-buffer-or-string.ts new file mode 100644 index 000000000..54c63945b --- /dev/null +++ b/tests/corpus/6471-stream-data-buffer-or-string.ts @@ -0,0 +1,47 @@ +// 'data' listeners on runtime streams may declare the chunk as +// `Buffer | string` (the @types/node ReadStream listener type): the +// listener receives a Buffer, or a string once an encoding applies. +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Readable } from "node:stream"; + +function describe(label: string, chunk: Buffer | string): void { + if (typeof chunk === "string") console.log(label, "string", JSON.stringify(chunk)); + else console.log(label, "buffer", chunk.length, JSON.stringify(chunk.toString())); +} + +const raw = new PassThrough(); +raw.on("data", (chunk: Buffer | string) => describe("raw", chunk)); +raw.write("abc"); +raw.end(Buffer.from("de")); + +const text = new PassThrough(); +text.setEncoding("utf8"); +text.on("data", (chunk: Buffer | string) => describe("text", chunk)); +text.end("héllo"); + +const r = new Readable({ read() {} }); +r.on("data", (chunk: Buffer | string) => describe("readable", chunk)); +r.on("end", () => console.log("readable end")); +r.push("one"); +r.push(null); + +const file = path.join(os.tmpdir(), `scriptc-6471-${process.pid}.txt`); +fs.writeFileSync(file, "file contents\n"); +const stream = fs.createReadStream(file); +const release = (): void => console.log("released"); +stream.on("data", (chunk: Buffer | string) => describe("file", chunk)); +stream.on("error", release); +// The second read starts once the first stream has closed its file, so the +// order of its 'close' and the second stream's data does not depend on +// which file operation finishes first. +stream.on("close", () => { + release(); + const encoded = fs.createReadStream(file, { encoding: "utf8" }); + encoded.on("data", (chunk: Buffer | string) => describe("file utf8", chunk)); + encoded.on("close", () => { + fs.unlinkSync(file); + console.log("done"); + }); +}); diff --git a/tests/corpus/6471-union-literal-results-into-record.ts b/tests/corpus/6471-union-literal-results-into-record.ts new file mode 100644 index 000000000..0f6c158d1 --- /dev/null +++ b/tests/corpus/6471-union-literal-results-into-record.ts @@ -0,0 +1,37 @@ +// A function returning differently shaped object literals (the checker +// normalizes omitted members to `?: undefined`) flows into a declared record +// result: each literal shape converts on its own. +class Tracer { + trace(_name: string, fn: () => T): T { + return fn(); + } +} +const tracer = new Tracer(); +type CacheResult = { downloaded: boolean; size?: number; cacheType?: string; dir?: string }; +async function download(n: number, dir: string | undefined): Promise { + const pending = tracer.trace("download", async () => { + if (n === 0) { + return { downloaded: false, dir }; + } + if (n < 0) { + return { downloaded: false }; + } + return { downloaded: true, size: n, cacheType: "full", dir }; + }); + return pending.finally(() => { + console.log("done", n); + }); +} +type Details = { obtain: string; url?: string; kind?: string }; +async function details(flag: number): Promise
{ + return tracer.trace("details", async () => { + if (flag === 0) return { obtain: "skipped" }; + return { obtain: "found", url: "u", kind: "k" }; + }); +} +async function main() { + console.log(await download(0, "/d"), await download(5, undefined), await download(-1, "z")); + const d0 = await details(0); + console.log(d0, "url" in d0, JSON.stringify(await details(1))); +} +main(); diff --git a/tests/corpus/6472-asserted-member-read.ts b/tests/corpus/6472-asserted-member-read.ts new file mode 100644 index 000000000..498a9bb57 --- /dev/null +++ b/tests/corpus/6472-asserted-member-read.ts @@ -0,0 +1,16 @@ +// `(source as { repoName: string }).repoName` on a union of records: arms +// that declare the member answer it, the others answer undefined, as Node +// reads a missing property. +type GithubSource = { type: string; repoId: number; repoName: string; ref: string }; +type GitlabSource = { type: string; projectId: number; ref: string }; +type BitbucketSource = { type: string; repoName: string; owner: string; ref: string }; +type Source = GithubSource | GitlabSource | BitbucketSource; +function label(source: Source): string { + return (source as { repoName: string }).repoName || "???"; +} +const sources: Source[] = [ + { type: "github", repoId: 1, repoName: "r1", ref: "main" }, + { type: "gitlab", projectId: 2, ref: "dev" }, + { type: "bitbucket", repoName: "", owner: "o", ref: "x" }, +]; +console.log(sources.map(label)); diff --git a/tests/corpus/6472-child-data-unknown-chunk.ts b/tests/corpus/6472-child-data-unknown-chunk.ts new file mode 100644 index 000000000..8308aa8cf --- /dev/null +++ b/tests/corpus/6472-child-data-unknown-chunk.ts @@ -0,0 +1,87 @@ +// A child stream 'data' listener whose chunk is unknown or any receives a +// Buffer, or a string when setEncoding ran before the listener registered. +import { spawn } from "node:child_process"; + +function report(code: number | null, kinds: string[], stdout: string, stderr: string): void { + console.log("code", code, "kinds", [...new Set(kinds)].join(",")); + console.log("stdout", JSON.stringify(stdout)); + console.log("stderr", JSON.stringify(stderr)); +} + +function runEncoded(script: string): Promise { + return new Promise((resolve) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + let stdout = ""; + let stderr = ""; + const kinds: string[] = []; + if (child.stdout) { + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (c: any) => { + kinds.push(typeof c); + stdout += c; + }); + } + const err = child.stderr; + if (err !== null) { + err.setEncoding("utf8"); + err.on("data", (c: unknown) => { + kinds.push(typeof c); + stderr += String(c); + }); + } + child.on("close", (code) => { + report(code, kinds, stdout, stderr); + resolve(); + }); + }); +} + +function runRaw(script: string): Promise { + return new Promise((resolve) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + let stdout = ""; + let stderr = ""; + const kinds: string[] = []; + child.stdout?.on("data", (c: unknown) => { + kinds.push(Buffer.isBuffer(c) ? "buffer" : typeof c); + stdout += String(c); + }); + if (child.stderr) { + child.stderr.on("data", (c: any) => { + const chunk = String(c); + kinds.push(Buffer.isBuffer(c) ? "buffer" : typeof c); + stderr += chunk; + }); + } + child.on("close", (code) => { + report(code, kinds, stdout, stderr); + resolve(); + }); + }); +} + +// setEncoding through one const alias of the stream and the listener +// through another: both name child.stdout, so chunks are strings. +function runAliased(script: string): Promise { + return new Promise((resolve) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + const encoded = child.stdout!; + const listened = child.stdout!; + let stdout = ""; + const kinds: string[] = []; + encoded.setEncoding("utf8"); + listened.on("data", (c: unknown) => { + kinds.push(typeof c); + stdout += String(c); + }); + child.on("close", (code) => { + report(code, kinds, stdout, ""); + resolve(); + }); + }); +} + +runEncoded("printf 'h\\303\\251llo'; echo warn >&2") + .then(() => runRaw("printf raw; echo oops >&2; exit 2")) + .then(() => runAliased("printf alias")) + .then(() => console.log("done")); diff --git a/tests/corpus/6472-namespace-destructuring/lib.ts b/tests/corpus/6472-namespace-destructuring/lib.ts new file mode 100644 index 000000000..d25d53d74 --- /dev/null +++ b/tests/corpus/6472-namespace-destructuring/lib.ts @@ -0,0 +1,21 @@ +export function add(a: number, b: number): number { + return a + b; +} + +export const label = "lib"; + +export let counter = 1; + +export function bump(): void { + counter++; +} + +export class Shape { + side: number; + constructor(side: number) { + this.side = side; + } + area(): number { + return this.side * this.side; + } +} diff --git a/tests/corpus/6472-namespace-destructuring/main.ts b/tests/corpus/6472-namespace-destructuring/main.ts new file mode 100644 index 000000000..7aa00a2d1 --- /dev/null +++ b/tests/corpus/6472-namespace-destructuring/main.ts @@ -0,0 +1,29 @@ +// Destructuring a program module's namespace, statically +// (`import * as ns`) and from a dynamic import: each element reads the +// export at the pattern's position — renames and defaults included — and +// binds a copy of its value (a later reassignment of an exported `let` is +// visible through the namespace, not through the binding). An annotation +// naming another module or an object type does not change which module's +// exports are read. +import * as ns from "./lib.ts"; +import type * as shape from "./shape.ts"; + +const { add, label, counter, bump, Shape: Ctor } = ns; +console.log(add(2, 3), label, counter); +bump(); +console.log(counter, ns.counter); +console.log(new Ctor(4).area()); + +async function main(): Promise { + const { add: plus, label: name = "unused" } = await import("./lib.ts"); + console.log(plus(10, 20), name); + const { add: annotatedAdd, label: annotated }: typeof shape = await import("./lib.ts"); + console.log(annotatedAdd(10, 20), annotated); +} + +const { add: shapeAdd, label: shapeLabel }: typeof shape = ns; +console.log(shapeAdd(2, 3), shapeLabel); +const { label: viaObject }: { label: string } = ns; +console.log(viaObject); + +main(); diff --git a/tests/corpus/6472-namespace-destructuring/shape.ts b/tests/corpus/6472-namespace-destructuring/shape.ts new file mode 100644 index 000000000..96adaffd3 --- /dev/null +++ b/tests/corpus/6472-namespace-destructuring/shape.ts @@ -0,0 +1,7 @@ +// A module whose exports have the same names and types as some of +// lib.ts's, so `typeof import("./shape.ts")` accepts lib.ts's namespace. +export const label: string = "shape"; + +export function add(a: number, b: number): number { + return a * b; +} diff --git a/tests/corpus/6473-any-parameter-values.ts b/tests/corpus/6473-any-parameter-values.ts new file mode 100644 index 000000000..399332551 --- /dev/null +++ b/tests/corpus/6473-any-parameter-values.ts @@ -0,0 +1,23 @@ +// Function values whose defaulted parameter the checker types `any`: under +// an `(...args: any[]) => any` wrapper slot the contextual `any` wins over +// the default's type. The callable's slot is the checked-dynamic value the +// body already reads. +function traced any>(name: string, fn: F): F { + console.log("wrap", name); + return fn; +} + +function configured(raw = process.env["SCRIPTC_TEST_UNSET_WIDTH"]): number | undefined { + const width = Number.parseInt(raw ?? "", 10); + return Number.isSafeInteger(width) && width > 0 ? width : undefined; +} + +const layout = traced("layout", (width = configured() ?? 80) => { + return { width, gutter: 2 }; +}); +console.log(JSON.stringify(layout()), JSON.stringify(layout(120))); + +const scaled = traced("scaled", function (factor = 2, base = 10) { + return base * factor; +}); +console.log(scaled(), scaled(3), scaled(undefined, 1)); diff --git a/tests/corpus/6473-unknown-spread-into-index-record.ts b/tests/corpus/6473-unknown-spread-into-index-record.ts new file mode 100644 index 000000000..c5cd4f6ad --- /dev/null +++ b/tests/corpus/6473-unknown-spread-into-index-record.ts @@ -0,0 +1,21 @@ +// Spreading an unknown value into an index-signature record copies its own +// keys in order and spreads nothing for null and undefined. +function build(extra: unknown, attempt: number): Record { + return { + "content-type": "application/json", + ...(extra as Record), + "x-attempt": String(attempt), + }; +} +function headersOf(params: { headers?: T }, attempt: number): Record { + return { + ...params.headers, + "x-attempt": String(attempt), + }; +} +const parsed: unknown = JSON.parse('{"authorization":"t","content-type":"text/plain"}'); +console.log(build(parsed, 1)); +console.log(headersOf({ headers: parsed }, 3), headersOf({}, 4)); +const nothing: unknown = JSON.parse("null"); +const five: unknown = JSON.parse("5"); +console.log(headersOf({ headers: nothing }, 5), headersOf({ headers: five }, 6)); diff --git a/tests/corpus/6474-async-any-callbacks/main.ts b/tests/corpus/6474-async-any-callbacks/main.ts new file mode 100644 index 000000000..c84ce37ba --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/main.ts @@ -0,0 +1,41 @@ +// Async callbacks whose result the checker types `Promise` handed to +// a JavaScript library typed `(...) => A | Promise` (A = any collapses +// the slot to `any`): the callback value still returns a promise, and its +// payload is the checked-dynamic value the body produced. +import retry from "retry-lite"; + +function loose(attempt: number): any { + if (attempt < 3) throw new Error(`attempt ${attempt} failed`); + return { text: `ok after ${attempt}`, size: attempt * 10 }; +} + +async function main(): Promise { + const got = await retry(async (_bail, attempt) => loose(attempt), { + retries: 5, + onRetry: (e: Error) => console.log("retry:", e.message), + }); + console.log(got.text, got.size); + + const parsed = await retry( + async () => { + const value = JSON.parse('{"items":[1,2,3]}'); + return value; + }, + { retries: 1 }, + ); + console.log(parsed.items.length); + + try { + await retry( + async (bail) => { + bail(new Error("gave up")); + throw new Error("ignored"); + }, + { retries: 2 }, + ); + } catch (e) { + console.log("caught:", (e as Error).message); + } +} + +main(); diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts new file mode 100644 index 000000000..896f81f9d --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.d.ts @@ -0,0 +1,15 @@ +declare function retryLite( + fn: retryLite.RetryFunction, + opts: retryLite.Options +): Promise; + +declare namespace retryLite { + interface Options { + retries?: number; + onRetry?: (e: Error) => any; + } + + type RetryFunction = (bail: (e: Error) => void, attempt: number) => A | Promise; +} + +export = retryLite; diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js new file mode 100644 index 000000000..a1ae718ab --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/index.js @@ -0,0 +1,36 @@ +"use strict"; + +function retryLite(fn, opts) { + const retries = opts.retries === undefined ? 3 : opts.retries; + return new Promise((resolve, reject) => { + let attempt = 0; + let bailed = false; + const bail = (err) => { + bailed = true; + reject(err); + }; + const onError = (err) => { + if (bailed) return; + if (attempt > retries) { + reject(err); + return; + } + if (opts.onRetry) opts.onRetry(err); + runAttempt(); + }; + const runAttempt = () => { + attempt++; + let val; + try { + val = fn(bail, attempt); + } catch (err) { + onError(err); + return; + } + Promise.resolve(val).then(resolve).catch(onError); + }; + runAttempt(); + }); +} + +module.exports = retryLite; diff --git a/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json new file mode 100644 index 000000000..251988634 --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/node_modules/retry-lite/package.json @@ -0,0 +1,6 @@ +{ + "name": "retry-lite", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6474-async-any-callbacks/tsconfig.json b/tests/corpus/6474-async-any-callbacks/tsconfig.json new file mode 100644 index 000000000..9297bdb5b --- /dev/null +++ b/tests/corpus/6474-async-any-callbacks/tsconfig.json @@ -0,0 +1,3 @@ +{ + "compilerOptions": { "strict": true, "types": ["node"], "esModuleInterop": true } +} diff --git a/tests/corpus/6474-index-record-spread-literals.ts b/tests/corpus/6474-index-record-spread-literals.ts new file mode 100644 index 000000000..59e1603a9 --- /dev/null +++ b/tests/corpus/6474-index-record-spread-literals.ts @@ -0,0 +1,59 @@ +// An object literal that spreads an index-signature record holds every key +// the spread copied, in insertion order, though the checker types it by its +// explicit members only. A conditional spread that does not apply adds no +// key. +function bucket(n: number | undefined): string { + return n === undefined ? "unknown" : n < 100 ? "small" : "large"; +} +function emit(name: string, tags: Record): void { + console.log(name, JSON.stringify(tags), Object.keys(tags).length); +} +function measure(base: Record, count: number | undefined, ok: boolean) { + const metricTags = { + ...base, + fileCountBucket: bucket(count), + status: ok ? "succeeded" : "failed", + }; + emit("files", metricTags); + console.log(metricTags.status.toUpperCase(), metricTags.fileCountBucket); + return metricTags; +} +const base = { plan: "pro", region: "iad1", status: "pending" }; +const m = measure(base, 42, true); +console.log(Object.entries(m)); +function walker(enable: boolean, metricTags: Record = {}) { + const tags = { + plan: "unknown", + ...metricTags, + largeCliDplPerformance: String(enable), + walkerFallback: "false", + }; + tags.walkerFallback = "true"; + console.log(tags, tags.plan); +} +walker(true, { plan: "hobby", extra: "x" }); +walker(false); +function counts(tags: Record, n: number) { + const t = { ...tags, attempts: n, retried: n > 1 }; + console.log(t, t.attempts + 1, t.retried); +} +counts({ a: "1" }, 2); +function depthTags(deep: boolean): { depth: number | undefined; kind: "shallow" | "deep"; useDeep: boolean } { + return { depth: deep ? undefined : 1, kind: deep ? "deep" : "shallow", useDeep: deep }; +} +function attempt(base: Record, n: number, deep: boolean, bytes: number | undefined) { + const tags = { + ...base, + git_attempt: n, + repo: "r", + ...(bytes !== undefined && { packBytes: bytes }), + ...depthTags(deep), + }; + console.log(JSON.stringify(tags), Object.keys(tags).join(","), tags.git_attempt, tags.useDeep); +} +attempt({ plan: "pro" }, 1, false, 10); +attempt({ plan: "pro", repo: "x" }, 2, true, undefined); +function addTags(tags: Record): void { + console.log(Object.entries(tags).map(([k, v]) => `${k}=${String(v)}`).join(" ")); +} +addTags({ ...{ plan: "pro", team: "t" } as Record, count: 3, ok: true }); diff --git a/tests/corpus/6475-dynamic-spread-literals.ts b/tests/corpus/6475-dynamic-spread-literals.ts new file mode 100644 index 000000000..eaf76598e --- /dev/null +++ b/tests/corpus/6475-dynamic-spread-literals.ts @@ -0,0 +1,63 @@ +// Object literals spreading index-signature records whose members are not +// all scalars, or whose spread values are unknown, are checked-dynamic +// objects built like JavaScript's: each spread copies its own keys in +// order, conditional spreads add nothing when they do not apply, and later +// keyed writes land on the same object. +function flatten(obj: unknown, prefix: string): Record { + const out: Record = {}; + if (typeof obj === "object" && obj !== null) { + for (const [k, v] of Object.entries(obj)) out[`${prefix}.${k}`] = v; + } + return out; +} +function summary(status: string, ms: number, bytes: number | undefined, err: string | undefined) { + const milestones = { connect: 3, done: 9 }; + const resultTags = { + "git.fetch.status": status, + "git.fetch.durationMs": ms, + ...flatten(milestones, "git.fetch"), + ...(bytes !== undefined && { "git.fetch.packBytes": bytes }), + ...(err !== undefined && { "git.fetch.error": err }), + }; + console.log(JSON.stringify({ event: "git.fetch.summary", version: "v1", ...resultTags })); +} +summary("ok", 12, 100, undefined); +summary("failed", 3, undefined, "boom"); +type SendOptions = { method: string; headersTimeout?: number; signal?: AbortSignal }; +function send(url: string, options: SendOptions): string { + return `${url} ${options.method} ${options.headersTimeout ?? "-"} ${options.signal ? options.signal.aborted : "none"}`; +} +function callWith(timeout: number | undefined, attemptTimeout: number | undefined): string { + return send("u", { + headersTimeout: timeout, + method: "GET", + ...(attemptTimeout && { signal: AbortSignal.timeout(attemptTimeout) }), + }); +} +console.log(callWith(5, 1000)); +console.log(callWith(undefined, undefined)); +console.log(callWith(5, 0)); +function report(tags: string[], from: string, logs: [string, string][]) { + const ddTags = { + "errClass.from": from, + "errClass.tagLabels": Array.from(new Set(tags)).sort(), + "errClass.tags": "", + ...tags.reduce((obj: Record, tag: string) => { + obj[`errClass.tags.${tag}`] = true; + return obj; + }, {}), + "errClass.logLinesSent": logs.length, + "errClass.lastRule": "none", + }; + if (tags.length > 1) { + ddTags["errClass.lastRule"] = "rule-2"; + } + for (const tag of ["late"]) { + // @ts-expect-error -- keys outside the literal's declared ones + ddTags[`errClass.tags.${tag}`] = true; + } + ddTags["errClass.tags"] = tags.join(","); + console.log(JSON.stringify(ddTags), ddTags["errClass.logLinesSent"] + 1); +} +report(["b", "a"], "build", [["stdout", "x"]]); +report([], "init", []); diff --git a/tests/corpus/6475-js-promise-resolve-adoption/main.ts b/tests/corpus/6475-js-promise-resolve-adoption/main.ts new file mode 100644 index 000000000..61a58dbe2 --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/main.ts @@ -0,0 +1,23 @@ +// JavaScript's Promise.resolve over a checked-dynamic value that holds a +// promise (a TypeScript async callback's result handed back into a +// JavaScript library) answers that same promise — adopted, not wrapped — +// and wraps plain values. +import { sameAfterResolve, viaExecutor, viaResolve, viaThen } from "calls"; + +async function main(): Promise { + console.log(await viaResolve(async (): Promise => "resolved")); + console.log(await viaThen(async (): Promise => 42)); + console.log(await viaExecutor(async (): Promise => "executor")); + console.log(await viaExecutor((): string => "plain")); + try { + await viaExecutor(async (): Promise => { + throw new Error("callback failed"); + }); + } catch (e) { + console.log("caught:", (e as Error).message); + } + const pending = Promise.resolve("p"); + console.log(sameAfterResolve(pending), sameAfterResolve("p")); +} + +main(); diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts new file mode 100644 index 000000000..d1040a227 --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.d.ts @@ -0,0 +1,4 @@ +export declare function viaResolve(fn: () => A | Promise): Promise; +export declare function viaThen(fn: () => A | Promise): Promise; +export declare function viaExecutor(fn: () => A | Promise): Promise; +export declare function sameAfterResolve(value: unknown): boolean; diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js new file mode 100644 index 000000000..fb55d867c --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/index.js @@ -0,0 +1,21 @@ +"use strict"; + +function viaResolve(fn) { + return Promise.resolve(fn()); +} + +function viaThen(fn) { + return Promise.resolve(fn()).then((value) => value); +} + +function viaExecutor(fn) { + return new Promise((resolve, reject) => { + Promise.resolve(fn()).then(resolve).catch(reject); + }); +} + +function sameAfterResolve(value) { + return Promise.resolve(value) === value; +} + +module.exports = { viaResolve, viaThen, viaExecutor, sameAfterResolve }; diff --git a/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json new file mode 100644 index 000000000..c85cd1f4b --- /dev/null +++ b/tests/corpus/6475-js-promise-resolve-adoption/node_modules/calls/package.json @@ -0,0 +1,6 @@ +{ + "name": "calls", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6480-boolean-callbacks.ts b/tests/corpus/6480-boolean-callbacks.ts new file mode 100644 index 000000000..b9ac4b4ab --- /dev/null +++ b/tests/corpus/6480-boolean-callbacks.ts @@ -0,0 +1,34 @@ +// The ambient Boolean as an array callback beyond filter: every, some, +// find, findIndex and map call it with the element first, so each answers +// ToBoolean of the element (later arguments are ignored). filter(Boolean) +// also covers elements the checker types `any`. +const flags = [true, true, false]; +console.log(flags.every(Boolean), flags.some(Boolean), flags.slice(0, 2).every(Boolean)); +const counts = [0, 3, 0, 7]; +console.log(counts.find(Boolean), counts.findIndex(Boolean), counts.map(Boolean).join(",")); +const names = ["", "a", "", "b"]; +console.log(names.some(Boolean), names.every(Boolean), names.findIndex(Boolean)); +const maybe: (string | undefined)[] = [undefined, "x", undefined]; +console.log(maybe.some(Boolean), maybe.find(Boolean), maybe.map(Boolean).join(",")); + +// Elements the checker types `any` keep their runtime kind; filter(Boolean) +// answers their runtime truthiness. +function untyped(value: unknown): any { + return value; +} +const configFiles = [untyped("app.json"), untyped(""), untyped(undefined), untyped("site.json")].filter( + Boolean, +) as string[]; +console.log(configFiles.length, configFiles.join(",")); + +async function check(n: number): Promise { + return n % 2 === 1; +} + +async function main(): Promise { + const all = (await Promise.all([1, 3, 5].map(async (n) => check(n)))).every(Boolean); + const any = (await Promise.all([2, 4].map(async (n) => check(n)))).some(Boolean); + console.log(all, any); +} + +main(); diff --git a/tests/corpus/6480-catch-typeof-untyped-throws.ts b/tests/corpus/6480-catch-typeof-untyped-throws.ts new file mode 100644 index 000000000..98122f851 --- /dev/null +++ b/tests/corpus/6480-catch-typeof-untyped-throws.ts @@ -0,0 +1,40 @@ +// Values thrown from `unknown` storage read back through typeof tests on +// the catch binding, like the same values thrown with static types. +const thrown: unknown[] = [5, 0, -1.5, "text", "", true, false, null, undefined, { kind: "box" }]; +function describe(value: unknown): string { + try { + throw value; + } catch (e) { + const same = e === value; + if (typeof e === "number") return `${same} number ${e + 1}`; + if (typeof e === "string") return `${same} string ${e.length}`; + if (typeof e === "boolean") return `${same} boolean ${!e}`; + if (typeof e === "object") return `${same} object ${e === null ? "null" : "value"}`; + return `${same} ${typeof e}`; + } +} +for (const value of thrown) console.log(describe(value)); +function rethrow(value: unknown): void { + try { + throw value; + } catch (e) { + if (typeof e !== "string") throw e; + console.log("kept string", e); + } +} +for (const value of ["s", 7, false]) { + try { + rethrow(value); + } catch (e) { + console.log("rethrown", typeof e, String(e)); + } +} +let count = 0; +for (const value of thrown) { + try { + throw value; + } catch (e) { + if (typeof e === "number" && e > 0) count += e; + } +} +console.log(count); diff --git a/tests/corpus/6480-generic-callback-extended-results.ts b/tests/corpus/6480-generic-callback-extended-results.ts new file mode 100644 index 000000000..0d75a1f53 --- /dev/null +++ b/tests/corpus/6480-generic-callback-extended-results.ts @@ -0,0 +1,41 @@ +// A callback whose returns extend the elements it was given keeps the +// added members once its result type names them (an annotation on the +// callback or on the type argument), as Node keeps them on the objects. +type Plain = { name: string; n: number }; +type Item = Plain & { extra?: boolean }; +const plain: Plain[] = [ + { name: "a", n: 1 }, + { name: "b", n: 2 }, +]; + +function run(fn: () => T): T { + return fn(); +} +async function runAsync(fn: () => Promise): Promise { + return fn(); +} + +const annotated = run((): Item[] => { + if (plain.length === 0) return plain; + return plain.map((x) => ({ ...x, extra: x.n > 1 })); +}); +console.log(JSON.stringify(annotated)); + +const viaTypeArgument = run(() => { + if (plain.length > 5) return plain; + return plain.map((x) => ({ ...x, extra: true })); +}); +console.log(JSON.stringify(viaTypeArgument), viaTypeArgument[0]!.extra); + +runAsync(async (): Promise => { + if (plain.length === 0) return plain; + return plain.map((x) => ({ ...x, extra: false })); +}).then((items) => console.log(items)); + +// Callbacks whose results the checker types by their own returns keep +// every member already. +const mapped = plain.map((x) => (x.n > 1 ? x : { ...x, extra: true })); +const asItems: Item[] = mapped; +console.log(JSON.stringify(asItems)); +const summed = plain.reduce((acc, x) => ({ ...acc, total: acc.total + x.n }), { total: 0, label: "sum" }); +console.log(JSON.stringify(summed)); diff --git a/tests/corpus/6480-node-stream-dyn-readable/main.ts b/tests/corpus/6480-node-stream-dyn-readable/main.ts new file mode 100644 index 000000000..21d7bf2b1 --- /dev/null +++ b/tests/corpus/6480-node-stream-dyn-readable/main.ts @@ -0,0 +1,72 @@ +// NodeJS.ReadableStream slots hold native streams and JavaScript stream +// objects alike: a plain object implementing on/pipe, a constructor-function +// stream, and a native Readable pass through a TypeScript function typed +// NodeJS.ReadableStream. TypeScript consumes them with on("data") and +// pipe() into a native Writable, JavaScript consumes the returned values +// (the same objects), and a native Readable typed NodeJS.ReadableStream +// pipes into JavaScript sinks through Node's pipe protocol. +import { Readable, Writable } from "node:stream"; +import { jsCollect, plainSink, plainSource, same, sink, source } from "./sources.js"; + +function pass(stream: NodeJS.ReadableStream): NodeJS.ReadableStream { + return stream; +} + +async function collect(label: string, stream: NodeJS.ReadableStream): Promise { + const parts: string[] = []; + await new Promise((resolve) => { + stream.on("data", (chunk: Buffer | string) => parts.push(chunk.toString())); + stream.on("end", () => resolve()); + }); + console.log(label, "on data", parts.join("")); +} + +async function pipeInto(label: string, stream: NodeJS.ReadableStream): Promise { + const parts: string[] = []; + await new Promise((resolve) => { + const native = new Writable({ + write(chunk: Buffer, _encoding, done) { + parts.push(chunk.toString()); + done(); + }, + }); + native.on("finish", () => resolve()); + stream.pipe(native); + }); + console.log(label, "pipe", parts.join("")); +} + +async function main(): Promise { + await collect("plain", pass(plainSource(["p1", "p2"]))); + await collect("constructor", pass(source(["s1", "s2"]))); + await collect("native", pass(Readable.from(["n1", "n2"]))); + + await pipeInto("plain", pass(plainSource(["p3", "p4"]))); + await pipeInto("constructor", pass(source(["s3", "s4"]))); + await pipeInto("native", pass(Readable.from(["n3", "n4"]))); + + console.log("plain js", await jsCollect(pass(plainSource(["p5", "p6"])))); + console.log("constructor js", await jsCollect(pass(source(["s5", "s6"])))); + console.log("native js", await jsCollect(pass(Readable.from(["n5", "n6"])))); + + const plain = plainSource(["p7"]); + const made = source(["s7"]); + const native = Readable.from(["n7"]); + console.log("identity", same(plain, pass(plain)), same(made, pass(made)), same(native, pass(native))); + + await new Promise((resolve) => { + pass(Readable.from(["n8", "n9"])).pipe(plainSink("native into plain")); + pass(plainSource(["p8"])).pipe(plainSink("plain into plain")); + setTimeout(resolve, 20); + }); + + await new Promise((resolve) => { + const target = sink("native into constructor"); + target.on("pipe", () => console.log("sink heard pipe")); + target.on("unpipe", () => console.log("sink heard unpipe")); + target.on("finish", () => setImmediate(resolve)); + pass(Readable.from(["n10", "n11"])).pipe(target); + }); +} + +void main(); diff --git a/tests/corpus/6480-node-stream-dyn-readable/sources.js b/tests/corpus/6480-node-stream-dyn-readable/sources.js new file mode 100644 index 000000000..3b520c29f --- /dev/null +++ b/tests/corpus/6480-node-stream-dyn-readable/sources.js @@ -0,0 +1,138 @@ +// JavaScript stream objects that are not native streams: a plain object +// implementing on/pipe, and a constructor function with its own listener +// table. Sinks: a plain object with write/end, and a constructor function +// that keeps listeners, so a native pipe() into it runs Node's protocol. + +/** @returns {any} */ +export function plainSource(chunks) { + const listeners = {}; + const source = { + on(event, listener) { + (listeners[event] ??= []).push(listener); + return source; + }, + emit(event, value) { + for (const listener of listeners[event] ?? []) listener(value); + }, + pipe(dest) { + source.on("data", (chunk) => dest.write(chunk)); + source.on("end", () => dest.end()); + return dest; + }, + }; + setImmediate(() => { + for (const chunk of chunks) source.emit("data", chunk); + source.emit("end"); + }); + return source; +} + +function Source(chunks) { + this.chunks = chunks; + this.listeners = {}; + this.readable = true; + setImmediate(() => { + for (const chunk of this.chunks) this.emit("data", chunk); + this.emit("end"); + }); +} +Source.prototype.on = function (event, listener) { + (this.listeners[event] = this.listeners[event] || []).push(listener); + return this; +}; +Source.prototype.emit = function (event, value) { + for (const listener of this.listeners[event] || []) listener(value); + return true; +}; +Source.prototype.pipe = function (dest) { + this.on("data", (chunk) => dest.write(chunk)); + this.on("end", () => dest.end()); + return dest; +}; + +/** @returns {any} */ +export function source(chunks) { + return new Source(chunks); +} + +/** @returns {any} */ +export function plainSink(label) { + const parts = []; + return { + write(chunk) { + parts.push(String(chunk)); + return true; + }, + end() { + console.log(label, "sink end", parts.join("")); + }, + on() { + return this; + }, + once() { + return this; + }, + emit() { + return false; + }, + }; +} + +function Sink(label) { + this.label = label; + this.parts = []; + this.listeners = []; + this.writable = true; +} +Sink.prototype.on = function (event, listener) { + this.listeners.push({ event, listener, once: false }); + return this; +}; +Sink.prototype.once = function (event, listener) { + this.listeners.push({ event, listener, once: true }); + return this; +}; +Sink.prototype.prependListener = function (event, listener) { + this.listeners.unshift({ event, listener, once: false }); + return this; +}; +Sink.prototype.removeListener = function (event, listener) { + this.listeners = this.listeners.filter((l) => l.event !== event || l.listener !== listener); + return this; +}; +Sink.prototype.listenerCount = function (event) { + return this.listeners.filter((l) => l.event === event).length; +}; +Sink.prototype.emit = function (event, ...args) { + const matching = this.listeners.filter((l) => l.event === event); + this.listeners = this.listeners.filter((l) => l.event !== event || !l.once); + for (const l of matching) l.listener(...args); + return matching.length > 0; +}; +Sink.prototype.write = function (chunk) { + this.parts.push(String(chunk)); + return true; +}; +Sink.prototype.end = function () { + console.log(this.label, "ended with", this.parts.join("|"), "listening to", this.listeners.map((l) => l.event).join(",")); + this.emit("finish"); + console.log(this.label, "after finish listening to", this.listeners.map((l) => l.event).join(",")); +}; + +/** @returns {any} */ +export function sink(label) { + return new Sink(label); +} + +/** @returns {Promise} */ +export function jsCollect(stream) { + return new Promise((resolve) => { + const parts = []; + stream.on("data", (chunk) => parts.push(String(chunk))); + stream.on("end", () => resolve(parts.join(""))); + }); +} + +export function same(a, b) { + return a === b; +} diff --git a/tests/corpus/6481-enum-runtime-keys.ts b/tests/corpus/6481-enum-runtime-keys.ts new file mode 100644 index 000000000..183d509b2 --- /dev/null +++ b/tests/corpus/6481-enum-runtime-keys.ts @@ -0,0 +1,29 @@ +// @transform-types +// Enum member lookup through a runtime string key (`E[name as keyof +// typeof E]`): each member name answers its constant — string, numeric and +// renamed members alike — and a name no member carries reads undefined. +enum Color { + red = "red", + "dark-blue" = "dark-blue", + green = "light-green", +} + +enum Level { + low, + mid = 5, + high, +} + +function color(name: string | undefined): Color | undefined { + return name ? Color[name as keyof typeof Color] : undefined; +} + +for (const name of ["red", "dark-blue", "green", "light-green", "", undefined]) { + console.log(JSON.stringify(name), JSON.stringify(color(name))); +} + +const levelName = process.argv.length > 99 ? "unused" : "high"; +const level: Level | undefined = Level[levelName as keyof typeof Level]; +console.log(level, level === Level.high); +const missing: Level | undefined = Level[("med" + "ium") as keyof typeof Level]; +console.log(missing === undefined); diff --git a/tests/corpus/6481-node-stream-dyn-writable/main.ts b/tests/corpus/6481-node-stream-dyn-writable/main.ts new file mode 100644 index 000000000..9ac429b8f --- /dev/null +++ b/tests/corpus/6481-node-stream-dyn-writable/main.ts @@ -0,0 +1,67 @@ +// NodeJS.WritableStream slots hold a native Writable, a plain JavaScript +// sink and process.stdout: TypeScript writes and ends them, and a copy +// helper typed over both interfaces pipes native and JavaScript sources +// into native and JavaScript destinations. +import { Readable, Writable } from "node:stream"; +import { plainSink, plainSource } from "./sources.js"; + +function emit(label: string, out: NodeJS.WritableStream): void { + const ok = out.write(`${label} one\n`); + out.write(`${label} two\n`); + console.log(label, "write answered", typeof ok); +} + +function finish(out: NodeJS.WritableStream): void { + out.end(); +} + +function copy(src: NodeJS.ReadableStream, dst: NodeJS.WritableStream): NodeJS.WritableStream { + return src.pipe(dst); +} + +function collector(parts: string[]): Writable { + return new Writable({ + write(chunk: Buffer, _encoding, done) { + parts.push(chunk.toString()); + done(); + }, + }); +} + +async function main(): Promise { + const parts: string[] = []; + const native = collector(parts); + const finished = new Promise((resolve) => native.on("finish", () => resolve())); + emit("native", native); + finish(native); + await finished; + console.log("native got", JSON.stringify(parts.join(""))); + + const sink = plainSink("plain"); + emit("plain", sink); + finish(sink); + + emit("stdout", process.stdout); + + const target = plainSink("copy native into plain"); + console.log("copy returns target", copy(Readable.from(["c1", "c2"]), target) === target); + await new Promise((resolve) => setTimeout(resolve, 10)); + copy(plainSource(["c3", "c4"]), plainSink("copy plain into plain")); + await new Promise((resolve) => setTimeout(resolve, 10)); + const copied: string[] = []; + const native2 = collector(copied); + await new Promise((resolve) => { + native2.on("finish", () => resolve()); + copy(plainSource(["c5", "c6"]), native2); + }); + console.log("copy plain into native", copied.join("")); + const copiedNative: string[] = []; + const native3 = collector(copiedNative); + await new Promise((resolve) => { + native3.on("finish", () => resolve()); + copy(Readable.from(["c7", "c8"]), native3); + }); + console.log("copy native into native", copiedNative.join("")); +} + +void main(); diff --git a/tests/corpus/6481-node-stream-dyn-writable/sources.js b/tests/corpus/6481-node-stream-dyn-writable/sources.js new file mode 100644 index 000000000..9c8491455 --- /dev/null +++ b/tests/corpus/6481-node-stream-dyn-writable/sources.js @@ -0,0 +1,49 @@ +// A plain JavaScript source implementing on/pipe and a plain sink with +// write/end. + +/** @returns {any} */ +export function plainSource(chunks) { + const listeners = {}; + const source = { + on(event, listener) { + (listeners[event] ??= []).push(listener); + return source; + }, + emit(event, value) { + for (const listener of listeners[event] ?? []) listener(value); + }, + pipe(dest) { + source.on("data", (chunk) => dest.write(chunk)); + source.on("end", () => dest.end()); + return dest; + }, + }; + setImmediate(() => { + for (const chunk of chunks) source.emit("data", chunk); + source.emit("end"); + }); + return source; +} + +/** @returns {any} */ +export function plainSink(label) { + const parts = []; + return { + write(chunk) { + parts.push(String(chunk)); + return true; + }, + end() { + console.log(label, "sink end", parts.join("")); + }, + on() { + return this; + }, + once() { + return this; + }, + emit() { + return false; + }, + }; +} diff --git a/tests/corpus/6482-node-stream-dyn-helpers/main.ts b/tests/corpus/6482-node-stream-dyn-helpers/main.ts new file mode 100644 index 000000000..db1c686a9 --- /dev/null +++ b/tests/corpus/6482-node-stream-dyn-helpers/main.ts @@ -0,0 +1,98 @@ +// The helper shapes real code writes around NodeJS.ReadableStream, fed by +// native and JavaScript streams: an async function returning one of +// several producers, a JavaScript consumer of its result, a part piped +// into a PassThrough with { end: false } and unpiped, pause/resume/ +// setEncoding/readable, an optional destroy through an intersection type, +// for await and pipeline over native values in the slot. +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Readable, Writable } from "node:stream"; +import { pipeline } from "node:stream/promises"; +import * as zlib from "node:zlib"; +import { openPart, part, streamToText } from "./sources.js"; + +async function open(kind: string, file: string): Promise { + if (kind === "file") return fs.createReadStream(file); + return openPart(["x", "y"]); +} + +async function pipePart(item: NodeJS.ReadableStream, out: PassThrough): Promise { + item.pipe(out, { end: false }); + await new Promise((resolve) => item.on("end", () => resolve())); + item.unpipe(out); +} + +function pass(stream: NodeJS.ReadableStream): NodeJS.ReadableStream { + return stream; +} + +function stop(stream: NodeJS.ReadableStream): void { + (stream as NodeJS.ReadableStream & { destroy?: () => void }).destroy?.(); +} + +async function iterate(label: string, stream: NodeJS.ReadableStream): Promise { + let text = ""; + for await (const chunk of stream) text += chunk.toString(); + console.log(label, "iterated", text); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-stream-dyn-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "file text"); + console.log("file", await streamToText(await open("file", file))); + console.log("part", await streamToText(await open("part", file))); + + const out = new PassThrough(); + const parts: string[] = []; + out.on("data", (chunk: Buffer) => parts.push(chunk.toString())); + await pipePart(await open("part", file), out); + await pipePart(await open("file", file), out); + const ended = new Promise((resolve) => out.on("end", () => resolve())); + out.end(); + await ended; + console.log("out", parts.join("|")); + + const js = pass(part(["j1"])); + js.pause(); + js.resume(); + js.setEncoding("utf8"); + console.log("js readable", js.readable); + await new Promise((resolve) => js.on("end", () => resolve())); + const native = pass(Readable.from(["n1"])); + native.setEncoding("utf8"); + console.log("native readable", native.readable); + const chunks: string[] = []; + await new Promise((resolve) => { + native.on("data", (chunk: Buffer | string) => chunks.push(chunk.toString())); + native.on("end", () => resolve()); + }); + console.log("native chunks", chunks.join("")); + + const closing = pass(Readable.from(["c1"])); + closing.on("close", () => console.log("native closed")); + stop(pass(part(["d1"]))); + stop(closing); + + await iterate("native", pass(Readable.from(["i1", "i2"]))); + await iterate("file", pass(fs.createReadStream(file))); + + const packed = path.join(dir, "a.gz"); + fs.writeFileSync(packed, zlib.gzipSync(Buffer.from("packed text"))); + const unpacked: string[] = []; + await pipeline( + pass(fs.createReadStream(packed)), + zlib.createGunzip(), + new Writable({ + write(chunk: Buffer, _encoding, done) { + unpacked.push(chunk.toString()); + done(); + }, + }), + ); + console.log("pipeline", unpacked.join("")); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6482-node-stream-dyn-helpers/sources.js b/tests/corpus/6482-node-stream-dyn-helpers/sources.js new file mode 100644 index 000000000..c395b4379 --- /dev/null +++ b/tests/corpus/6482-node-stream-dyn-helpers/sources.js @@ -0,0 +1,71 @@ +// A constructor-function stream part with its own listener table, whose +// pipe() honors { end: false } and whose unpipe() detaches it, plus a +// JavaScript consumer that reads a stream to text. + +function Part(chunks) { + this.chunks = chunks; + this.listeners = {}; + this.dest = null; + this.ending = true; + this.readable = true; + setImmediate(() => { + for (const chunk of this.chunks) { + if (this.dest) this.dest.write(chunk); + else this.emit("data", chunk); + } + if (this.dest && this.ending) this.dest.end(); + this.emit("end"); + }); +} +Part.prototype.on = function (event, listener) { + (this.listeners[event] = this.listeners[event] || []).push(listener); + return this; +}; +Part.prototype.emit = function (event, value) { + for (const listener of this.listeners[event] || []) listener(value); + return true; +}; +Part.prototype.pipe = function (dest, options) { + this.dest = dest; + this.ending = !options || options.end !== false; + return dest; +}; +Part.prototype.unpipe = function () { + console.log("part unpipe"); + this.dest = null; + return this; +}; +Part.prototype.pause = function () { + console.log("part pause"); + return this; +}; +Part.prototype.resume = function () { + console.log("part resume"); + return this; +}; +Part.prototype.setEncoding = function (encoding) { + console.log("part setEncoding", encoding); + return this; +}; +Part.prototype.destroy = function () { + console.log("part destroy"); +}; + +/** @returns {Promise} */ +export async function openPart(chunks) { + return new Part(chunks); +} + +/** @returns {any} */ +export function part(chunks) { + return new Part(chunks); +} + +/** @returns {any} */ +export function streamToText(stream) { + return new Promise((resolve) => { + const parts = []; + stream.on("data", (chunk) => parts.push(String(chunk))); + stream.on("end", () => resolve(parts.join(""))); + }); +} diff --git a/tests/corpus/6482-promise-all-tuples.ts b/tests/corpus/6482-promise-all-tuples.ts new file mode 100644 index 000000000..3c5e38e29 --- /dev/null +++ b/tests/corpus/6482-promise-all-tuples.ts @@ -0,0 +1,97 @@ +// Promise.all over an array literal of DIFFERENT promise types (the +// checker's tuple overload): destructuring with holes and nested +// patterns, void entries, plain values, `any` entries that may or may not +// hold a promise or a thenable, tuples of more than ten entries, entry +// identity, and rejection — +// the first rejection in settlement order wins, later ones count as +// handled. +interface Version { + major: number; + range: string; +} + +class Scan { + tool: string; + schema: number | undefined; + constructor(tool: string, schema?: number) { + this.tool = tool; + this.schema = schema; + } +} + +const shared: Version = { major: 22, range: "22.x" }; +const scanned = new Scan("make", 9); + +async function version(): Promise { + return shared; +} +async function scan(): Promise { + return scanned; +} +async function patch(): Promise { + console.log("patched"); +} +async function creds(ok: boolean): Promise<{ key: string } | undefined> { + return ok ? { key: "k" } : undefined; +} +function delay(ms: number, value: T, fail?: string): Promise { + return new Promise((resolve, reject) => + setTimeout(() => (fail ? reject(new Error(fail)) : resolve(value)), ms), + ); +} + +// JavaScript-style results the checker types `any`: one holds a promise, +// one a plain value. +function untypedCount(): any { + return 41; +} +function untypedRecord(): any { + return Promise.resolve({ label: "from a promise" }); +} + +// A thenable that is not a native promise: Promise.all adopts its +// settlement, calling `then` one microtask after the call. +class Deferred { + then(resolve: (value: string) => void): void { + console.log("then called"); + resolve("adopted"); + } +} +function untypedThenable(): any { + return new Deferred(); +} + +async function main(): Promise { + const [runtime, { tool: kind, schema: level }] = await Promise.all([version(), scan()]); + console.log(runtime.major, kind, level, runtime === shared); + + const first = scan(); + const second = patch(); + const third = creds(true); + const [found, , c] = await Promise.all([first, second, third]); + console.log(found === scanned, c?.key); + + const both = await Promise.all([version(), 5, "x", creds(false)]); + console.log(both[0].range, both[1] + 1, both[2], both[3]); + + const [count, { label }, plain] = await Promise.all([untypedCount(), untypedRecord(), "p"]); + console.log(count + 1, label, plain); + + const pending = Promise.all([untypedThenable(), version()]); + console.log("after the call"); + const [adopted, latest] = await pending; + console.log(adopted, latest.major); + + const many = await Promise.all([version(), 1, 2, 3, 4, 5, 6, 7, 8, 9, "ten", scan()]); + console.log(many[0].major, many[2], many[9], many[10], many[11].tool); + + try { + await Promise.all([delay(20, 1, "slow failure"), delay(5, "s", "fast failure"), delay(1, true)]); + } catch (e) { + console.log("rejected:", (e as Error).message); + } + await delay(40, 0); + console.log("done"); +} + +main(); diff --git a/tests/corpus/6483-nullable-regex-receivers.ts b/tests/corpus/6483-nullable-regex-receivers.ts new file mode 100644 index 000000000..d6b190a30 --- /dev/null +++ b/tests/corpus/6483-nullable-regex-receivers.ts @@ -0,0 +1,23 @@ +// Regex-argument string methods on a receiver the checker types +// `string | undefined` (an unchecked index read, ts-ignored): a string +// takes the regex lowering, undefined throws Node's TypeError. +const lines = "100 /srv/node1/a.txt\n2048 /srv/node22/b/c.bin"; +const pairs = lines.split("\n").map((line) => { + const [count, file] = line.trim().split(/\s+/) as [string | undefined, string | undefined]; + // @ts-ignore possibly undefined + return `${count.replace(/0+$/, "")}:${file.replace(/^\/srv\/node\d+\//, "")}`; +}); +console.log(pairs); + +function parts(text: string): (string | undefined)[] { + return text.split(",").map((s) => (s === "" ? undefined : s)); +} + +for (const value of parts("a-b-c,")) { + try { + // @ts-ignore possibly undefined + console.log(value.replaceAll(/-/g, "+"), value.split(/-/).length, value.search(/c/)); + } catch (e) { + console.log(e instanceof TypeError, (e as Error).message); + } +} diff --git a/tests/corpus/6490-events-once-signal.ts b/tests/corpus/6490-events-once-signal.ts new file mode 100644 index 000000000..8535e1fec --- /dev/null +++ b/tests/corpus/6490-events-once-signal.ts @@ -0,0 +1,120 @@ +// events.once(emitter, name, { signal }) follows Node's lib/events.js: an +// abort removes the event's and the 'error' listener and rejects with an +// AbortError whose cause is signal.reason, an aborted signal rejects +// without registering anything, and a settled once removes its abort +// listener. The result settles on Node's microtask turn: once is an async +// function returning the executor's promise. +import { EventEmitter, once } from "node:events"; + +async function main(): Promise { + const ee = new EventEmitter(); + const ac = new AbortController(); + setTimeout(() => ee.emit("ready", 1, "two"), 1); + const args = await once(ee, "ready", { signal: ac.signal }); + console.log("fulfilled", JSON.stringify(args), ee.listenerCount("ready"), ee.listenerCount("error")); + + const ac2 = new AbortController(); + const pending = once(ee, "ready", { signal: ac2.signal }); + console.log("before abort", ee.listenerCount("ready"), ee.listenerCount("error")); + ac2.abort(); + console.log("after abort", ee.listenerCount("ready"), ee.listenerCount("error")); + try { + await pending; + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log("rejected", e.name, e.message, e.code, (e.cause as Error).name); + } + + const ac3 = new AbortController(); + ac3.abort(new Error("already")); + try { + await once(ee, "ready", { signal: ac3.signal }); + } catch (err) { + const e = err as Error; + console.log("pre-aborted", e.name, (e.cause as Error).message, ee.listenerCount("ready")); + } + + const ac4 = new AbortController(); + setTimeout(() => ee.emit("error", new Error("boom")), 1); + try { + await once(ee, "ready", { signal: ac4.signal }); + } catch (err) { + console.log("error event", (err as Error).message, ee.listenerCount("ready")); + } + ac4.abort(); + console.log("late abort changes nothing"); + + const errs = once(ee, "error", { signal: ac.signal }); + ee.emit("error", new Error("as value")); + const [first] = await errs; + console.log("error as value", (first as Error).message); + + // An undefined signal is the plain form. + const maybe: AbortSignal | undefined = undefined; + setTimeout(() => ee.emit("ready", 3, "four"), 1); + console.log("undefined signal", JSON.stringify(await once(ee, "ready", { signal: maybe }))); + + // Listeners registered before and after the once's abort listener. + const ac5 = new AbortController(); + const ee5 = new EventEmitter(); + ac5.signal.addEventListener("abort", () => { + console.log("first abort listener", ee5.listenerCount("z")); + }); + const p5 = once(ee5, "z", { signal: ac5.signal }); + ac5.signal.addEventListener("abort", () => { + console.log("second abort listener", ee5.listenerCount("z")); + }); + ac5.abort("why"); + await p5.catch((err: Error) => console.log("p5", err.name, String(err.cause))); + + // The abort's removals emit 'removeListener'. + const ee6 = new EventEmitter(); + ee6.on("removeListener", (name: string) => console.log("removed", name)); + const ac6 = new AbortController(); + const p6 = once(ee6, "w", { signal: ac6.signal }); + ac6.abort(); + await p6.catch(() => console.log("p6 rejected")); + + // A 'newListener' listener that throws rejects the promise. + const ee7 = new EventEmitter(); + ee7.on("newListener", (name: string) => { + if (name === "go") throw new Error("no " + name); + }); + await once(ee7, "go", { signal: new AbortController().signal }).then( + () => console.log("fulfilled"), + (err: Error) => console.log("newListener rejected", err.message, ee7.listenerCount("go")), + ); + + // Settlement turns against plain promise chains. + const ee8 = new EventEmitter(); + const ac8 = new AbortController(); + void once(ee8, "x").then(() => console.log("plain")); + void once(ee8, "x", { signal: ac8.signal }).then(() => console.log("signal")); + void Promise.resolve() + .then(() => console.log("t1")) + .then(() => console.log("t2")) + .then(() => console.log("t3")) + .then(() => console.log("t4")); + ee8.emit("x"); + const ac9 = new AbortController(); + void once(ee8, "y", { signal: ac9.signal }).then( + () => console.log("fulfilled"), + () => console.log("aborted"), + ); + ac9.abort(); + const ac10 = new AbortController(); + ac10.abort(); + void once(ee8, "y", { signal: ac10.signal }).then( + () => console.log("fulfilled"), + () => console.log("pre-aborted turn"), + ); + await new Promise((resolve) => setTimeout(resolve, 5)); + + // Pending onces whose signal never aborts keep their listeners. + const idle = new AbortController(); + void once(ee, "never", { signal: idle.signal }); + void once(new EventEmitter(), "never", { signal: idle.signal }); + console.log("pending", ee.listenerCount("never"), ee.listenerCount("error")); +} + +void main(); diff --git a/tests/corpus/6490-js-named-promise-executor/main.ts b/tests/corpus/6490-js-named-promise-executor/main.ts new file mode 100644 index 000000000..79e2a17d7 --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/main.ts @@ -0,0 +1,22 @@ +// A JavaScript library that builds its promise from a named executor +// function (`function run(resolve, reject) {...}; return new Promise(run)`, +// the classic retry helper shape): the executor's parameters are untyped, +// so it receives the native resolve and reject as checked-dynamic function +// values and calls them like any JavaScript function. +import { attempt } from "later"; +async function main(): Promise { + const v = await attempt(async (_bail: (e: Error) => void, n: number): Promise => { + if (n < 3) throw new Error("try " + n); + return "done after " + n; + }, 5); + console.log(v); + try { + await attempt((bail: (e: Error) => void): string => { + bail(new Error("stop")); + return "ignored"; + }, 5); + } catch (e) { + console.log("caught", (e as Error).message); + } +} +main(); diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts new file mode 100644 index 000000000..55956f01d --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.d.ts @@ -0,0 +1 @@ +export declare function attempt(fn: (bail: (e: Error) => void, n: number) => A | Promise, retries: number): Promise; diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js new file mode 100644 index 000000000..deefa6aee --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/index.js @@ -0,0 +1,31 @@ +"use strict"; +function attempt(fn, retries) { + function run(resolve, reject) { + let n = 0; + function bail(err) { + reject(err || new Error("Aborted")); + } + function onError(err) { + if (err.bail) { + bail(err); + return; + } + if (n > retries) reject(err); + else runAttempt(); + } + function runAttempt() { + n++; + let val; + try { + val = fn(bail, n); + } catch (err) { + onError(err); + return; + } + Promise.resolve(val).then(resolve).catch(onError); + } + runAttempt(); + } + return new Promise(run); +} +module.exports = { attempt }; diff --git a/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json b/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json new file mode 100644 index 000000000..ce1f29bac --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/node_modules/later/package.json @@ -0,0 +1,6 @@ +{ + "name": "later", + "version": "1.0.0", + "main": "./index.js", + "types": "./index.d.ts" +} diff --git a/tests/corpus/6490-js-named-promise-executor/tsconfig.json b/tests/corpus/6490-js-named-promise-executor/tsconfig.json new file mode 100644 index 000000000..27d492dd2 --- /dev/null +++ b/tests/corpus/6490-js-named-promise-executor/tsconfig.json @@ -0,0 +1,3 @@ +{ + "compilerOptions": { "strict": true, "types": ["node"] } +} diff --git a/tests/corpus/6490-literal-key-field-updates.ts b/tests/corpus/6490-literal-key-field-updates.ts new file mode 100644 index 000000000..f90739c31 --- /dev/null +++ b/tests/corpus/6490-literal-key-field-updates.ts @@ -0,0 +1,132 @@ +// Read-modify-write through a key whose type is a union of literal field +// names (`totals[side] += n`): every compound operator, ++/-- and logical +// assignment dispatch over the declared fields the key can name. The +// receiver and key evaluate once, before the right side. +type Side = "sent" | "received"; +interface Traffic { + sent: number; + received: number; + label: string; + updatedAt: number; +} + +const traffic: Traffic = { sent: 0, received: 0, label: "link", updatedAt: 0 }; + +function record(side: Side, bytes: number): void { + traffic[side] += bytes; + traffic.updatedAt++; +} +record("sent", 120); +record("received", 64); +record("sent", 8); +console.log(JSON.stringify(traffic)); + +function adjust(side: Side): string { + const parts: number[] = []; + parts.push((traffic[side] -= 3)); + parts.push((traffic[side] *= 2)); + parts.push((traffic[side] /= 4)); + parts.push((traffic[side] %= 7)); + parts.push((traffic[side] **= 2)); + parts.push((traffic[side] |= 1)); + parts.push((traffic[side] &= 0xff)); + parts.push((traffic[side] ^= 0x0f)); + parts.push((traffic[side] <<= 2)); + parts.push((traffic[side] >>= 1)); + parts.push((traffic[side] >>>= 1)); + return parts.join(","); +} +console.log(adjust("sent"), adjust("received")); + +// ++ and -- in statement and value positions, prefix and postfix. +const sides: Side[] = ["sent", "received", "sent"]; +for (const s of sides) traffic[s]++; +for (const s of sides) --traffic[s]; +const post = traffic[sides[1]!]--; +const pre = ++traffic[sides[0]!]; +console.log(post, pre, traffic.sent, traffic.received); + +// One literal key, spelled at the site or carried by a const. +traffic["sent"] += 1000; +const fixed = "received"; +traffic[fixed] -= 1; +traffic["received"]++; +console.log(traffic.sent, traffic.received); + +// Evaluation order: receiver, key, old value, right side, write. +let trace = ""; +function pick(s: Side): Side { + trace += `key:${s};`; + return s; +} +function target(): Traffic { + trace += "receiver;"; + return traffic; +} +function amount(n: number): number { + trace += `rhs:${n};`; + return n; +} +console.log((target()[pick("received")] += amount(5)), trace); +trace = ""; +console.log(target()[pick("sent")]++, trace); +trace = ""; +let current = traffic; +function swap(): number { + current = { sent: -1, received: -1, label: "other", updatedAt: 0 }; + return 1; +} +current[pick("sent")] += swap(); +console.log(traffic.sent, current.sent, trace); + +// The right side throws after the old value is read: nothing is written. +function fail(): number { + throw new Error("rhs failed"); +} +try { + traffic[pick("received")] *= fail(); +} catch (e) { + if (e instanceof Error) console.log(e.message, traffic.received); +} + +// String fields append. +type Part = "head" | "tail"; +const text: { head: string; tail: string; size: number } = { head: "a", tail: "z", size: 0 }; +function append(p: Part, s: string): string { + return (text[p] += s); +} +console.log(append("head", "bc"), append("tail", "yx"), append("head", "!")); +console.log(JSON.stringify(text)); + +// Optional fields with ??=: only a missing value takes the right side. +type Phase = "dns" | "connect" | "tls"; +const timings: { dns?: number; connect?: number; tls?: number; total: number } = { total: 0 }; +function mark(phase: Phase, at: number): number { + return (timings[phase] ??= at); +} +console.log(mark("dns", 4), mark("connect", 9), mark("dns", 12), mark("tls", 0), mark("tls", 7)); +console.log(timings.dns, timings.connect, timings.tls, timings.total); + +// ||= and &&= test truthiness and write only in the selected branch. +const flags = { quiet: false, verbose: true, color: false }; +type Flag = keyof typeof flags; +function settle(f: Flag, value: boolean): string { + const a = (flags[f] ||= value); + const b = (flags[f] &&= f !== "color"); + return `${f}:${a}:${b}`; +} +console.log(settle("quiet", true), settle("verbose", false), settle("color", true)); +console.log(JSON.stringify(flags)); + +// Nested receivers and keys from a typed lookup. +const stats = { day: { hits: 1, misses: 2 }, week: { hits: 10, misses: 20 } }; +type Window = keyof typeof stats; +type Outcome = "hits" | "misses"; +const events: [Window, Outcome][] = [ + ["day", "hits"], + ["week", "misses"], + ["day", "misses"], + ["day", "hits"], +]; +for (const [w, o] of events) stats[w][o] += 1; +console.log(JSON.stringify(stats)); diff --git a/tests/corpus/6491-catch-any-bindings.ts b/tests/corpus/6491-catch-any-bindings.ts new file mode 100644 index 000000000..9979637b4 --- /dev/null +++ b/tests/corpus/6491-catch-any-bindings.ts @@ -0,0 +1,34 @@ +// A catch binding annotated `any` reads the caught value through its +// checked-dynamic face: member reads, expando writes, `delete` and `in` +// act on the thrown object itself, and a rethrow keeps its identity. +const thrown: Error[] = []; + +function fail(n: number): never { + const e = new Error(`failure ${n}`); + thrown.push(e); + throw e; +} + +for (const n of [1, 2]) { + try { + try { + fail(n); + } catch (error: any) { + error.bail = n === 1; + console.log("marked:", error.bail, "bail" in error, error.message); + if (error.bail) { + delete error.bail; + console.log("deleted:", error.bail, "bail" in error); + } + throw error; + } + } catch (outer) { + console.log("outer:", outer === thrown[n - 1], (outer as Error).message, "bail" in (outer as object)); + } +} + +try { + JSON.parse("{"); +} catch (error: any) { + console.log(error.name, typeof error.message, error instanceof SyntaxError); +} diff --git a/tests/corpus/6491-events-once-streams.ts b/tests/corpus/6491-events-once-streams.ts new file mode 100644 index 000000000..efe60c737 --- /dev/null +++ b/tests/corpus/6491-events-once-streams.ts @@ -0,0 +1,87 @@ +// events.once over streams: file streams, Readable.from, a PassThrough +// destroyed with an error, a Writable, and a NodeJS.ReadableStream slot +// raced against its destination's 'close' with an AbortSignal that the +// finally block aborts. +import { once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Readable, Writable } from "node:stream"; + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough): Promise { + const ac = new AbortController(); + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + console.log("part done", out.listenerCount("close"), out.listenerCount("error")); + }); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-streams-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "part one\n"); + + const rs = fs.createReadStream(file); + rs.resume(); + console.log("file end", JSON.stringify(await once(rs, "end"))); + const ws = fs.createWriteStream(path.join(dir, "b.txt")); + ws.end("x"); + await once(ws, "finish"); + console.log("write stream finish"); + await once(ws, "close"); + console.log("write stream close"); + + const pt = new PassThrough(); + setTimeout(() => pt.destroy(new Error("pt broke")), 1); + try { + await once(pt, "end"); + } catch (err) { + console.log("passthrough error", (err as Error).message, pt.listenerCount("end")); + } + const r = Readable.from(["a", "b"]); + r.resume(); + await once(r, "close"); + console.log("readable close"); + const w = new Writable({ + write(_chunk, _encoding, callback) { + callback(); + }, + }); + w.end("z"); + await once(w, "finish"); + console.log("writable finish"); + + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + await copyPart(fs.createReadStream(file), out); + await copyPart(fs.createReadStream(file), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + const closing = new PassThrough(); + setTimeout(() => closing.destroy(), 1); + try { + await copyPart(new Readable({ read() {} }), closing); + } catch (err) { + console.log("closed", (err as Error).message); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6491-index-record-updates.ts b/tests/corpus/6491-index-record-updates.ts new file mode 100644 index 000000000..108d34012 --- /dev/null +++ b/tests/corpus/6491-index-record-updates.ts @@ -0,0 +1,44 @@ +// Compound assignment and ++/-- through a string key of an +// index-signature record: a missing entry reads as undefined, which every +// numeric operator turns into NaN, and the write inserts or replaces it. +const counts: Record = {}; +const words = "the cat and the hat and the bat".split(" "); +for (const w of words) { + counts[w] ??= 0; + counts[w]++; +} +console.log(JSON.stringify(counts)); + +const sums: { [key: string]: number } = { a: 1, b: 2 }; +sums["a"] += 10; +sums.b *= 3; +const key = "a"; +sums[key] -= 1; +console.log(JSON.stringify(sums)); + +// A missing entry: undefined op number is NaN, and the value form yields it. +console.log((sums["missing"] += 1), sums["missing"], Number.isNaN(sums["missing"])); +const later = sums["gone"]++; +console.log(later, sums["gone"], Object.keys(sums).join(",")); + +// Totals keyed by a runtime string, with the key and right side evaluated once. +let trace = ""; +function name(n: string): string { + trace += `key:${n};`; + return n; +} +function size(n: number): number { + trace += `rhs:${n};`; + return n; +} +const totals: Record = { disk: 0, net: 0 }; +const samples: [string, number][] = [ + ["disk", 4], + ["net", 7], + ["disk", 2], +]; +for (const [k, v] of samples) totals[name(k)] += size(v); +console.log(JSON.stringify(totals), trace); +const before = totals["net"]--; +const after = --totals["net"]; +console.log(before, after, totals["net"]); diff --git a/tests/corpus/6492-promise-race-combined.ts b/tests/corpus/6492-promise-race-combined.ts new file mode 100644 index 000000000..70f5399af --- /dev/null +++ b/tests/corpus/6492-promise-race-combined.ts @@ -0,0 +1,72 @@ +// Promise.race over events.once results: entries that only reject (a +// handler that throws) fit any result, and entries of different value +// types combine into 'unknown'. +import { EventEmitter, once } from "node:events"; + +function waitFor(a: EventEmitter, b: EventEmitter) { + let failed = false; + return Promise.race([ + once(a, "done"), + once(a, "error").then(([error]) => { + failed = true; + throw error; + }), + once(b, "close").then(() => { + failed = true; + throw new Error("closed early"); + }), + ]).finally(() => { + console.log("settled", failed); + }); +} + +function waitVoid(a: EventEmitter): Promise { + return Promise.race([ + once(a, "done").then((): void => undefined), + once(a, "error").then(([error]) => { + throw error; + }), + ]); +} + +function waitMixed(a: EventEmitter): Promise { + return Promise.race([once(a, "done").then((): void => undefined), once(a, "other")]); +} + +async function main(): Promise { + const a = new EventEmitter(); + const b = new EventEmitter(); + setTimeout(() => a.emit("done", 7), 1); + console.log("value", JSON.stringify(await waitFor(a, b))); + setTimeout(() => b.emit("close"), 1); + try { + await waitFor(a, b); + } catch (err) { + console.log("caught", (err as Error).message); + } + setTimeout(() => a.emit("error", new Error("bad")), 1); + try { + await waitFor(a, b); + } catch (err) { + console.log("caught", (err as Error).message); + } + setTimeout(() => a.emit("done", 8), 1); + await waitVoid(a); + console.log("void done"); + setTimeout(() => a.emit("other", 1, "x"), 1); + console.log("mixed", JSON.stringify(await waitMixed(a))); + setTimeout(() => a.emit("done", 9), 1); + console.log("mixed undefined", await waitMixed(a)); + // Turn order: the race settles one turn after its first entry. + const c = new EventEmitter(); + void waitMixed(c).then(() => console.log("race settled")); + void Promise.resolve() + .then(() => console.log("u1")) + .then(() => console.log("u2")) + .then(() => console.log("u3")) + .then(() => console.log("u4")) + .then(() => console.log("u5")); + c.emit("other", 2, "y"); +} + +void main(); diff --git a/tests/corpus/6493-stream-function-callbacks.ts b/tests/corpus/6493-stream-function-callbacks.ts new file mode 100644 index 000000000..7d5ee45e1 --- /dev/null +++ b/tests/corpus/6493-stream-function-callbacks.ts @@ -0,0 +1,66 @@ +// Stream subclasses that declare their completion callbacks with the broad +// `Function` type: the runtime's callable arrives and calls through it +// complete the chunk, pass data, or fail the stream. +import { Transform, Writable } from "node:stream"; + +class Prefixer extends Transform { + _s: string; + constructor() { + super(); + this._s = ""; + } + _transform(c: Buffer, _: string, cb: Function) { + const text = c.toString(); + const lines = text.split("\n").map((line, index, all) => { + if (index === 0) return this._s === "" || this._s.endsWith("\n") ? "> " + line : line; + if (index === all.length - 1) return line !== "" ? "> " + line : line; + return "> " + line; + }); + this._s += text; + this.push(lines.join("\n")); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: Buffer, _encoding: string, cb: Function) { + if (chunk.toString() === "bad") { + cb(new Error("bad chunk")); + return; + } + cb(null, chunk.toString().toUpperCase()); + } + _flush(cb: Function) { + this.push("!"); + cb(); + } +} + +class Sink extends Writable { + seen: string[] = []; + _write(chunk: Buffer, _encoding: string, cb: Function) { + this.seen.push(chunk.toString()); + cb(); + } + _final(cb: Function) { + this.seen.push(""); + cb(); + } +} + +const prefixer = new Prefixer(); +prefixer.pipe(process.stdout); +prefixer.write("a\nb"); +prefixer.write("c\n"); +prefixer.end("d\n"); + +const sink = new Sink(); +const upper = new Upper(); +upper.pipe(sink); +sink.on("finish", () => console.log("sink", JSON.stringify(sink.seen))); +upper.write("a"); +upper.end("b"); + +const bad = new Upper(); +bad.on("error", (err: Error) => console.log("error", err.message)); +bad.write("bad"); diff --git a/tests/corpus/6494-default-export-class-members/lib/date.js b/tests/corpus/6494-default-export-class-members/lib/date.js new file mode 100644 index 000000000..0c4a426f7 --- /dev/null +++ b/tests/corpus/6494-default-export-class-members/lib/date.js @@ -0,0 +1,8 @@ +// A class whose base scriptc cannot extend yet: the default export object +// below narrows this member away, and only reads of it would fence. +export class StampDate extends Date { + constructor(text) { + super(text); + this.label = "stamp:" + text; + } +} diff --git a/tests/corpus/6494-default-export-class-members/lib/error.js b/tests/corpus/6494-default-export-class-members/lib/error.js new file mode 100644 index 000000000..7f2ac5744 --- /dev/null +++ b/tests/corpus/6494-default-export-class-members/lib/error.js @@ -0,0 +1,7 @@ +export class ParseError extends Error { + constructor(message, line) { + super(message); + this.name = "ParseError"; + this.line = line; + } +} diff --git a/tests/corpus/6494-default-export-class-members/lib/index.js b/tests/corpus/6494-default-export-class-members/lib/index.js new file mode 100644 index 000000000..66935fac6 --- /dev/null +++ b/tests/corpus/6494-default-export-class-members/lib/index.js @@ -0,0 +1,14 @@ +import { StampDate } from "./date.js"; +import { ParseError } from "./error.js"; +function parse(text) { + if (text === "") throw new ParseError("empty document", 1); + const [key, value] = text.split("=").map((part) => part.trim()); + return { key, value }; +} +function stringify(value) { + return JSON.stringify(value); +} +// A default export object of functions and class values (the shorthand +// spelling of `{ ParseError: ParseError }`). +export default { parse, stringify, StampDate, ParseError }; +export { parse, stringify, StampDate, ParseError }; diff --git a/tests/corpus/6494-default-export-class-members/main.ts b/tests/corpus/6494-default-export-class-members/main.ts new file mode 100644 index 000000000..1548e6415 --- /dev/null +++ b/tests/corpus/6494-default-export-class-members/main.ts @@ -0,0 +1,15 @@ +// A JavaScript module's default export object holding functions and class +// values builds at module initialization, and its members work through +// both the default object and the named exports. +import lib, { parse, ParseError } from "./lib/index.js"; + +console.log(JSON.stringify(parse("a = 1"))); +console.log(lib.stringify(lib.parse("b = 2"))); +try { + lib.parse(""); +} catch (e) { + if (e instanceof ParseError) console.log(e.name, e.message, e.line); +} +const Ctor = lib.ParseError; +const made = new Ctor("built through the default object", 7); +console.log(made instanceof ParseError, made.message, made.line, Ctor === ParseError); diff --git a/tests/corpus/6494-default-export-class-members/package.json b/tests/corpus/6494-default-export-class-members/package.json new file mode 100644 index 000000000..ca00e63c9 --- /dev/null +++ b/tests/corpus/6494-default-export-class-members/package.json @@ -0,0 +1,5 @@ +{ + "name": "default-export-class-members-corpus", + "private": true, + "type": "module" +} diff --git a/tests/corpus/6494-events-once-dyn-stream-slot/main.ts b/tests/corpus/6494-events-once-dyn-stream-slot/main.ts new file mode 100644 index 000000000..501944ae3 --- /dev/null +++ b/tests/corpus/6494-events-once-dyn-stream-slot/main.ts @@ -0,0 +1,59 @@ +// events.once over a checked-dynamic NodeJS.ReadableStream slot: the +// program also moves a JavaScript stream object into such a slot, so the +// slots hold native streams through their typed capsules. +import { once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough } from "node:stream"; +import { part } from "./sources.js"; + +function collect(src: NodeJS.ReadableStream): Promise { + return new Promise((resolve) => { + let text = ""; + src.on("data", (chunk: Buffer | string) => { + text += chunk.toString(); + }); + src.on("end", () => resolve(text)); + }); +} + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough) { + const ac = new AbortController(); + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + }); +} + +async function main(): Promise { + console.log("js part", await collect(part(["a", "b"]))); + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-dyn-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "native part\n"); + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + await copyPart(fs.createReadStream(file), out); + await copyPart(fs.createReadStream(file), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/corpus/6494-events-once-dyn-stream-slot/sources.js b/tests/corpus/6494-events-once-dyn-stream-slot/sources.js new file mode 100644 index 000000000..e76e88785 --- /dev/null +++ b/tests/corpus/6494-events-once-dyn-stream-slot/sources.js @@ -0,0 +1,21 @@ +// A constructor-function stream with its own listener table. +function Part(chunks) { + this.chunks = chunks; + this.listeners = {}; + setImmediate(() => { + for (const chunk of this.chunks) this.emit("data", chunk); + this.emit("end"); + }); +} +Part.prototype.on = function (event, listener) { + (this.listeners[event] = this.listeners[event] || []).push(listener); + return this; +}; +Part.prototype.emit = function (event, value) { + for (const listener of this.listeners[event] || []) listener(value); + return true; +}; + +export function part(chunks) { + return new Part(chunks); +} diff --git a/tests/corpus/6560-constructor-function-readable.cjs b/tests/corpus/6560-constructor-function-readable.cjs new file mode 100644 index 000000000..5319aada1 --- /dev/null +++ b/tests/corpus/6560-constructor-function-readable.cjs @@ -0,0 +1,121 @@ +// Readable streams built by constructor functions, the from2 pattern: +// util.inherits(Ctor, Readable), Readable.call(this, opts) and _read on +// the prototype. +const { Readable } = require("stream"); +const util = require("util"); + +function Source(list, opts) { + if (!(this instanceof Source)) return new Source(list, opts); + this._list = list.slice(); + // Before Readable.call there is no state: Node's setter ignores this. + this.destroyed = false; + Readable.call(this, opts || {}); + this._reads = 0; + console.log("hwm", this._readableState.highWaterMark, "objectMode", this._readableState.objectMode); +} +util.inherits(Source, Readable); +Source.prototype._read = function (size) { + this._reads++; + const item = this._list.shift(); + this.push(item === undefined ? null : Buffer.from(item)); +}; + +const s = Source(["alpha", "beta", "gamma"]); +console.log("instanceof", s instanceof Source, s instanceof Readable, Source.super_ === Readable); +console.log("readable", s.readable, "ended", s.readableEnded, "destroyed", s.destroyed); +console.log("flowing", s.readableFlowing, s._readableState.flowing); +s.on("data", function (chunk) { + console.log("data", this === s, Buffer.isBuffer(chunk), chunk.toString()); +}); +s.on("end", function () { + console.log("end", this === s, s.readableEnded, s._readableState.ended, s._reads); +}); +s.on("close", () => { + console.log("close", s.destroyed, s.closed); + paused(); +}); +console.log("sync after on('data')", s.readableFlowing); + +// from2's callback-driven _read, asynchronous pushes and a small +// highWaterMark. +function From(read, opts) { + if (!(this instanceof From)) return new From(read, opts); + this._reading = false; + this._callback = check; + this.destroyed = false; + Readable.call(this, opts); + const self = this; + const hwm = this._readableState.highWaterMark; + function check(err, data) { + if (self.destroyed) return; + if (err) return self.destroy(err); + if (data === null) return self.push(null); + self._reading = false; + if (self.push(data)) self._read(hwm); + } + this._from = read; +} +util.inherits(From, Readable); +From.prototype._read = function (size) { + if (this._reading || this.destroyed) return; + this._reading = true; + this._from(size, this._callback); +}; +From.prototype.destroy = function (err) { + if (this.destroyed) return; + this.destroyed = true; + const self = this; + process.nextTick(function () { + if (err) self.emit("error", err); + self.emit("close"); + }); +}; + +function paused() { + const parts = ["one ", "two ", "three"]; + const r = new From(function (size, next) { + setImmediate(() => next(null, parts.length ? parts.shift() : null)); + }, { highWaterMark: 4 }); + console.log("from hwm", r._readableState.highWaterMark, r.readableHighWaterMark); + r.setEncoding("utf8"); + let text = ""; + r.on("readable", () => { + let chunk; + while ((chunk = r.read()) !== null) text += chunk; + }); + r.on("end", () => { + console.log("paused text", JSON.stringify(text), typeof text); + // From() without new runs outside the listener, where `this` is unset. + setImmediate(failing); + }); +} + +function failing() { + let calls = 0; + const r = From(function (size, next) { + calls++; + if (calls === 2) return next(new Error("source failed")); + next(null, "chunk" + calls); + }); + r.on("data", (d) => console.log("failing data", String(d))); + r.on("error", (e) => console.log("failing error", e.message, r.destroyed)); + r.on("close", () => { + console.log("failing close", calls); + unshifted(); + }); +} + +function unshifted() { + const r = new Source(["xyz"]); + r.once("readable", () => { + const first = r.read(1); + console.log("read(1)", String(first), r.readableLength); + r.unshift(first); + console.log("after unshift", r.readableLength); + r.pause(); + console.log("isPaused", r.isPaused()); + r.on("data", (d) => console.log("rest", String(d))); + r.resume(); + }); + r.on("end", () => console.log("unshifted end")); +} diff --git a/tests/corpus/6561-constructor-function-writable.cjs b/tests/corpus/6561-constructor-function-writable.cjs new file mode 100644 index 000000000..ee25b7595 --- /dev/null +++ b/tests/corpus/6561-constructor-function-writable.cjs @@ -0,0 +1,141 @@ +// Writable and Duplex streams built by constructor functions, the bl and +// tar-stream patterns: Writable.call(this, opts), Duplex.call(this), +// _write/_final/_read on the prototype, listeners added before the base +// constructor runs, and an end() override that calls the base's end. +const stream = require("stream"); +const util = require("util"); + +function Sink(opts) { + if (!(this instanceof Sink)) return new Sink(opts); + stream.Writable.call(this, opts); + this.chunks = []; + this.finalized = false; +} +util.inherits(Sink, stream.Writable); +Sink.prototype._write = function (chunk, enc, cb) { + console.log("_write", this instanceof Sink, enc, Buffer.isBuffer(chunk), chunk.toString()); + this.chunks.push(chunk); + if (this.chunks.length === 2) setImmediate(cb); + else cb(); +}; +Sink.prototype._final = function (cb) { + console.log("_final", this.chunks.length); + this.finalized = true; + cb(); +}; + +const sink = new Sink({ highWaterMark: 8 }); +console.log("writable", sink.writable, sink.writableHighWaterMark, sink._writableState.highWaterMark); +console.log("write a", sink.write("first")); +console.log("write b", sink.write(Buffer.from("second"), () => console.log("write b callback"))); +console.log("needDrain", sink.writableNeedDrain, sink.writableLength); +sink.on("drain", () => console.log("drain", sink.writableLength)); +sink.on("finish", function () { + console.log("finish", this === sink, sink.finalized, sink.writableFinished, Buffer.concat(sink.chunks).toString()); +}); +sink.on("close", () => { + console.log("close", sink.destroyed, sink.closed); + duplex(); +}); +sink.end("third", (err) => console.log("end callback", err, sink.writableEnded, sink._writableState.ended)); +console.log("after end", sink.writableEnded, sink.writable); + +// A bl-style list: a Duplex that collects writes and reads them back. +function BufferListStream(callback) { + if (!(this instanceof BufferListStream)) return new BufferListStream(callback); + if (typeof callback === "function") { + this._callback = callback; + const piper = function piper(err) { + if (this._callback) { + this._callback(err); + this._callback = null; + } + }.bind(this); + this.on("pipe", function onPipe(src) { + console.log("pipe event", src instanceof stream.Readable); + src.on("error", piper); + }); + this.on("unpipe", function onUnpipe(src) { + console.log("unpipe event"); + src.removeListener("error", piper); + }); + callback = null; + } + this._bufs = []; + this.length = 0; + stream.Duplex.call(this); +} +util.inherits(BufferListStream, stream.Duplex); +BufferListStream.prototype._write = function (buf, encoding, callback) { + this._bufs.push(buf); + this.length += buf.length; + callback(); +}; +BufferListStream.prototype._read = function (size) { + if (!this.length) return this.push(null); + const all = Buffer.concat(this._bufs); + size = Math.min(size, this.length, 4); + this.push(all.subarray(0, size)); + this._bufs = [all.subarray(size)]; + this.length -= size; +}; +BufferListStream.prototype.end = function end(chunk) { + stream.Duplex.prototype.end.call(this, chunk); + if (this._callback) { + this._callback(null, Buffer.concat(this._bufs)); + this._callback = null; + } +}; +BufferListStream.prototype.slice = function () { + return Buffer.concat(this._bufs); +}; + +function Letters(text) { + stream.Readable.call(this, { highWaterMark: 2 }); + this._text = text; +} +util.inherits(Letters, stream.Readable); +Letters.prototype._read = function () { + const next = this._text.slice(0, 3); + this._text = this._text.slice(3); + this.push(next.length ? next : null); +}; + +function duplex() { + const list = new BufferListStream((err, data) => { + console.log("bl callback", err, data.toString(), data.length); + }); + console.log("duplex", list.readable, list.writable, list.allowHalfOpen, list instanceof stream.Duplex); + console.log("pre-constructor listeners", list.listenerCount("pipe"), list.listenerCount("unpipe")); + list.write("hello "); + list.write("constructor "); + list.end("functions"); + list.on("finish", () => { + const out = []; + list.on("data", (d) => out.push(d.toString())); + list.on("end", () => { + console.log("read back", JSON.stringify(out)); + piping(); + }); + }); +} + +function piping() { + const src = new Letters("abcdefghij"); + const dst = new Sink({ highWaterMark: 1 }); + dst.on("pipe", (from) => console.log("dst pipe", from === src)); + const returned = src.pipe(dst); + console.log("pipe returns dest", returned === dst); + dst.on("finish", () => { + console.log("piped", Buffer.concat(dst.chunks).toString(), src.readableEnded); + const keep = new Sink(); + const from = new Letters("xyz"); + from.pipe(keep, { end: false }); + from.on("end", () => { + setImmediate(() => { + console.log("end:false keeps the destination open", keep.writable, keep.writableEnded); + keep.end(); + }); + }); + }); +} diff --git a/tests/corpus/6562-constructor-function-transform.cjs b/tests/corpus/6562-constructor-function-transform.cjs new file mode 100644 index 000000000..144a746bc --- /dev/null +++ b/tests/corpus/6562-constructor-function-transform.cjs @@ -0,0 +1,143 @@ +// Transform and PassThrough streams built by constructor functions (the +// yazl and tar-stream patterns), piped together with runtime streams: +// zlib and fs. +const { Readable, Writable, Transform, PassThrough } = require("stream"); +const util = require("util"); +const zlib = require("zlib"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +// yazl's ByteCounter +function ByteCounter(options) { + Transform.call(this, options); + this.byteCount = 0; +} +util.inherits(ByteCounter, Transform); +ByteCounter.prototype._transform = function (chunk, encoding, cb) { + this.byteCount += chunk.length; + cb(null, chunk); +}; + +// A transform with a flush that appends a trailer. +function Upper() { + if (!(this instanceof Upper)) return new Upper(); + Transform.call(this); + this.seen = 0; +} +util.inherits(Upper, Transform); +Upper.prototype._transform = function (chunk, encoding, cb) { + this.seen++; + this.push(chunk.toString().toUpperCase()); + cb(); +}; +Upper.prototype._flush = function (cb) { + cb(null, "[" + this.seen + " chunks]"); +}; + +// tar-stream's entry stream: a PassThrough with its own destroy. +function Source(parent, offset) { + this._parent = parent; + this.offset = offset; + PassThrough.call(this, { autoDestroy: false }); +} +util.inherits(Source, PassThrough); +Source.prototype.destroy = function (err) { + console.log("Source.destroy forwarded", err && err.message); + this._parent.destroyed = true; +}; + +// A PassThrough without its own _transform passes chunks through. +function Plain() { + PassThrough.call(this); +} +util.inherits(Plain, PassThrough); + +function Words(words) { + Readable.call(this); + this._words = words.slice(); +} +util.inherits(Words, Readable); +Words.prototype._read = function () { + this.push(this._words.length ? this._words.shift() : null); +}; + +function Collect() { + Writable.call(this); + this.parts = []; +} +util.inherits(Collect, Writable); +Collect.prototype._write = function (chunk, enc, cb) { + this.parts.push(chunk); + cb(); +}; +Collect.prototype.text = function () { + return Buffer.concat(this.parts).toString(); +}; + +const upper = Upper(); +const words = []; +upper.on("data", (d) => words.push(d.toString())); +upper.on("end", () => { + console.log("upper", JSON.stringify(words), upper.seen, upper._readableState.ended, upper._writableState.finished); + counter(); +}); +upper.write("one "); +upper.write(Buffer.from("two ")); +upper.end("three"); + +function counter() { + const counterStream = new ByteCounter(); + const sink = new Collect(); + new Words(["hello ", "byte ", "counter"]).pipe(counterStream).pipe(sink); + sink.on("finish", () => { + console.log("counted", counterStream.byteCount, JSON.stringify(sink.text())); + entry(); + }); +} + +function entry() { + const parent = { destroyed: false }; + const source = new Source(parent, 512); + const plain = new Plain(); + console.log("source", source.offset, source instanceof PassThrough, source instanceof Transform); + let body = ""; + source.pipe(plain); + plain.on("data", (d) => (body += d)); + plain.on("end", () => { + console.log("entry body", JSON.stringify(body), source.destroyed); + source.destroy(new Error("stop")); + console.log("parent destroyed", parent.destroyed); + gzip(); + }); + source.write("file "); + source.end("contents"); +} + +function gzip() { + const counted = new ByteCounter(); + const out = new Collect(); + new Words(["compressed ", "through ", "zlib"]).pipe(counted).pipe(zlib.createGzip()).pipe(out); + out.on("finish", () => { + const back = zlib.gunzipSync(Buffer.concat(out.parts)).toString(); + console.log("gzip roundtrip", JSON.stringify(back), counted.byteCount); + toFile(); + }); +} + +function toFile() { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "ctor-stream-")); + const file = path.join(dir, "out.txt"); + const ws = fs.createWriteStream(file); + new Words(["written ", "by ", "a ", "constructor"]).pipe(Upper()).pipe(ws); + ws.on("finish", () => { + console.log("file", JSON.stringify(fs.readFileSync(file, "utf8"))); + // A runtime stream piped into a constructor-function stream. + const back = new Collect(); + fs.createReadStream(file).pipe(new ByteCounter()).pipe(back); + back.on("finish", () => { + console.log("read back", JSON.stringify(back.text())); + fs.rmSync(dir, { recursive: true }); + }); + }); +} diff --git a/tests/corpus/6563-constructor-function-stream-errors.cjs b/tests/corpus/6563-constructor-function-stream-errors.cjs new file mode 100644 index 000000000..cf2fb3e7c --- /dev/null +++ b/tests/corpus/6563-constructor-function-stream-errors.cjs @@ -0,0 +1,137 @@ +// Errors, destroy and custom events on constructor-function streams. +const { Readable, Writable, Duplex } = require("stream"); +const util = require("util"); + +function Failing() { + Writable.call(this); +} +util.inherits(Failing, Writable); +Failing.prototype._write = function (chunk, enc, cb) { + const err = new Error("cannot store " + chunk); + err.code = "E_STORE"; + this.lastError = err; + cb(err); +}; + +const failing = new Failing(); +failing.on("error", function (err) { + console.log("write error", this === failing, err === failing.lastError, err.message, err.code); + console.log("errored", failing.errored === err, failing.destroyed, failing.writable); +}); +failing.on("close", () => { + console.log("write close"); + destroying(); +}); +failing.write("x", (err) => console.log("write callback", err && err.code)); + +function Ticker() { + Readable.call(this); + this.n = 0; +} +util.inherits(Ticker, Readable); +Ticker.prototype._read = function () { + this.push(String(this.n++)); +}; +Ticker.prototype._destroy = function (err, cb) { + console.log("_destroy", err && err.message, this.n > 0); + setImmediate(() => cb(err && new Error("wrapped: " + err.message))); +}; + +function destroying() { + const t = new Ticker(); + const order = []; + t.on("data", (d) => { + if (d.toString() === "3") t.destroy(new Error("enough"), (e) => order.push("destroy callback " + (e && e.message))); + }); + t.on("error", (e) => order.push("error " + e.message)); + t.on("close", () => { + order.push("close"); + console.log(order.join(" | "), t.destroyed, t.readable); + t.destroy(null, () => console.log("destroy after close calls back")); + notImplemented(); + }); +} + +function Bare() { + Readable.call(this); +} +util.inherits(Bare, Readable); + +function Throws() { + Readable.call(this); +} +util.inherits(Throws, Readable); +Throws.prototype._read = function () { + throw new TypeError("read threw"); +}; + +function notImplemented() { + const bare = new Bare(); + bare.on("error", (e) => { + console.log("bare", e.code, e.message); + const thrower = new Throws(); + thrower.on("error", (err) => { + console.log("throws", err instanceof TypeError, err.message, thrower.destroyed); + afterEnd(); + }); + thrower.resume(); + }); + bare.resume(); +} + +function Quiet() { + Writable.call(this); +} +util.inherits(Quiet, Writable); +Quiet.prototype._write = function (chunk, enc, cb) { + cb(); +}; + +function afterEnd() { + const q = new Quiet(); + q.end("done"); + q.on("error", (e) => { + console.log("after end", e.code, e.message); + customEvents(); + }); + q.write("late"); +} + +// tar-stream's Extract emits 'entry' with three arguments. +function Extract() { + if (!(this instanceof Extract)) return new Extract(); + Writable.call(this); +} +util.inherits(Extract, Writable); +Extract.prototype._write = function (data, enc, cb) { + const header = { name: data.toString(), size: data.length }; + this.emit("entry", header, "stream-for-" + header.name, cb); +}; + +function customEvents() { + const ex = Extract(); + const seen = []; + function onEntry(header, s, next) { + seen.push(header.name + ":" + header.size + ":" + s + ":" + (this === ex)); + next(); + } + ex.on("entry", onEntry); + ex.once("entry", (header) => seen.push("once " + header.name)); + ex.prependListener("entry", () => seen.push("first")); + console.log("listenerCount", ex.listenerCount("entry"), ex.listenerCount("entry", onEntry)); + ex.write("a.txt"); + ex.removeListener("entry", onEntry); + ex.write("b.txt"); + console.log(seen.join(" | ")); + console.log("emit returns", ex.emit("nothing"), ex.emit("entry", { name: "c", size: 0 }, "", () => {})); + try { + ex.emit("error", new RangeError("nobody listens")); + } catch (e) { + console.log("unhandled error rethrown", e instanceof RangeError, e.message); + } + ex.setMaxListeners(1); + console.log("max listeners", ex.getMaxListeners()); + ex.removeAllListeners("entry"); + console.log("after removeAll", ex.listenerCount("entry")); + console.log("Duplex prototype", typeof Duplex.prototype.write, typeof Duplex.prototype.push); +} diff --git a/tests/corpus/6564-constructor-function-stream-late-hooks.cjs b/tests/corpus/6564-constructor-function-stream-late-hooks.cjs new file mode 100644 index 000000000..22a8ec2f4 --- /dev/null +++ b/tests/corpus/6564-constructor-function-stream-late-hooks.cjs @@ -0,0 +1,117 @@ +// Stream hooks a constructor function assigns after the base constructor +// runs, and unpipe from the destination's 'pipe' listener. +const { Writable, Transform, PassThrough } = require("stream"); +const util = require("util"); + +function Sink() { + Writable.call(this); + this.chunks = []; + this._final = function (cb) { + console.log("_final", this instanceof Sink, this.chunks.join(",")); + setImmediate(cb); + }; +} +util.inherits(Sink, Writable); +Sink.prototype._write = function (chunk, enc, cb) { + this.chunks.push(String(chunk)); + cb(); +}; + +const sink = new Sink(); +sink.on("prefinish", () => console.log("sink prefinish")); +sink.on("finish", () => { + console.log("sink finish"); + flushing(); +}); +sink.write("a"); +sink.end("b"); + +function Summer() { + Transform.call(this); + this.total = 0; + this._flush = function (cb) { + console.log("_flush", this.total); + cb(null, "total " + this.total); + }; +} +util.inherits(Summer, Transform); +Summer.prototype._transform = function (chunk, enc, cb) { + this.total += Number(String(chunk)); + cb(null, "seen " + chunk + "\n"); +}; + +function flushing() { + const summer = new Summer(); + const out = []; + summer.on("data", (chunk) => out.push(String(chunk))); + summer.on("end", () => { + console.log("summer end", JSON.stringify(out)); + passing(); + }); + summer.write("2"); + summer.write("3"); + summer.end(); +} + +function Upper() { + PassThrough.call(this); + this._transform = function (chunk, enc, cb) { + cb(null, String(chunk).toUpperCase()); + }; +} +util.inherits(Upper, PassThrough); + +function passing() { + const upper = new Upper(); + const out = []; + upper.on("data", (chunk) => out.push(String(chunk))); + upper.on("end", () => { + console.log("upper end", JSON.stringify(out)); + plain(); + }); + upper.write("x"); + upper.end("y"); +} + +// Without late hooks the defaults still apply. +function Plain() { + PassThrough.call(this); +} +util.inherits(Plain, PassThrough); + +function plain() { + const p = new Plain(); + const out = []; + p.on("data", (chunk) => out.push(String(chunk))); + p.on("finish", () => console.log("plain finish")); + p.on("end", () => { + console.log("plain end", JSON.stringify(out)); + unpiping(); + }); + p.end("same"); +} + +function Collector() { + Writable.call(this); + this.chunks = []; +} +util.inherits(Collector, Writable); +Collector.prototype._write = function (chunk, enc, cb) { + this.chunks.push(String(chunk)); + cb(); +}; + +function unpiping() { + const source = new PassThrough(); + const collector = new Collector(); + collector.on("pipe", function (src) { + console.log("pipe", typeof src.unpipe, this === collector); + src.unpipe(this); + }); + collector.on("unpipe", (src) => { + console.log("unpipe", typeof src.pipe); + source.write("after"); + setImmediate(() => console.log("collected", JSON.stringify(collector.chunks), source.readableFlowing)); + }); + source.pipe(collector); +} diff --git a/tests/diagnostics/retired-engine-761-any-objects.ts b/tests/corpus/761-any-objects.ts similarity index 67% rename from tests/diagnostics/retired-engine-761-any-objects.ts rename to tests/corpus/761-any-objects.ts index 59796020b..45e6e6f82 100644 --- a/tests/diagnostics/retired-engine-761-any-objects.ts +++ b/tests/corpus/761-any-objects.ts @@ -1,6 +1,6 @@ -// Object/array literals in `any` slots build natively in the island; -// property reads/writes, computed keys, and method calls go through the -// engine (this-binding and coercions JS-exact). +// Object/array literals in `any` slots: property reads/writes, computed +// keys, and method calls go through the checked-dynamic value, including a +// member read on a fresh typed call result (`s.split(" ").length`). const o: any = { a: 1, b: "x", nested: { deep: true } }; o.a = o.a + 10; o["c"] = 2.5; diff --git a/tests/corpus/cjs-class-hierarchy-npm/main.ts b/tests/corpus/cjs-class-hierarchy-npm/main.ts new file mode 100644 index 000000000..b709aa556 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/main.ts @@ -0,0 +1,58 @@ +// Class hierarchies across the CommonJS modules of an npm package (the +// undici dispatcher shape): an EventEmitter-rooted base in one module, +// symbol-keyed fields whose keys come from a shared export table, a +// computed accessor, subclasses in further modules re-exported through +// require bindings and inline requires, and TypeScript subclasses that +// override a JavaScript method with typed parameters and call super. +import { Agent, Dispatcher, DispatcherBase, Pool, describe, type Handler } from "dispatchkit"; + +class TracingAgent extends Agent { + readonly trace: string[] = []; + + constructor() { + super({ label: "tracing", prefix: "+" }); + } + + dispatch(path: string, handler: Handler): boolean { + this.trace.push(path); + return super.dispatch(path.toUpperCase(), handler); + } +} + +class LimitedPool extends Pool { + rejected = 0; + + dispatch(path: string, handler: Handler): boolean { + const ok = super.dispatch(path, handler); + if (!ok) this.rejected++; + return ok; + } +} + +const print: Handler = (line) => console.log("handled", line); + +function run(d: Dispatcher, paths: string[]): void { + for (const path of paths) console.log(path, "->", d.dispatch(path, print)); +} + +const plain = new Agent(); +run(plain, ["/a", "/b"]); +console.log(describe(plain), plain.kind, plain.prefix); + +const tracing = new TracingAgent(); +tracing.on("closed", (label: unknown, count: unknown) => console.log("closed event", label, count)); +run(tracing, ["/x", "/y/z"]); +console.log(tracing.trace.join(","), tracing.dispatched, tracing.closed); +tracing.close(); +tracing.close(); +console.log(describe(tracing), tracing.kind); +try { + tracing.dispatch("/late", print); +} catch (err) { + console.log("error", (err as Error).message); +} + +const pool = new LimitedPool(3); +run(pool, ["/1", "/long", "/2"]); +console.log(pool.rejected, pool.size, pool.dispatched, pool instanceof DispatcherBase); +console.log(describe(pool), pool.kind); diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/index.d.ts b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/index.d.ts new file mode 100644 index 000000000..bde4d4117 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/index.d.ts @@ -0,0 +1,27 @@ +import { EventEmitter } from "node:events"; + +export type Handler = (line: string) => void; + +export declare class Dispatcher extends EventEmitter { + dispatch(path: string, handler: Handler): boolean; + close(): void; +} + +export declare class DispatcherBase extends Dispatcher { + constructor(opts?: { label?: string }); + get closed(): boolean; + get dispatched(): number; + get kind(): string; +} + +export declare class Agent extends DispatcherBase { + constructor(opts?: { label?: string; prefix?: string }); + prefix: string; +} + +export declare class Pool extends Agent { + constructor(size: number); + size: number; +} + +export declare function describe(d: Dispatcher): string; diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/index.js b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/index.js new file mode 100644 index 000000000..1f6cd7f85 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/index.js @@ -0,0 +1,12 @@ +'use strict'; + +// The undici index shape: classes re-exported through require bindings and +// inline requires of modules whose whole export is the class. +const Dispatcher = require('./lib/dispatcher'); +const { describe } = require('./lib/describe'); + +module.exports.Dispatcher = Dispatcher; +module.exports.DispatcherBase = require('./lib/dispatcher-base'); +module.exports.Agent = require('./lib/agent'); +module.exports.Pool = require('./lib/pool'); +module.exports.describe = describe; diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/agent.js b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/agent.js new file mode 100644 index 000000000..95b2e02f2 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/agent.js @@ -0,0 +1,18 @@ +'use strict'; + +const DispatcherBase = require('./dispatcher-base'); +const { kCount, kDispatch, kLabel } = require('./symbols'); + +class Agent extends DispatcherBase { + constructor({ label = 'agent', prefix = '>' } = {}) { + super({ label }); + this.prefix = prefix; + } + + [kDispatch](path, handler) { + handler(this.prefix + ' ' + this[kLabel] + ' ' + path + ' #' + this[kCount]); + return true; + } +} + +module.exports = Agent; diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/describe.js b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/describe.js new file mode 100644 index 000000000..7c0c3aef7 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/describe.js @@ -0,0 +1,9 @@ +'use strict'; + +const { kCount, kLabel } = require('./symbols'); + +function describe(d) { + return d[kLabel] + ': ' + d[kCount] + ' dispatched, ' + (d.closed ? 'closed' : 'open'); +} + +module.exports = { describe }; diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/dispatcher-base.js b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/dispatcher-base.js new file mode 100644 index 000000000..091455592 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/dispatcher-base.js @@ -0,0 +1,48 @@ +'use strict'; + +const Dispatcher = require('./dispatcher'); +const { kClosed, kDispatch, kCount, kLabel, kOnClose } = require('./symbols'); + +const KIND = 'kind'; + +class DispatcherBase extends Dispatcher { + // Symbol-keyed fields whose keys come from another module. + [kClosed] = false; + + [kCount] = 0; + + [kOnClose] = null; + + constructor(opts) { + super(); + this[kLabel] = (opts && opts.label) || 'base'; + } + + get closed() { + return this[kClosed]; + } + + get dispatched() { + return this[kCount]; + } + + // A computed accessor name. + get [KIND]() { + return 'dispatcher:' + this[kLabel]; + } + + close() { + if (this[kClosed]) return; + this[kClosed] = true; + this.emit('closed', this[kLabel], this[kCount]); + } + + dispatch(path, handler) { + if (this[kClosed]) throw new Error(this[kLabel] + ' is closed'); + this[kCount]++; + // Subclasses declare the symbol-keyed method this calls. + return this[kDispatch](path, handler); + } +} + +module.exports = DispatcherBase; diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/dispatcher.js b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/dispatcher.js new file mode 100644 index 000000000..510208c49 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/dispatcher.js @@ -0,0 +1,15 @@ +'use strict'; + +const EventEmitter = require('node:events'); + +class Dispatcher extends EventEmitter { + dispatch() { + throw new Error('not implemented'); + } + + close() { + throw new Error('not implemented'); + } +} + +module.exports = Dispatcher; diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/pool.js b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/pool.js new file mode 100644 index 000000000..837239fbd --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/pool.js @@ -0,0 +1,20 @@ +'use strict'; + +const Agent = require('./agent'); +const { kCount, kDispatch, kLabel } = require('./symbols'); + +class Pool extends Agent { + constructor(size) { + super({ label: 'pool', prefix: '*' }); + this.size = size; + } + + // Overrides the symbol-keyed method Agent declares. + [kDispatch](path, handler) { + if (path.length > this.size) return false; + handler(this.prefix + ' ' + this[kLabel] + ' ' + path + ' #' + this[kCount]); + return true; + } +} + +module.exports = Pool; diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/symbols.js b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/symbols.js new file mode 100644 index 000000000..a795c4178 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/lib/symbols.js @@ -0,0 +1,11 @@ +'use strict'; + +// Keys shared across modules through one export table (undici's +// core/symbols.js). Two keys deliberately share no description. +module.exports = { + kClosed: Symbol('closed'), + kDispatch: Symbol('dispatch'), + kCount: Symbol('count'), + kLabel: Symbol('label'), + kOnClose: Symbol('on close'), +}; diff --git a/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/package.json b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/package.json new file mode 100644 index 000000000..e187b1539 --- /dev/null +++ b/tests/corpus/cjs-class-hierarchy-npm/node_modules/dispatchkit/package.json @@ -0,0 +1,6 @@ +{ + "name": "dispatchkit", + "version": "1.0.0", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/corpus/cjs-open-options-npm/main.ts b/tests/corpus/cjs-open-options-npm/main.ts new file mode 100644 index 000000000..9fd133155 --- /dev/null +++ b/tests/corpus/cjs-open-options-npm/main.ts @@ -0,0 +1,12 @@ +// TypeScript object literals passed to a JavaScript constructor whose +// options parameter is a destructuring pattern with a rest element: the +// literal's extra properties reach the rest object. +import { Agent } from "optkit"; + +console.log(new Agent().describe("a")); +console.log(new Agent({ keepAliveTimeout: 10 }).describe("b")); +console.log(new Agent({ maxOrigins: 2, keepAliveTimeout: 5, pipelining: 1 }).describe("c")); +console.log(new Agent({ factory: (origin) => "client(" + origin + ")" }).describe("d")); + +class TracingAgent extends Agent {} +console.log(new TracingAgent({ pipelining: 3 }).describe("e")); diff --git a/tests/corpus/cjs-open-options-npm/node_modules/optkit/index.d.ts b/tests/corpus/cjs-open-options-npm/node_modules/optkit/index.d.ts new file mode 100644 index 000000000..643b92d84 --- /dev/null +++ b/tests/corpus/cjs-open-options-npm/node_modules/optkit/index.d.ts @@ -0,0 +1,11 @@ +export interface Options { + factory?: (origin: string) => string; + maxOrigins?: number; + keepAliveTimeout?: number; + pipelining?: number; +} + +export declare class Agent { + constructor(opts?: Options); + describe(origin: string): string; +} diff --git a/tests/corpus/cjs-open-options-npm/node_modules/optkit/index.js b/tests/corpus/cjs-open-options-npm/node_modules/optkit/index.js new file mode 100644 index 000000000..0034fc442 --- /dev/null +++ b/tests/corpus/cjs-open-options-npm/node_modules/optkit/index.js @@ -0,0 +1,21 @@ +'use strict'; + +function defaultFactory(origin) { + return 'pool(' + origin + ')'; +} + +// undici's Agent constructor shape: named options with defaults, and a rest +// element collecting every other option for the per-origin dispatchers. +class Agent { + constructor({ factory = defaultFactory, maxOrigins = Infinity, ...options } = {}) { + this.factory = factory; + this.maxOrigins = maxOrigins; + this.options = options; + } + + describe(origin) { + return this.factory(origin) + ' max=' + this.maxOrigins + ' ' + JSON.stringify(this.options); + } +} + +module.exports = { Agent }; diff --git a/tests/corpus/cjs-open-options-npm/node_modules/optkit/package.json b/tests/corpus/cjs-open-options-npm/node_modules/optkit/package.json new file mode 100644 index 000000000..9552d73ee --- /dev/null +++ b/tests/corpus/cjs-open-options-npm/node_modules/optkit/package.json @@ -0,0 +1,6 @@ +{ + "name": "optkit", + "version": "1.0.0", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/corpus/cjs-strict-arguments.cjs b/tests/corpus/cjs-strict-arguments.cjs new file mode 100644 index 000000000..78af27f0d --- /dev/null +++ b/tests/corpus/cjs-strict-arguments.cjs @@ -0,0 +1,43 @@ +// `arguments` as a value in CommonJS functions with parameters where Node +// gives the unmapped object: a 'use strict' file prologue (undici's +// webidl.argumentLengthCheck(arguments, 1, name) idiom) and non-simple +// parameter lists. The object's elements never alias the parameters. +'use strict'; + +function argumentLengthCheck(args, min, ctx) { + if (args.length < min) { + throw new TypeError(`${ctx}: ${min} argument${min === 1 ? '' : 's'} required, but only ${args.length} present.`); + } +} + +function getCookies(headers) { + argumentLengthCheck(arguments, 1, 'getCookies'); + return 'cookies of ' + headers; +} + +function setCookie(headers, cookie) { + argumentLengthCheck(arguments, 2, 'setCookie'); + headers = headers + '!'; + return headers + ' ' + cookie + ' ' + arguments[0]; +} + +function count(first, second) { + const args = arguments; + let total = 0; + for (let i = 0; i < args.length; i++) total += args[i]; + return first + ':' + second + ':' + total; +} + +console.log(getCookies('h1')); +console.log(setCookie('h2', 'c=1')); +try { + getCookies(); +} catch (err) { + console.log(err.name, err.message); +} +try { + setCookie('only'); +} catch (err) { + console.log(err.message); +} +console.log(count(1, 2), count(1, 2, 3, 4), count(5)); diff --git a/tests/corpus/cjs-symbol-key-collisions/client.js b/tests/corpus/cjs-symbol-key-collisions/client.js new file mode 100644 index 000000000..aaa9dbb5d --- /dev/null +++ b/tests/corpus/cjs-symbol-key-collisions/client.js @@ -0,0 +1,16 @@ +'use strict'; + +const { kClose, kUrl } = require('./core-symbols'); + +class Client { + constructor(url) { + this[kUrl] = url; + this[kClose] = 'core close slot'; + } + + close() { + return 'closing ' + this[kUrl] + ' (' + this[kClose] + ')'; + } +} + +module.exports = Client; diff --git a/tests/corpus/cjs-symbol-key-collisions/core-symbols.js b/tests/corpus/cjs-symbol-key-collisions/core-symbols.js new file mode 100644 index 000000000..e42309aa3 --- /dev/null +++ b/tests/corpus/cjs-symbol-key-collisions/core-symbols.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = { + kClose: Symbol('close'), + kUrl: Symbol('url'), +}; diff --git a/tests/corpus/cjs-symbol-key-collisions/main.js b/tests/corpus/cjs-symbol-key-collisions/main.js new file mode 100644 index 000000000..9f66616b4 --- /dev/null +++ b/tests/corpus/cjs-symbol-key-collisions/main.js @@ -0,0 +1,11 @@ +// Distinct symbols from two CommonJS key tables that share one description: +// each keeps its own slot, and inspect prints both under Node's spelling. +'use strict'; + +const { inspect } = require('node:util'); +const MockClient = require('./mock-client'); + +const m = new MockClient('http://a'); +console.log(m.shutdown()); +console.log(m.shutdown()); +console.log(inspect(m)); diff --git a/tests/corpus/cjs-symbol-key-collisions/mock-client.js b/tests/corpus/cjs-symbol-key-collisions/mock-client.js new file mode 100644 index 000000000..17113c4bb --- /dev/null +++ b/tests/corpus/cjs-symbol-key-collisions/mock-client.js @@ -0,0 +1,24 @@ +'use strict'; + +const Client = require('./client'); +const { kClose, kCalls } = require('./mock-symbols'); + +// Inherits the core `Symbol('close')` slot and declares a method under the +// mock table's distinct `Symbol('close')` (undici's MockClient shape). +class MockClient extends Client { + constructor(url) { + super(url); + this[kCalls] = ''; + } + + [kClose]() { + this[kCalls] = this[kCalls] + '*'; + return 'mock close ' + this[kCalls]; + } + + shutdown() { + return this[kClose]() + ' then ' + this.close(); + } +} + +module.exports = MockClient; diff --git a/tests/corpus/cjs-symbol-key-collisions/mock-symbols.js b/tests/corpus/cjs-symbol-key-collisions/mock-symbols.js new file mode 100644 index 000000000..9726a8444 --- /dev/null +++ b/tests/corpus/cjs-symbol-key-collisions/mock-symbols.js @@ -0,0 +1,7 @@ +'use strict'; + +// A second table reuses a description the first already holds. +module.exports = { + kClose: Symbol('close'), + kCalls: Symbol('calls'), +}; diff --git a/tests/corpus/cjs-symbol-key-collisions/package.json b/tests/corpus/cjs-symbol-key-collisions/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/corpus/cjs-symbol-key-collisions/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/corpus/cjs-symbol-key-table/main.js b/tests/corpus/cjs-symbol-key-table/main.js new file mode 100644 index 000000000..2c639bf6e --- /dev/null +++ b/tests/corpus/cjs-symbol-key-table/main.js @@ -0,0 +1,19 @@ +// Symbol-keyed class fields and methods whose keys come from a CommonJS +// key table in another module: field declarations, constructor and method +// assignments, a base method calling a subclass's symbol-keyed method, +// reads through the table from a third module, and inspect output. +'use strict'; + +const { inspect } = require('node:util'); +const UpperQueue = require('./upper-queue'); +const { kState, kSize, kName } = require('./symbols'); + +const q = new UpperQueue('jobs'); +console.log(q.push('a'), q.push('b'), q.push(3)); +console.log(q.state, q[kSize], q[kName]); +console.log(q.drain()); +console.log(q.state, q[kSize], q[kState]); +q[kName] = 'renamed'; +console.log(q.state); +console.log(inspect(q)); +console.log(Object.keys(q)); diff --git a/tests/corpus/cjs-symbol-key-table/package.json b/tests/corpus/cjs-symbol-key-table/package.json new file mode 100644 index 000000000..5bbefffba --- /dev/null +++ b/tests/corpus/cjs-symbol-key-table/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/tests/corpus/cjs-symbol-key-table/queue.js b/tests/corpus/cjs-symbol-key-table/queue.js new file mode 100644 index 000000000..c5b7fdaf5 --- /dev/null +++ b/tests/corpus/cjs-symbol-key-table/queue.js @@ -0,0 +1,37 @@ +'use strict'; + +const { kState, kSize, kName, kRun } = require('./symbols'); + +class Queue { + // Field declarations keyed by table symbols. + [kState] = 'idle'; + + [kSize] = 0; + + constructor(name) { + this[kName] = name; + this.items = []; + } + + push(item) { + this.items.push(item); + this[kSize] = this.items.length; + return this[kSize]; + } + + drain() { + this[kState] = 'draining'; + // The subclass declares the symbol-keyed method this calls. + const out = this[kRun](this.items); + this.items = []; + this[kSize] = 0; + this[kState] = 'idle'; + return out; + } + + get state() { + return this[kName] + ' is ' + this[kState]; + } +} + +module.exports = Queue; diff --git a/tests/corpus/cjs-symbol-key-table/symbols.js b/tests/corpus/cjs-symbol-key-table/symbols.js new file mode 100644 index 000000000..1eca50273 --- /dev/null +++ b/tests/corpus/cjs-symbol-key-table/symbols.js @@ -0,0 +1,11 @@ +// The key table: one module exports every symbol its siblings use as +// class keys (undici's core/symbols.js). Each property names one symbol +// identity for all requirers. +'use strict'; + +module.exports = { + kState: Symbol('state'), + kSize: Symbol('size'), + kName: Symbol('name'), + kRun: Symbol('run'), +}; diff --git a/tests/corpus/cjs-symbol-key-table/upper-queue.js b/tests/corpus/cjs-symbol-key-table/upper-queue.js new file mode 100644 index 000000000..cebdb5b58 --- /dev/null +++ b/tests/corpus/cjs-symbol-key-table/upper-queue.js @@ -0,0 +1,12 @@ +'use strict'; + +const Queue = require('./queue'); +const symbols = require('./symbols'); + +class UpperQueue extends Queue { + [symbols.kRun](items) { + return items.map((item) => String(item).toUpperCase()).join(' '); + } +} + +module.exports = UpperQueue; diff --git a/tests/corpus/cjs-unmapped-arguments.cjs b/tests/corpus/cjs-unmapped-arguments.cjs new file mode 100644 index 000000000..ab4a8f3cb --- /dev/null +++ b/tests/corpus/cjs-unmapped-arguments.cjs @@ -0,0 +1,28 @@ +// Sloppy-mode CommonJS functions whose parameter lists are not simple +// (defaults, destructuring) get the unmapped `arguments` object: +// writing a parameter never shows through it. +function withDefault(a, b = 2) { + a = 'changed'; + return [a, b, arguments.length, arguments[0]].join(' '); +} + +function withPattern({ x }, y) { + y = 'changed'; + return [x, y, arguments.length, arguments[1]].join(' '); +} + +function passAlong(first, { y } = {}) { + first = 'changed'; + return collect(arguments) + ' / ' + y + ' ' + first; +} + +function collect(args) { + const out = []; + for (let i = 0; i < args.length; i++) out.push(String(args[i])); + return out.join(','); +} + +console.log(withDefault('orig')); +console.log(withDefault('orig', 5, 6)); +console.log(withPattern({ x: 1 }, 'orig')); +console.log(passAlong('a', { y: 'b' }, 'c')); diff --git a/tests/corpus/code-generation-disallowed.cjs b/tests/corpus/code-generation-disallowed.cjs new file mode 100644 index 000000000..b98f902f7 --- /dev/null +++ b/tests/corpus/code-generation-disallowed.cjs @@ -0,0 +1,36 @@ +// @disallow-code-generation +// The probe packages ship (zod 4's allowsEval): an aliased Function +// constructor inside try/catch decides between a generated fast path and +// an interpreted fallback. Arguments convert to strings before the +// refusal, calling an object's toString. +const F = Function; +function allowsEval() { + try { + new F(""); + return true; + } catch (e) { + console.log("probe:", e.constructor === EvalError, e.message); + return false; + } +} +const parse = allowsEval() + ? new F("input", "return input.trim()") + : (input) => input.trim(); +console.log(JSON.stringify(parse(" value "))); + +try { + new Function({ toString() { console.log("toString called"); return "x"; } }, "return x"); +} catch (e) { + console.log(String(e)); +} +try { + eval({ toString() { console.log("never called"); return "1"; } }); +} catch (e) { + console.log(String(e)); +} +const indirect = eval; +try { + indirect("1"); +} catch (e) { + console.log(e.name); +} diff --git a/tests/corpus/code-generation-disallowed.ts b/tests/corpus/code-generation-disallowed.ts new file mode 100644 index 000000000..d19a30964 --- /dev/null +++ b/tests/corpus/code-generation-disallowed.ts @@ -0,0 +1,44 @@ +// @disallow-code-generation +// eval and the Function constructor throw Node's EvalError, exactly as +// under node --disallow-code-generation-from-strings (the harness runs the +// Node oracle with that flag): catchable, at call time, after the +// arguments evaluate. The Function constructor converts its arguments to +// strings before it refuses; eval refuses without converting. +const log: string[] = []; +const arg = (text: string): string => { + log.push(text); + return text; +}; + +let allowsEval = true; +try { + new Function(""); +} catch (e) { + allowsEval = false; + const err = e as Error; + console.log(err.name, err.message, err instanceof EvalError, err instanceof Error); +} +console.log("allowsEval", allowsEval); + +try { + eval(arg("1 + 1")); +} catch (e) { + log.push((e as Error).name); +} +try { + Function(arg("a"), arg("return a")); +} catch (e) { + log.push((e as Error).name); +} +try { + (0, eval)(arg("2")); +} catch (e) { + log.push((e as Error).name); +} +try { + globalThis.eval(arg("3")); +} catch (e) { + log.push((e as Error).name); +} +console.log(log.join(",")); +console.log(typeof eval, typeof Function); diff --git a/tests/corpus/comma-null-union-return.cjs b/tests/corpus/comma-null-union-return.cjs new file mode 100644 index 000000000..52cef027e --- /dev/null +++ b/tests/corpus/comma-null-union-return.cjs @@ -0,0 +1,15 @@ +// `return log(), null` from a function that otherwise returns a list: the +// comma expression's effects run, then null takes the result's +// representation. +"use strict"; +async function promote(list) { + if (list.length === 0) return console.log("no static mounts"), null; + return list.map((x) => x * 2); +} +function first(list) { + if (list.length === 0) return console.log("empty"), undefined; + return list[0]; +} +promote([]).then((v) => console.log("promoted", v)); +promote([1, 2]).then((v) => console.log("promoted", v)); +console.log(first([]), first([7])); diff --git a/tests/corpus/dns-promises-lookup.ts b/tests/corpus/dns-promises-lookup.ts new file mode 100644 index 000000000..4dbde911a --- /dev/null +++ b/tests/corpus/dns-promises-lookup.ts @@ -0,0 +1,40 @@ +// node:dns/promises and dns.promises: lookup and lookupService as promises +// over the same resolution as the callback forms. Literal addresses settle +// without a resolver; argument errors throw synchronously, as Node +// validates before it creates the promise; failures reject with the +// callback form's errors. +import { lookup } from "node:dns/promises"; +import * as dns from "node:dns"; + +function attempt(label: string, f: () => unknown): void { + try { + f(); + console.log(label, "ok"); + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log(label, e.name, e.code, e.message); + } +} + +attempt("family 5", () => lookup("localhost", 5)); +attempt("options.family", () => lookup("localhost", { family: 3 })); +attempt("hostname", () => lookup(42 as unknown as string)); + +(async () => { + const one = await lookup("127.0.0.1"); + console.log("literal", one.address, one.family); + const all = await dns.promises.lookup("::1", { all: true }); + console.log("literal all", JSON.stringify(all)); + const four = await lookup("localhost", 4); + console.log("family 4", four.address, four.family); + const v4 = await lookup("localhost", { family: 4, all: true }); + console.log("all", JSON.stringify(v4)); + try { + await lookup("no-such-host.invalid"); + } catch (err) { + const e = err as NodeJS.ErrnoException; + console.log("missing", e.name, e.code, e.syscall, e.message); + } + console.log("order", dns.promises.getDefaultResultOrder()); +})(); +process.nextTick(() => console.log("tick before the first settlement")); diff --git a/tests/corpus/dns-promises-untyped.cjs b/tests/corpus/dns-promises-untyped.cjs new file mode 100644 index 000000000..de25f1404 --- /dev/null +++ b/tests/corpus/dns-promises-untyped.cjs @@ -0,0 +1,34 @@ +// The promise forms from CommonJS, with untyped results: lookup's +// { address, family } objects and arrays, the promise form's stricter +// options (family is a number only), a rejected lookup, and +// lookupService's argument checks. +const dnsPromises = require("dns/promises"); +const { promises } = require("node:dns"); + +async function main() { + console.log(await dnsPromises.lookup("127.0.0.1")); + console.log(await promises.lookup("::1", { all: true })); + console.log(await dnsPromises.lookup("localhost", { family: 4 })); + try { + dnsPromises.lookup("localhost", { family: "IPv4" }); + } catch (e) { + console.log(e.code, e.message); + } + try { + dnsPromises.lookup("localhost", "4"); + } catch (e) { + console.log(e.code, e.message); + } + await dnsPromises.lookup("no-such-host.invalid").catch((e) => console.log("rejected", e.code)); + try { + dnsPromises.lookupService("not-an-ip", 80); + } catch (e) { + console.log(e.code, e.message); + } + try { + dnsPromises.lookupService("127.0.0.1", 70000); + } catch (e) { + console.log(e.code, e.message); + } +} +main(); diff --git a/tests/corpus/function-local-detached-methods.js b/tests/corpus/function-local-detached-methods.js new file mode 100644 index 000000000..9bde77a19 --- /dev/null +++ b/tests/corpus/function-local-detached-methods.js @@ -0,0 +1,22 @@ +// A method of a function-local class that never reads `this` keeps working +// when it is taken off an instance and called detached or with another +// receiver. +'use strict'; + +function make(label) { + class Box { + one() { + return 1; + } + + named() { + return `box ${label}`; + } + } + return new Box(); +} + +const box = make('a'); +const one = box.one; +console.log(one(), box.one.call(null), box.one.call({}), box.named()); +console.log([box, make('b')].map((b) => b.one()).join(',')); diff --git a/tests/corpus/function-local-detached-methods.ts b/tests/corpus/function-local-detached-methods.ts new file mode 100644 index 000000000..ba4d3ba3c --- /dev/null +++ b/tests/corpus/function-local-detached-methods.ts @@ -0,0 +1,19 @@ +// A method of a function-local class that never reads `this` keeps working +// when it is taken off an instance and called detached or with another +// receiver. +function make(label: string) { + class Box { + one(): number { + return 1; + } + + named(): string { + return `box ${label}`; + } + } + return new Box(); +} + +const box = make("a"); +const one = box.one; +console.log(one(), box.one.call(null), box.one.call({}), box.named()); diff --git a/tests/corpus/function-local-private-evaluations.js b/tests/corpus/function-local-private-evaluations.js new file mode 100644 index 000000000..6e18a4766 --- /dev/null +++ b/tests/corpus/function-local-private-evaluations.js @@ -0,0 +1,172 @@ +// Each evaluation of a function-local class creates its own private names. +// A method taken from one evaluation and called with an instance of another +// (through call, a prototype read, or a checked receiver) finds no brand: +// field reads and writes throw Node's TypeErrors and `#x in this` answers +// false. Calls within one evaluation share the instance's fields. +'use strict'; + +function make(label) { + class Counter { + #count = 0; + #label = label; + #format = (n) => `${label}#${n}`; + + bump() { + this.#count = this.#count + 1; + return `${this.#label}:${this.#count}`; + } + + add(n) { + this.#count += n; + return this.#count; + } + + increment() { + this.#count++; + return this.#count; + } + + reset() { + [this.#count] = [0]; + return this.#count; + } + + assign() { + return (this.#count = 7); + } + + order(note) { + this.#count = note(); + return this.#count; + } + + has() { + return #count in this; + } + + later() { + return () => this.#label; + } + + describe() { + return this.#format(this.#count); + } + + get count() { + return this.#count; + } + + set count(value) { + this.#count = value; + } + } + + class Tagged extends Counter { + tag() { + return `tagged ${this.bump()}`; + } + } + + return { counter: () => new Counter(), tagged: () => new Tagged() }; +} + +function attempt(label, fn) { + try { + console.log(label, fn()); + } catch (e) { + console.log(label, e instanceof TypeError, e.message); + } +} + +const one = make('one'); +const two = make('two'); +const a = one.counter(); +const b = two.counter(); +const a2 = one.counter(); + +console.log(a.bump(), a.bump(), b.bump(), a2.bump()); +console.log(a.add(5), a.increment(), a.has(), a.later()()); + +// Borrowed within one evaluation: the fields are the instance's own. +const bump = a.bump; +console.log(bump.call(a2), a2.bump(), bump.call(a)); +console.log(Object.getPrototypeOf(a).bump.call(a2)); + +// Borrowed across evaluations. +attempt('call read', () => a.bump.call(b)); +attempt('compound', () => a.add.call(b, 1)); +attempt('update', () => a.increment.call(b)); +attempt('destructure', () => a.reset.call(b)); +attempt('assign value', () => a.assign.call(b)); +attempt('value first', () => + a.order.call(b, () => { + console.log('value evaluated'); + return 1; + }), +); +attempt('proto read', () => Object.getPrototypeOf(a).bump.call(b)); +attempt('has other', () => a.has.call(b)); +attempt('has own', () => a.has.call(a2)); +attempt('arrow', () => a.later.call(b)()); +attempt('field call', () => a.describe.call(b)); +attempt('field call own', () => a.describe.call(a2)); +attempt('apply', () => a.bump.apply(b)); +attempt('bind', () => a.bump.bind(b)()); +attempt('Reflect.apply', () => Reflect.apply(a.bump, b, [])); +const proto = Object.getPrototypeOf(a); +const bumpDescriptor = /** @type {any} */ (Object.getOwnPropertyDescriptor(proto, 'bump')); +const countDescriptor = /** @type {any} */ (Object.getOwnPropertyDescriptor(proto, 'count')); +attempt('descriptor value', () => bumpDescriptor.value.call(b)); +attempt('getter', () => countDescriptor.get.call(b)); +attempt('setter', () => countDescriptor.set.call(b, 5)); +attempt('Reflect.get', () => Reflect.get(a, 'count', b)); +attempt('Reflect.set', () => Reflect.set(a, 'count', 3, b)); +attempt('own getter', () => [countDescriptor.get.call(a2), Reflect.get(a, 'count', a2)].join(' ')); +console.log(b.bump(), b.count, (b.count = 10), b.bump()); + +// Receivers that are not instances. +for (const recv of [{}, [], undefined, null, 1, 'x']) { + attempt(`read ${String(recv)}`, () => a.bump.call(recv)); + attempt(`write ${String(recv)}`, () => a.reset.call(recv)); + attempt(`has ${String(recv)}`, () => a.has.call(recv)); +} + +// A subclass declared after the class in the same function carries the +// brand of its own evaluation. +const t1 = one.tagged(); +const t2 = two.tagged(); +console.log(t1.tag(), t2.tag(), t1.has(), a.has.call(t1), a.has.call(t2)); +attempt('subclass across', () => a.bump.call(t2)); +attempt('subclass own', () => b.bump.call(t2)); + +// undici's iterator guard: the error comes from the method itself. +function createIterator(name) { + class FastIterableIterator { + #target; + #index = 0; + + constructor(target) { + this.#target = target; + } + + next() { + if (typeof this !== 'object' || this === null || !(#target in this)) { + throw new TypeError(`'next' called on an object that does not implement interface ${name} Iterator.`); + } + const index = this.#index; + if (index >= this.#target.length) return { value: undefined, done: true }; + this.#index = index + 1; + return { value: this.#target[index], done: false }; + } + } + return (target) => new FastIterableIterator(target); +} + +const headers = createIterator('Headers'); +const params = createIterator('Params'); +const it = headers(['a', 'b']); +const next = it.next; +console.log(JSON.stringify(next.call(it)), JSON.stringify(it.next()), JSON.stringify(next.call(it))); +for (const recv of [headers(['c']), params(['d']), {}, null]) { + attempt('next', () => JSON.stringify(next.call(recv))); +} diff --git a/tests/corpus/function-local-private-fields.js b/tests/corpus/function-local-private-fields.js new file mode 100644 index 000000000..c8c999659 --- /dev/null +++ b/tests/corpus/function-local-private-fields.js @@ -0,0 +1,48 @@ +// A class declared inside a function with private fields used only on +// `this` (undici's webidl createIterator): each evaluation's instances meet +// only their own methods, so one compiled layout serves every evaluation. +'use strict'; + +function createIterator(name, entries) { + class FastIterableIterator { + #target; + #kind; + #index; + + constructor(target, kind) { + this.#target = target; + this.#kind = kind; + this.#index = 0; + } + + next() { + if (typeof this !== 'object' || this === null || !(#target in this)) { + throw new TypeError(`'next' called on an object that does not implement interface ${name} Iterator.`); + } + const values = entries(this.#target); + if (this.#index >= values.length) return { value: undefined, done: true }; + const pair = values[this.#index]; + this.#index = this.#index + 1; + if (this.#kind === 'key') return { value: pair[0], done: false }; + if (this.#kind === 'value') return { value: pair[1], done: false }; + return { value: pair[0] + '=' + pair[1], done: false }; + } + } + return function (target, kind) { + return new FastIterableIterator(target, kind); + }; +} + +const headers = createIterator('Headers', (t) => t.pairs); +const params = createIterator('Params', (t) => t.list); + +function drain(it) { + const out = []; + for (let step = it.next(); !step.done; step = it.next()) out.push(step.value); + return out.join(','); +} + +const target = { pairs: [['a', '1'], ['b', '2']] }; +console.log(drain(headers(target, 'key'))); +console.log(drain(headers(target, 'value'))); +console.log(drain(params({ list: [['x', 'y']] }, 'key+value'))); diff --git a/tests/corpus/graceful-fs-alias/main.cjs b/tests/corpus/graceful-fs-alias/main.cjs new file mode 100644 index 000000000..874cab8c2 --- /dev/null +++ b/tests/corpus/graceful-fs-alias/main.cjs @@ -0,0 +1,34 @@ +// graceful-fs compiles as node:fs (its retry queue for EMFILE/ENFILE is +// the one documented difference): the same file operations, errors and +// callback order as under Node with the real package (vendored here, +// 4.2.11, ISC). gracefulify(fs) is a no-op statement in compiled programs. +const fs = require("graceful-fs"); +const { readFileSync, writeFileSync } = require("graceful-fs"); +const path = require("node:path"); +const os = require("node:os"); + +fs.gracefulify(require("fs")); +const dir = fs.mkdtempSync(path.join(os.tmpdir(), "graceful-fs-alias-")); +const file = path.join(dir, "a.txt"); +writeFileSync(file, "hello"); +fs.appendFileSync(file, " world"); +console.log(readFileSync(file, "utf8"), fs.existsSync(file), fs.statSync(file).size); +console.log(fs.readdirSync(dir)); +try { + fs.readFileSync(path.join(dir, "missing.txt")); +} catch (e) { + console.log("sync", e.code, e.syscall); +} + +fs.readFile(path.join(dir, "missing.txt"), "utf8", (err) => { + console.log("callback", err.code); + fs.writeFile(file, "rewritten", (werr) => { + console.log("written", werr); + fs.readFile(file, "utf8", (rerr, text) => { + console.log("read", rerr, text); + fs.rmSync(dir, { recursive: true }); + console.log("removed", fs.existsSync(dir)); + }); + }); +}); +console.log("scheduled"); diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/LICENSE b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/LICENSE new file mode 100644 index 000000000..e906a25ac --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) 2011-2022 Isaac Z. Schlueter, Ben Noordhuis, and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/clone.js b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/clone.js new file mode 100644 index 000000000..dff3cc8c5 --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/clone.js @@ -0,0 +1,23 @@ +'use strict' + +module.exports = clone + +var getPrototypeOf = Object.getPrototypeOf || function (obj) { + return obj.__proto__ +} + +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj + + if (obj instanceof Object) + var copy = { __proto__: getPrototypeOf(obj) } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/graceful-fs.js b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/graceful-fs.js new file mode 100644 index 000000000..8d5b89e4f --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/graceful-fs.js @@ -0,0 +1,448 @@ +var fs = require('fs') +var polyfills = require('./polyfills.js') +var legacy = require('./legacy-streams.js') +var clone = require('./clone.js') + +var util = require('util') + +/* istanbul ignore next - node 0.x polyfill */ +var gracefulQueue +var previousSymbol + +/* istanbul ignore else - node 0.x polyfill */ +if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { + gracefulQueue = Symbol.for('graceful-fs.queue') + // This is used in testing by future versions + previousSymbol = Symbol.for('graceful-fs.previous') +} else { + gracefulQueue = '___graceful-fs.queue' + previousSymbol = '___graceful-fs.previous' +} + +function noop () {} + +function publishQueue(context, queue) { + Object.defineProperty(context, gracefulQueue, { + get: function() { + return queue + } + }) +} + +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } + +// Once time initialization +if (!fs[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + var queue = global[gracefulQueue] || [] + publishQueue(fs, queue) + + // Patch fs.close/closeSync to shared queue version, because we need + // to retry() whenever a close happens *anywhere* in the program. + // This is essential when multiple graceful-fs instances are + // in play at the same time. + fs.close = (function (fs$close) { + function close (fd, cb) { + return fs$close.call(fs, fd, function (err) { + // This function uses the graceful-fs shared queue + if (!err) { + resetQueue() + } + + if (typeof cb === 'function') + cb.apply(this, arguments) + }) + } + + Object.defineProperty(close, previousSymbol, { + value: fs$close + }) + return close + })(fs.close) + + fs.closeSync = (function (fs$closeSync) { + function closeSync (fd) { + // This function uses the graceful-fs shared queue + fs$closeSync.apply(fs, arguments) + resetQueue() + } + + Object.defineProperty(closeSync, previousSymbol, { + value: fs$closeSync + }) + return closeSync + })(fs.closeSync) + + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(fs[gracefulQueue]) + require('assert').equal(fs[gracefulQueue].length, 0) + }) + } +} + +if (!global[gracefulQueue]) { + publishQueue(global, fs[gracefulQueue]); +} + +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; +} + +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$readFile(path, options, cb) + + function go$readFile (path, options, cb, startTime) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$writeFile(path, data, options, cb) + + function go$writeFile (path, data, options, cb, startTime) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$appendFile(path, data, options, cb) + + function go$appendFile (path, data, options, cb, startTime) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$copyFile = fs.copyFile + if (fs$copyFile) + fs.copyFile = copyFile + function copyFile (src, dest, flags, cb) { + if (typeof flags === 'function') { + cb = flags + flags = 0 + } + return go$copyFile(src, dest, flags, cb) + + function go$copyFile (src, dest, flags, cb, startTime) { + return fs$copyFile(src, dest, flags, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$copyFile, [src, dest, flags, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + var fs$readdir = fs.readdir + fs.readdir = readdir + var noReaddirOptionVersions = /^v[0-5]\./ + function readdir (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + var go$readdir = noReaddirOptionVersions.test(process.version) + ? function go$readdir (path, options, cb, startTime) { + return fs$readdir(path, fs$readdirCallback( + path, options, cb, startTime + )) + } + : function go$readdir (path, options, cb, startTime) { + return fs$readdir(path, options, fs$readdirCallback( + path, options, cb, startTime + )) + } + + return go$readdir(path, options, cb) + + function fs$readdirCallback (path, options, cb, startTime) { + return function (err, files) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([ + go$readdir, + [path, options, cb], + err, + startTime || Date.now(), + Date.now() + ]) + else { + if (files && files.sort) + files.sort() + + if (typeof cb === 'function') + cb.call(this, err, files) + } + } + } + } + + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } + + var fs$ReadStream = fs.ReadStream + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } + + var fs$WriteStream = fs.WriteStream + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } + + Object.defineProperty(fs, 'ReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'WriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) + + // legacy names + var FileReadStream = ReadStream + Object.defineProperty(fs, 'FileReadStream', { + get: function () { + return FileReadStream + }, + set: function (val) { + FileReadStream = val + }, + enumerable: true, + configurable: true + }) + var FileWriteStream = WriteStream + Object.defineProperty(fs, 'FileWriteStream', { + get: function () { + return FileWriteStream + }, + set: function (val) { + FileWriteStream = val + }, + enumerable: true, + configurable: true + }) + + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } + + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() + + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } + + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } + + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } + + function createReadStream (path, options) { + return new fs.ReadStream(path, options) + } + + function createWriteStream (path, options) { + return new fs.WriteStream(path, options) + } + + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null + + return go$open(path, flags, mode, cb) + + function go$open (path, flags, mode, cb, startTime) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb], err, startTime || Date.now(), Date.now()]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + } + }) + } + } + + return fs +} + +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + fs[gracefulQueue].push(elem) + retry() +} + +// keep track of the timeout between retry() calls +var retryTimer + +// reset the startTime and lastTime to now +// this resets the start of the 60 second overall timeout as well as the +// delay between attempts so that we'll retry these jobs sooner +function resetQueue () { + var now = Date.now() + for (var i = 0; i < fs[gracefulQueue].length; ++i) { + // entries that are only a length of 2 are from an older version, don't + // bother modifying those since they'll be retried anyway. + if (fs[gracefulQueue][i].length > 2) { + fs[gracefulQueue][i][3] = now // startTime + fs[gracefulQueue][i][4] = now // lastTime + } + } + // call retry to make sure we're actively processing the queue + retry() +} + +function retry () { + // clear the timer and remove it to help prevent unintended concurrency + clearTimeout(retryTimer) + retryTimer = undefined + + if (fs[gracefulQueue].length === 0) + return + + var elem = fs[gracefulQueue].shift() + var fn = elem[0] + var args = elem[1] + // these items may be unset if they were added by an older graceful-fs + var err = elem[2] + var startTime = elem[3] + var lastTime = elem[4] + + // if we don't have a startTime we have no way of knowing if we've waited + // long enough, so go ahead and retry this item now + if (startTime === undefined) { + debug('RETRY', fn.name, args) + fn.apply(null, args) + } else if (Date.now() - startTime >= 60000) { + // it's been more than 60 seconds total, bail now + debug('TIMEOUT', fn.name, args) + var cb = args.pop() + if (typeof cb === 'function') + cb.call(null, err) + } else { + // the amount of time between the last attempt and right now + var sinceAttempt = Date.now() - lastTime + // the amount of time between when we first tried, and when we last tried + // rounded up to at least 1 + var sinceStart = Math.max(lastTime - startTime, 1) + // backoff. wait longer than the total time we've been retrying, but only + // up to a maximum of 100ms + var desiredDelay = Math.min(sinceStart * 1.2, 100) + // it's been long enough since the last retry, do it again + if (sinceAttempt >= desiredDelay) { + debug('RETRY', fn.name, args) + fn.apply(null, args.concat([startTime])) + } else { + // if we can't do this job yet, push it to the end of the queue + // and let the next iteration check again + fs[gracefulQueue].push(elem) + } + } + + // schedule our next run if one isn't already scheduled + if (retryTimer === undefined) { + retryTimer = setTimeout(retry, 0) + } +} diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/legacy-streams.js b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/legacy-streams.js new file mode 100644 index 000000000..d617b50fc --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/legacy-streams.js @@ -0,0 +1,118 @@ +var Stream = require('stream').Stream + +module.exports = legacy + +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } + + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); + + Stream.call(this); + + var self = this; + + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; + + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.encoding) this.setEncoding(this.encoding); + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } + + if (this.start > this.end) { + throw new Error('start must be <= end'); + } + + this.pos = this.start; + } + + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } + + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } + + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } + + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); + + Stream.call(this); + + this.path = path; + this.fd = null; + this.writable = true; + + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } + + this.pos = this.start; + } + + this.busy = false; + this._queue = []; + + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } +} diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/package.json b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/package.json new file mode 100644 index 000000000..87babf024 --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/package.json @@ -0,0 +1,53 @@ +{ + "name": "graceful-fs", + "description": "A drop-in replacement for fs, making various improvements.", + "version": "4.2.11", + "repository": { + "type": "git", + "url": "https://github.com/isaacs/node-graceful-fs" + }, + "main": "graceful-fs.js", + "directories": { + "test": "test" + }, + "scripts": { + "preversion": "npm test", + "postversion": "npm publish", + "postpublish": "git push origin --follow-tags", + "test": "nyc --silent node test.js | tap -c -", + "posttest": "nyc report" + }, + "keywords": [ + "fs", + "module", + "reading", + "retry", + "retries", + "queue", + "error", + "errors", + "handling", + "EMFILE", + "EAGAIN", + "EINVAL", + "EPERM", + "EACCESS" + ], + "license": "ISC", + "devDependencies": { + "import-fresh": "^2.0.0", + "mkdirp": "^0.5.0", + "rimraf": "^2.2.8", + "tap": "^16.3.4" + }, + "files": [ + "fs.js", + "graceful-fs.js", + "legacy-streams.js", + "polyfills.js", + "clone.js" + ], + "tap": { + "reporter": "classic" + } +} diff --git a/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/polyfills.js b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/polyfills.js new file mode 100644 index 000000000..453f1a9e7 --- /dev/null +++ b/tests/corpus/graceful-fs-alias/node_modules/graceful-fs/polyfills.js @@ -0,0 +1,355 @@ +var constants = require('constants') + +var origCwd = process.cwd +var cwd = null + +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform + +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd +} +try { + process.cwd() +} catch (er) {} + +// This check is needed until node.js 12 is required +if (typeof process.chdir === 'function') { + var chdir = process.chdir + process.chdir = function (d) { + cwd = null + chdir.call(process, d) + } + if (Object.setPrototypeOf) Object.setPrototypeOf(process.chdir, chdir) +} + +module.exports = patch + +function patch (fs) { + // (re-)implement some things that are known busted or missing. + + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } + + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } + + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. + + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) + + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) + + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) + + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) + + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) + + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) + + // if lchmod/lchown do not exist, then make them no-ops + if (fs.chmod && !fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (fs.chown && !fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) + } + fs.lchownSync = function () {} + } + + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. + + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = typeof fs.rename !== 'function' ? fs.rename + : (function (fs$rename) { + function rename (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + } + if (Object.setPrototypeOf) Object.setPrototypeOf(rename, fs$rename) + return rename + })(fs.rename) + } + + // if read() returns EAGAIN, then just try it again. + fs.read = typeof fs.read !== 'function' ? fs.read + : (function (fs$read) { + function read (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + + // This ensures `util.promisify` works as it does for native `fs.read`. + if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read) + return read + })(fs.read) + + fs.readSync = typeof fs.readSync !== 'function' ? fs.readSync + : (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } + } + }})(fs.readSync) + + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) + }) + }) + } + + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + } + + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK") && fs.futimes) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } + + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + + } else if (fs.futimes) { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } + } + + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + + + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + function callback (er, stats) { + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + if (cb) cb.apply(this, arguments) + } + return options ? orig.call(fs, target, options, callback) + : orig.call(fs, target, callback) + } + } + + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options) { + var stats = options ? orig.call(fs, target, options) + : orig.call(fs, target) + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + return stats; + } + } + + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true + + if (er.code === "ENOSYS") + return true + + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } + + return false + } +} diff --git a/tests/corpus/local-class-detached-method.ts b/tests/corpus/local-class-detached-method.ts new file mode 100644 index 000000000..528539c48 --- /dev/null +++ b/tests/corpus/local-class-detached-method.ts @@ -0,0 +1,16 @@ +// A method that never reads `this`, taken off an instance of a class +// declared inside a function: the detached method runs without a receiver. +function helper(): string { + return "helper"; +} +function make() { + return class Local { + tag = "t"; + plain(): string { + return helper(); + } + }; +} +const C = make(); +const detached = new C().plain; +console.log(detached()); diff --git a/tests/corpus/object-literal-accessors-dyn.ts b/tests/corpus/object-literal-accessors-dyn.ts new file mode 100644 index 000000000..3b7dc95e4 --- /dev/null +++ b/tests/corpus/object-literal-accessors-dyn.ts @@ -0,0 +1,31 @@ +// Object literals with get/set accessors held as `any` or `unknown` keep +// their accessors, as in Node: a read calls the getter, a write calls the +// setter, Object.keys, Object.entries and JSON.stringify list the accessor +// in declaration order and call the getter, and the property descriptor +// has a get and set function. Writes to data properties reach the +// original object. + +function dyn(value: unknown): any { + return value; +} + +let count = 0; +const lit = { + a: 1, + get g(): number { count++; return 42; }, + set g(v: number) { console.log("set", v); }, + b: 2, + get only(): string { return "read-only"; }, +}; +const d = dyn(lit); +console.log("read", d.g, d.only, count); +console.log("keys", Object.keys(d).join(","), JSON.stringify(d), count); +console.log("entries", Object.entries(d).map((e) => e.join("=")).join(";")); +console.log("in", "g" in d, Object.hasOwn(d, "g")); +d.g = 7; +d.a = 5; +console.log("write", lit.a, d.a); +const desc = Object.getOwnPropertyDescriptor(d, "g")!; +console.log("descriptor", typeof desc.get, typeof desc.set, desc.enumerable, desc.configurable); +const u: unknown = lit; +console.log("unknown", (u as any).g, JSON.stringify(u)); diff --git a/tests/corpus/private-in-checked-primitives.js b/tests/corpus/private-in-checked-primitives.js new file mode 100644 index 000000000..f73902b74 --- /dev/null +++ b/tests/corpus/private-in-checked-primitives.js @@ -0,0 +1,28 @@ +// `#x in v` where v is a checked value: objects answer by brand, and a +// primitive operand throws Node's TypeError, which names the value as +// String(v) spells it. +'use strict'; + +class Base {} + +class Box extends Base { + #value = 1; + + static is(v) { + return #value in v; + } +} + +class Sub extends Box {} + +console.log(Box.is(new Box()), Box.is(new Sub()), Box.is(new Base())); +console.log(Box.is({}), Box.is([]), Box.is(function () {}), Box.is(Box)); + +const primitives = [1, -0, NaN, 1.5, 'str', '', true, false, undefined, null, 10n, -5n, Symbol('s'), Symbol()]; +for (const v of primitives) { + try { + console.log('no throw', Box.is(v)); + } catch (e) { + console.log(e instanceof TypeError, e.message); + } +} diff --git a/tests/corpus/required-module-truthiness.cjs b/tests/corpus/required-module-truthiness.cjs new file mode 100644 index 000000000..626d1cd4d --- /dev/null +++ b/tests/corpus/required-module-truthiness.cjs @@ -0,0 +1,10 @@ +// A required builtin module object in a boolean context is truthy, as any +// object: readable-stream's `process.env.READABLE_STREAM === "disable" && +// Stream` and `Stream || exports` guards. +"use strict"; +var Stream = require("stream"); +var os = require("os"); +console.log(Stream ? "stream" : "none", !!os, !os); +console.log(process.env.SCRIPTC_UNSET_FLAG === "disable" && Stream ? "disabled" : "default"); +var fallback = Stream || null; +console.log(fallback === Stream); diff --git a/tests/corpus/stream-web-readable.ts b/tests/corpus/stream-web-readable.ts new file mode 100644 index 000000000..5117a1738 --- /dev/null +++ b/tests/corpus/stream-web-readable.ts @@ -0,0 +1,22 @@ +// node:stream/web exports the web stream globals themselves: its +// ReadableStream is globalThis.ReadableStream, with the same behavior +// (identity is pinned by stream-web-require.cjs). +import { ReadableStream } from "node:stream/web"; +import type { ReadableStreamDefaultController } from "node:stream/web"; + +const stream = new ReadableStream({ + start(controller: ReadableStreamDefaultController) { + controller.enqueue("a"); + controller.enqueue("b"); + controller.close(); + }, +}); +const reader = stream.getReader(); +(async () => { + for (;;) { + const { done, value } = await reader.read(); + if (done) break; + console.log("chunk", value); + } + for await (const n of ReadableStream.from([1, 2, 3])) console.log("from", n); +})(); diff --git a/tests/corpus/stream-web-require.cjs b/tests/corpus/stream-web-require.cjs new file mode 100644 index 000000000..9c0308010 --- /dev/null +++ b/tests/corpus/stream-web-require.cjs @@ -0,0 +1,10 @@ +// The undici shape: CommonJS code reads ReadableStream off +// require("stream/web") (lazily, and as a fallback for the global); the +// value is the global class. +let ReadableStream; +function getStream() { + if (!ReadableStream) ReadableStream = require("stream/web").ReadableStream; + return ReadableStream; +} +const Fallback = globalThis.ReadableStream || require("node:stream/web").ReadableStream; +console.log(getStream() === globalThis.ReadableStream, Fallback === getStream()); diff --git a/tests/corpus/strict-arguments-parameters.cjs b/tests/corpus/strict-arguments-parameters.cjs new file mode 100644 index 000000000..463cb316a --- /dev/null +++ b/tests/corpus/strict-arguments-parameters.cjs @@ -0,0 +1,15 @@ +// A CommonJS file under "use strict": a function with named parameters +// reads `arguments`, which in strict code never aliases those parameters. +"use strict"; +function wrap(fn, label) { + label = label + "!"; + return [label, arguments.length, arguments[1], typeof arguments[0]].join(" "); +} +function sum(a, b) { + a = 100; + let total = 0; + for (let i = 0; i < arguments.length; i++) total += arguments[i]; + return total; +} +console.log(wrap(() => 1, "tag")); +console.log(sum(1, 2, 3)); diff --git a/tests/corpus/temporal-absent-probe.js b/tests/corpus/temporal-absent-probe.js new file mode 100644 index 000000000..79312bbcd --- /dev/null +++ b/tests/corpus/temporal-absent-probe.js @@ -0,0 +1,17 @@ +// The feature-probe shape packages ship (smol-toml's date handling): a +// JavaScript module checks for Temporal and falls back to Date, reading +// the global object too. +const temporal = typeof Temporal === "object" ? Temporal : null; +console.log(temporal, globalThis.Temporal, "Temporal" in globalThis); + +function toDate(value) { + if (temporal !== null) return temporal.Instant.from(value).toString(); + return new Date(value).toISOString(); +} +console.log(toDate("1979-05-27T07:32:00Z")); + +try { + Temporal.Now.plainDateISO(); +} catch (e) { + console.log(e.name + ": " + e.message); +} diff --git a/tests/corpus/temporal-absent.ts b/tests/corpus/temporal-absent.ts new file mode 100644 index 000000000..102044f31 --- /dev/null +++ b/tests/corpus/temporal-absent.ts @@ -0,0 +1,35 @@ +// Node 24 defines no Temporal global. Programs that name it type-check +// against TS 7's Temporal declarations and see Node's answers: typeof is +// "undefined", the global object has no such property, and any use throws +// the catchable ReferenceError at the root read, before arguments run. +console.log(typeof Temporal, typeof globalThis.Temporal); +const hasTemporal = typeof Temporal !== "undefined"; +console.log("hasTemporal", hasTemporal); + +function parseDate(text: string): string { + if (hasTemporal) return Temporal.PlainDate.from(text).toString(); + return new Date(text).toISOString().slice(0, 10); +} +console.log(parseDate("2024-02-29")); + +let evaluated = false; +const argument = (): string => { + evaluated = true; + return "2024-01-01"; +}; +try { + Temporal.PlainDate.from(argument()); +} catch (e) { + const err = e as Error; + console.log(err.name, err.message, err instanceof ReferenceError, "argument evaluated:", evaluated); +} +try { + console.log(Temporal.Now.instant().epochMilliseconds); +} catch (e) { + console.log((e as Error).message); +} +try { + new Temporal.Duration(1); +} catch (e) { + console.log((e as Error).message); +} diff --git a/tests/corpus/v8-heap-statistics.ts b/tests/corpus/v8-heap-statistics.ts new file mode 100644 index 000000000..9586f60a2 --- /dev/null +++ b/tests/corpus/v8-heap-statistics.ts @@ -0,0 +1,16 @@ +// node:v8 is importable. getHeapStatistics answers allocator-derived +// figures in Node's shape and key order; the values are approximate (no +// V8 heap exists in a compiled binary), so this pins the shape and the +// relations between fields, not the numbers. +import * as v8 from "node:v8"; + +const warm: string[] = []; +for (let i = 0; i < 1000; i++) warm.push("item " + i); + +const stats = v8.getHeapStatistics(); +console.log(Object.keys(stats).join(",")); +console.log(Object.values(stats).every((value) => typeof value === "number")); +console.log(stats.used_heap_size <= stats.total_heap_size); +console.log(stats.heap_size_limit > 0, stats.total_available_size <= stats.heap_size_limit); +console.log(stats.does_zap_garbage === 0 || stats.does_zap_garbage === 1); +console.log(stats.number_of_native_contexts >= 1, warm.length); diff --git a/tests/corpus/v8-require-optional.cjs b/tests/corpus/v8-require-optional.cjs new file mode 100644 index 000000000..339e0af5f --- /dev/null +++ b/tests/corpus/v8-require-optional.cjs @@ -0,0 +1,18 @@ +// @deferred-fences: 2 +// The dd-trace shape: a module requires v8 at top level and touches its +// engine-observation members only on optional paths. Requiring it and +// destructuring members succeeds; getHeapStatistics answers. +const v8 = require("v8"); +const { writeHeapSnapshot, setFlagsFromString } = require("node:v8"); + +console.log(typeof v8, typeof v8.getHeapStatistics); +const stats = v8.getHeapStatistics(); +console.log(stats.heap_size_limit > 0, "used_heap_size" in stats); + +function snapshot(enabled) { + if (enabled) return writeHeapSnapshot(); + return "skipped"; +} +console.log(snapshot(process.env.SCRIPTC_NEVER_SET === "1")); +if (process.env.SCRIPTC_NEVER_SET === "1") setFlagsFromString("--expose-gc"); +console.log("done"); diff --git a/tests/corpus/webassembly-api.mjs b/tests/corpus/webassembly-api.mjs new file mode 100644 index 000000000..5215cd3a2 --- /dev/null +++ b/tests/corpus/webassembly-api.mjs @@ -0,0 +1,179 @@ +// The WebAssembly JavaScript API over modules translated to native code when +// the program is built: compile, instantiate, validate, Module, Instance, +// exported functions, memories, tables and globals, imports, linking two +// instances, every trap, link errors, the error classes, externref values +// and the asynchronous entry points. The modules are inline base64 literals +// (their text is in tests/wasm-spec/corpus/*.wat). +const api = Buffer.from("AGFzbQEAAAABLwlgAX8Bf2ACf38Bf2ABfwBgAABgAAF/YAJ+fgF+YAJ/fwJ/f2ABfQF9YAN/f38AAi0EA2VudgNsb2cAAgNlbnYEZmFpbAAAA2VudgRiYXNlA38AA2VudgNiaWcDfgADEhEAAAMBAQQFBgcAAAQAAAAICAQFAXABAggFBAEBAQQGFwN/AUEAC3wARAAAAAAAAApAC34AIwELB5kBEwZtZW1vcnkCAAV0YWJsZQEAB2NvdW50ZXIDAgJwaQMDBGJpZzIDBANhZGQABQVhcHBseQAGBGJ1bXAABwVtdWw2NAAIBmRpdm1vZAAJBm5hcnJvdwAKBnN0cmxlbgALBGdyb3cADAVwYWdlcwANCGNhbGxGYWlsAA4EdHJhcAAPB3JlY3Vyc2UAEARmaWxsABEEY29weQASCAEECQgBAEEACwICAwqHAhEHACAAQQJsCwcAIAAgAGwLBgAjABAACwcAIAAgAWoLCQAgASAAEQAACwsAIwJBAWokAiMCCwcAIAAgAX4LDAAgACABbSAAIAFvCwQAIAALIAEBfwJAA0AgACABai0AAEUNASABQQFqIQEMAAsLIAELBgAgAEAACwQAPwALCQAgABABQQFqC1kAAkACQAJAAkACQAJAAkAgAA4GAAECAwQFBgsAC0EBQQBuDwtB/////wcoAgAPC0EBQQJBABEBAA8LQQFBBxEAAA8LRAAAAAAAAPh/qg8LQYCAgIB4QX9tCwwAIABBAWoQEEEBagsLACAAIAEgAvwLAAsMACAAIAEgAvwKAAALCw4BAEEQCwhzY3JpcHRjAA==", "base64"); +const linked = Buffer.from("AGFzbQEAAAABFwRgAn9/AX9gAX8Bf2ABfwBgA39/fwF/AjYEA2FwaQZtZW1vcnkCAAEDYXBpBXRhYmxlAXAAAgNhcGkHY291bnRlcgN/AQNhcGkDYWRkAAADBQQBAAIDBycEBHJlYWQAAQh1c2VUYWJsZQACCnNldENvdW50ZXIAAwRhZGQzAAQKJwQHACAALQAACwkAIAEgABEBAAsGACAAJAALDAAgACABEAAgAhAACw==", "base64"); +const refs = Buffer.from("AGFzbQEAAAABHwZgAW8Bb2ACf28AYAF/AW9gAW8Bf2ACb38Bf2AAAX8DBwYAAQIDBAUEBQFvAQEKBgYBbwHQbwsHPAgEcmVmcwEABHNsb3QDAARrZWVwAAAFc3RvcmUAAQRsb2FkAAIGaXNOdWxsAAMEZ3JvdwAEBHNpemUABQosBgQAIAALCAAgACABJgALBgAgACUACwUAIADRCwkAIAAgAfwPAAsFAPwQAAs=", "base64"); + +const show = (v) => { + if (typeof v === "bigint") return `${v}n`; + if (Array.isArray(v)) return `[${v.map(show).join(", ")}]`; + return String(v); +}; +const attempt = (label, f) => { + try { + console.log(label, show(f())); + } catch (e) { + console.log(label, e instanceof Error ? `${e.name}: ${e.message}` : `threw ${show(e)}`); + } +}; + +console.log(WebAssembly.validate(api), WebAssembly.validate(new Uint8Array([1, 2, 3]))); +attempt("validate(5)", () => WebAssembly.validate(5)); +const module = new WebAssembly.Module(api); +console.log(module instanceof WebAssembly.Module, module instanceof WebAssembly.Instance, String(module)); +console.log(module); + +const logged = []; +const env = { + log: (v) => logged.push(`log ${v}`), + fail(v) { + if (v < 0) throw new RangeError(`negative ${v}`); + return v * 10; + }, + base: 41, + big: 9007199254740993n, +}; +const instance = new WebAssembly.Instance(module, { env }); +console.log(logged.join(","), instance instanceof WebAssembly.Instance); +const x = instance.exports; +console.log(Object.keys(x).join(","), Object.isFrozen(x), Object.getPrototypeOf(x)); +console.log(x.add.name, x.add.length, typeof x.add, x.add === instance.exports.add); + +attempt("add", () => x.add(2, 3)); +attempt("add coerced", () => x.add("40", { valueOf: () => 2 })); +attempt("add wraps", () => x.add(0x7fffffff, 1)); +attempt("add missing", () => x.add(5)); +attempt("apply double", () => x.apply(0, 21)); +attempt("apply square", () => x.apply(1, 12)); +attempt("bump", () => [x.bump(), x.bump(), x.counter.value]); +x.counter.value = 100; +attempt("bump after set", () => x.bump()); +attempt("pi", () => x.pi.value); +attempt("set pi", () => { + x.pi.value = 1; +}); +attempt("big2", () => x.big2.value); +attempt("mul64", () => x.mul64(123456789n, 1000000007n)); +attempt("mul64 wraps", () => x.mul64(-1n, 2n ** 63n)); +attempt("mul64 number", () => x.mul64(1, 2)); +attempt("divmod", () => x.divmod(-17, 5)); +attempt("narrow", () => x.narrow(0.1)); +attempt("strlen", () => x.strlen(16)); + +const bytes = new Uint8Array(x.memory.buffer); +console.log(bytes.length, String.fromCharCode(...bytes.subarray(16, 23))); +console.log(x.memory instanceof WebAssembly.Memory, x.table instanceof WebAssembly.Table, x.counter instanceof WebAssembly.Global); +attempt("grow", () => x.grow(1)); +attempt("pages", () => x.pages()); +console.log(x.memory.buffer.byteLength); +attempt("memory.grow", () => x.memory.grow(1)); +attempt("memory.grow too far", () => x.memory.grow(2)); +console.log(x.memory.buffer.byteLength, x.pages()); +attempt("grow too far in wasm", () => x.grow(5)); +x.fill(100, 0x41, 5); +x.copy(200, 100, 5); +console.log(String.fromCharCode(...new Uint8Array(x.memory.buffer, 200, 5))); +attempt("fill out of bounds", () => x.fill(x.memory.buffer.byteLength - 2, 0, 5)); + +attempt("callFail", () => x.callFail(4)); +attempt("callFail throws", () => x.callFail(-1)); +for (let k = 0; k <= 6; k++) attempt(`trap ${k}`, () => x.trap(k)); +attempt("recurse", () => x.recurse(0)); +try { + x.trap(0); +} catch (e) { + console.log( + e instanceof WebAssembly.RuntimeError, + e instanceof Error, + e instanceof WebAssembly.LinkError, + Object.keys(e).length, + Object.prototype.hasOwnProperty.call(e, "name"), + ); +} +attempt("after traps", () => x.add(1, 1)); + +console.log(x.table.length); +attempt("table.get out of range", () => x.table.get(5)); +attempt("table.grow", () => x.table.grow(1)); +console.log(x.table.length); +x.table.set(2, x.add); +console.log(x.table.get(2) === x.add, x.table.length); +attempt("table.get past the end", () => x.table.get(3)); + +const linkedInstance = new WebAssembly.Instance(new WebAssembly.Module(linked), { api: x }); +const y = linkedInstance.exports; +attempt("read", () => y.read(16)); +attempt("useTable", () => y.useTable(1, 9)); +attempt("useTable signature", () => y.useTable(2, 9)); +attempt("useTable null", () => y.useTable(3, 9)); +y.setCounter(7); +attempt("shared counter", () => x.counter.value); +attempt("add3", () => y.add3(1, 2, 3)); + +attempt("no imports", () => new WebAssembly.Instance(module)); +attempt("missing module", () => new WebAssembly.Instance(module, {})); +attempt("imports not an object", () => new WebAssembly.Instance(module, 5)); +attempt("not callable", () => new WebAssembly.Instance(module, { env: { ...env, log: 1 } })); +attempt("global type", () => new WebAssembly.Instance(module, { env: { ...env, base: "x" } })); +attempt("bigint global", () => new WebAssembly.Instance(module, { env: { ...env, big: 1 } })); +attempt("memory import", () => + new WebAssembly.Instance(new WebAssembly.Module(linked), { + api: { memory: {}, table: x.table, counter: x.counter, add: x.add }, + }), +); +attempt("mutable global as number", () => + new WebAssembly.Instance(new WebAssembly.Module(linked), { + api: { memory: x.memory, table: x.table, counter: 1, add: x.add }, + }), +); +attempt("signature mismatch", () => + new WebAssembly.Instance(new WebAssembly.Module(linked), { + api: { memory: x.memory, table: x.table, counter: x.counter, add: x.bump }, + }), +); +attempt("Instance(5)", () => new WebAssembly.Instance(5)); +attempt("Module(5)", () => new WebAssembly.Module(5)); +try { + new WebAssembly.Module(new Uint8Array([0, 97, 115, 109, 2, 0, 0, 0])); +} catch (e) { + console.log(e.name, e instanceof WebAssembly.CompileError, e instanceof Error); +} + +const custom = new WebAssembly.RuntimeError("custom", { cause: 5 }); +console.log(String(custom), custom.name, custom.message, custom.cause); +console.log(custom instanceof WebAssembly.RuntimeError, custom instanceof WebAssembly.CompileError, custom instanceof Error); +console.log(String(WebAssembly.LinkError("called")), String(new WebAssembly.CompileError())); + +const z = new WebAssembly.Instance(new WebAssembly.Module(refs)).exports; +const token = { id: 1 }; +console.log(z.keep(token) === token, z.keep(null), z.keep(undefined), z.isNull(null), z.isNull(undefined), z.isNull(0)); +z.store(0, token); +console.log(z.load(0) === token, z.refs.get(0) === token); +attempt("grow refs", () => z.grow("filler", 3)); +console.log(z.size(), z.load(2), z.refs.length); +z.slot.value = token; +console.log(z.slot.value === token); +attempt("store out of range", () => z.store(10, token)); + +const order = []; +const compiled = WebAssembly.compile(api) + .then((m) => { + order.push(`compiled ${m instanceof WebAssembly.Module}`); + return WebAssembly.instantiate(m, { env }); + }) + .then((i) => { + order.push(`instance ${i instanceof WebAssembly.Instance} ${i.exports.add(1, 1)}`); + }); +const pair = WebAssembly.instantiate(api, { env }).then((result) => { + order.push(`pair ${Object.keys(result).join(",")} ${result.instance.exports.add(2, 2)}`); +}); +const rejected = WebAssembly.compile(new Uint8Array([1])).catch((e) => order.push(`rejected ${e.name}`)); +const typeError = WebAssembly.instantiate(5).catch((e) => order.push(`${e.name}: ${e.message}`)); +const linkError = WebAssembly.instantiate(api, {}).catch((e) => order.push(`${e.name}: ${e.message}`)); +await Promise.all([compiled, pair, rejected, typeError, linkError]); +console.log(order.sort().join("\n")); +console.log(logged.length); diff --git a/tests/corpus/webassembly-dylink/LICENSE.quickjs-wasi b/tests/corpus/webassembly-dylink/LICENSE.quickjs-wasi new file mode 100644 index 000000000..5a2e99d85 --- /dev/null +++ b/tests/corpus/webassembly-dylink/LICENSE.quickjs-wasi @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Vercel, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/tests/corpus/webassembly-dylink/extensions.js b/tests/corpus/webassembly-dylink/extensions.js new file mode 100644 index 000000000..8c6b26feb --- /dev/null +++ b/tests/corpus/webassembly-dylink/extensions.js @@ -0,0 +1,372 @@ +/** + * WASM Dynamic Linking - Extension Loader + * + * Loads WASM shared libraries (.so) compiled with wasi-sdk as extensions + * for the QuickJS WASM runtime. Extensions can call QuickJS C API functions + * directly through dynamic linking (shared memory + symbol resolution). + * + * Key concepts: + * - Extensions are WASM shared libraries with a `dylink.0` custom section + * - They share linear memory and the indirect function table with the main module + * - Symbol imports (env.*) are resolved against the main module's exports + * - Each extension gets a unique __memory_base and __table_base + * - Extensions export an init function (e.g., `qjs_ext_url_init`) + */ +const decoder = new TextDecoder(); +/** + * Parse a null-terminated "key=value\nkey=value" string from WASM memory + * into a Record. Used to read version info from extensions. + */ +function parseVersionString(memory, ptr) { + const mem = new Uint8Array(memory.buffer); + let end = ptr; + while (mem[end] !== 0) + end++; + if (end === ptr) + return {}; + const str = decoder.decode(mem.slice(ptr, end)); + const result = {}; + for (const line of str.split('\n')) { + const eq = line.indexOf('='); + if (eq > 0) { + result[line.slice(0, eq)] = line.slice(eq + 1); + } + } + return result; +} +/** + * Check if an extension exports a versions function and parse the result. + */ +function extractExtensionVersions(ext, memory) { + const versionsFnName = `qjs_ext_${ext.name.replace(/-/g, '_')}_versions`; + const versionsFn = ext.instance.exports[versionsFnName]; + if (typeof versionsFn !== 'function') + return undefined; + const ptr = versionsFn(); + if (ptr === 0) + return undefined; + return parseVersionString(memory, ptr); +} +// ---- dylink.0 parser ---- +/** Read a ULEB128-encoded integer from a byte array */ +function readULEB128(bytes, offset) { + let result = 0; + let shift = 0; + while (offset.value < bytes.length) { + const byte = bytes[offset.value++]; + result |= (byte & 0x7f) << shift; + if ((byte & 0x80) === 0) + break; + shift += 7; + } + return result; +} +/** Parse the dylink.0 custom section from a WASM module */ +export function parseDylink(module) { + const sections = WebAssembly.Module.customSections(module, 'dylink.0'); + if (sections.length === 0) + return null; + const bytes = new Uint8Array(sections[0]); + const offset = { value: 0 }; + const info = { + memorySize: 0, + memoryAlignment: 0, + tableSize: 0, + tableAlignment: 0, + needed: [], + }; + while (offset.value < bytes.length) { + const subsectionType = readULEB128(bytes, offset); + const subsectionSize = readULEB128(bytes, offset); + const subsectionEnd = offset.value + subsectionSize; + if (subsectionType === 1) { + // WASM_DYLINK_MEM_INFO + info.memorySize = readULEB128(bytes, offset); + info.memoryAlignment = readULEB128(bytes, offset); + info.tableSize = readULEB128(bytes, offset); + info.tableAlignment = readULEB128(bytes, offset); + } + else if (subsectionType === 2) { + // WASM_DYLINK_NEEDED + const count = readULEB128(bytes, offset); + for (let i = 0; i < count; i++) { + const len = readULEB128(bytes, offset); + const name = new TextDecoder().decode(bytes.slice(offset.value, offset.value + len)); + offset.value += len; + info.needed.push(name); + } + } + offset.value = subsectionEnd; + } + return info; +} +// ---- Alignment helper ---- +/** Align a value up to 2^align */ +function alignUp(value, align) { + if (align <= 0) + return value; + const mask = (1 << align) - 1; + return (value + mask) & ~mask; +} +// ---- Extension Loader ---- +/** + * Load a WASM shared library extension and link it against the main module. + * + * @param descriptor - Extension description (name, wasm bytes, init function) + * @param mainExports - The main QuickJS WASM module's exports + * @param wasiBuiltins - Built-in WASI implementations (lowest priority) + * @param wasiUserOverrides - User-provided WASI overrides (highest priority) + * @param memoryProxy - A memory proxy for extension WASI factories + * @param allocBase - Optional fixed memory/table bases (for snapshot restore) + */ +export async function loadExtension(descriptor, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy, allocBase) { + // Compile the extension module + let module; + if (descriptor.wasm instanceof WebAssembly.Module) { + module = descriptor.wasm; + } + else { + module = await WebAssembly.compile(descriptor.wasm); + } + // Parse dylink.0 section + const dylink = parseDylink(module); + if (!dylink) { + throw new Error(`Extension "${descriptor.name}" is not a WASM shared library (missing dylink.0 section)`); + } + const memory = mainExports.memory; + const table = mainExports.__indirect_function_table; + const stackPointer = mainExports.__stack_pointer; + const mallocFn = mainExports.malloc; + if (!memory) + throw new Error('Main module does not export memory'); + if (!table) + throw new Error('Main module does not export __indirect_function_table'); + if (!mallocFn) + throw new Error('Main module does not export malloc'); + // Allocate memory region for the extension's static data + let memoryBase; + let tableBase; + if (allocBase) { + // Restore mode: use the exact same bases as before + memoryBase = allocBase.memoryBase; + tableBase = allocBase.tableBase; + } + else { + // Fresh allocation + if (dylink.memorySize > 0) { + // Use malloc from the main module to allocate in the shared heap + memoryBase = mallocFn(dylink.memorySize); + if (memoryBase === 0) { + throw new Error(`Failed to allocate ${dylink.memorySize} bytes for extension "${descriptor.name}"`); + } + // Zero-initialize the region + new Uint8Array(memory.buffer, memoryBase, dylink.memorySize).fill(0); + } + else { + memoryBase = 0; + } + } + // Grow the function table if needed + if (allocBase) { + tableBase = allocBase.tableBase; + // Ensure table is large enough + if (tableBase + dylink.tableSize > table.length) { + table.grow(tableBase + dylink.tableSize - table.length); + } + } + else { + tableBase = table.length; + if (dylink.tableSize > 0) { + table.grow(dylink.tableSize); + } + } + // Build the import object + const extImports = WebAssembly.Module.imports(module); + const importObj = { + env: {}, + 'GOT.mem': {}, + 'GOT.func': {}, + }; + // Provide wasi_snapshot_preview1 imports to extensions that need them. + // Layered merge: builtins → extension-provided → user overrides. + const needsWasi = extImports.some((imp) => imp.module === 'wasi_snapshot_preview1'); + if (needsWasi) { + const extWasi = (descriptor.wasi && memoryProxy) ? descriptor.wasi(memoryProxy) : undefined; + importObj['wasi_snapshot_preview1'] = { + ...wasiBuiltins, // 1. Built-in defaults (lowest priority) + ...extWasi, // 2. Extension-provided + ...wasiUserOverrides // 3. User overrides (highest priority) + }; + } + // Collect names of functions the extension exports (for self-resolution) + const extExportNames = new Set(WebAssembly.Module.exports(module) + .filter((e) => e.kind === 'function') + .map((e) => e.name)); + const unresolvedFuncs = new Set(); + for (const imp of extImports) { + if (imp.module === 'env') { + if (imp.name === 'memory' && imp.kind === 'memory') { + importObj.env.memory = memory; + } + else if (imp.name === '__indirect_function_table' && + imp.kind === 'table') { + importObj.env.__indirect_function_table = table; + } + else if (imp.name === '__memory_base' && imp.kind === 'global') { + importObj.env.__memory_base = new WebAssembly.Global({ value: 'i32', mutable: false }, memoryBase); + } + else if (imp.name === '__table_base' && imp.kind === 'global') { + importObj.env.__table_base = new WebAssembly.Global({ value: 'i32', mutable: false }, tableBase); + } + else if (imp.name === '__stack_pointer' && imp.kind === 'global') { + importObj.env.__stack_pointer = stackPointer; + } + else if (imp.kind === 'function') { + // Resolve function imports from the main module's exports + const resolved = mainExports[imp.name]; + if (resolved && typeof resolved === 'function') { + importObj.env[imp.name] = resolved; + } + else { + // Provide a trap stub for unresolved symbols (e.g., C++ runtime + // functions like wstring methods that are imported but never called). + // If actually called at runtime, this will throw. + unresolvedFuncs.add(imp.name); + importObj.env[imp.name] = () => { + throw new Error(`Extension "${descriptor.name}" called unresolved symbol: env.${imp.name}`); + }; + } + } + else if (imp.kind === 'global') { + // Other globals - try to resolve from main exports + const resolved = mainExports[imp.name]; + if (resolved instanceof WebAssembly.Global) { + importObj.env[imp.name] = resolved; + } + else { + // Create a zero-initialized mutable global as fallback + importObj.env[imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0); + } + } + } + else if (imp.module === 'GOT.mem' && imp.kind === 'global') { + // GOT.mem entries are mutable globals containing memory addresses of symbols + // Try to resolve the symbol's address - for now, create mutable globals + // that can be patched by __wasm_apply_data_relocs + importObj['GOT.mem'][imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0); + } + else if (imp.module === 'GOT.func' && imp.kind === 'global') { + // GOT.func entries are mutable globals containing table indices of functions. + // We resolve these by finding the function in the main module's exports, + // adding it to the indirect function table, and providing the table index. + const resolved = mainExports[imp.name]; + if (resolved && typeof resolved === 'function') { + const idx = table.length; + table.grow(1); + table.set(idx, resolved); + importObj['GOT.func'][imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, idx); + } + else { + importObj['GOT.func'][imp.name] = new WebAssembly.Global({ value: 'i32', mutable: true }, 0); + } + } + } + // Self-resolve: in WASM shared libraries, the linker with --allow-undefined + // may leave symbols as both imports and exports (e.g., C++ weak symbols from + // libc++ string.cpp.o that satisfy references from ada.o). We use indirection + // through mutable wrapper functions: first create wrappers that initially trap, + // then instantiate, then patch the wrappers to forward to the instance's own exports. + // This way all internal calls go through the wrappers which point to the final exports. + const selfResolvable = [...unresolvedFuncs].filter((name) => extExportNames.has(name)); + // For self-resolvable symbols, create mutable wrappers + const wrappers = {}; + for (const name of selfResolvable) { + const wrapper = { target: null }; + wrappers[name] = wrapper; + // Replace the trap stub with a wrapper that forwards to the target + importObj.env[name] = (...args) => { + if (!wrapper.target) { + throw new Error(`Extension "${descriptor.name}" called unresolved symbol during init: env.${name}`); + } + return wrapper.target(...args); + }; + } + const instance = await WebAssembly.instantiate(module, importObj); + const extExports = instance.exports; + // Patch wrappers to point to the instance's own exports + for (const name of selfResolvable) { + const selfExport = extExports[name]; + if (typeof selfExport === 'function') { + wrappers[name].target = selfExport; + } + } + // Apply data relocations if the extension has them + if (typeof extExports.__wasm_apply_data_relocs === 'function') { + extExports.__wasm_apply_data_relocs(); + } + // Call constructors if present + if (typeof extExports.__wasm_call_ctors === 'function') { + extExports.__wasm_call_ctors(); + } + const initFn = descriptor.initFn ?? `qjs_ext_${descriptor.name.replace(/-/g, '_')}_init`; + const ext = { + name: descriptor.name, + module, + instance, + dylink, + memoryBase, + tableBase, + initFn, + }; + // Check for version reporting + ext.versions = extractExtensionVersions(ext, memory); + return ext; +} +/** + * Call the extension's init function, passing the JSContext and JSRuntime + * pointers from the main module. + */ +export function initExtension(ext, mainExports) { + const initFunc = ext.instance.exports[ext.initFn]; + if (typeof initFunc !== 'function') { + throw new Error(`Extension "${ext.name}" does not export init function "${ext.initFn}"`); + } + // Get the JSContext and JSRuntime pointers from the main module + const ctxPtr = mainExports.qjs_get_context_ptr(); + const rtPtr = mainExports.qjs_get_runtime_ptr(); + // Call the extension's init function: qjs_ext_xxx_init(ctx, rt) + const result = initFunc(ctxPtr, rtPtr); + if (result !== 0) { + throw new Error(`Extension "${ext.name}" init function returned error code ${result}`); + } +} +/** + * Re-instantiate extensions for snapshot restore. + * + * During restore, we need to: + * 1. Instantiate extension modules with the same memory/table bases + * 2. Let them populate the function table (via elem segments and __wasm_apply_data_relocs) + * 3. Do NOT call the init function (the state is already in the snapshot memory) + * + * This reconstructs the function table entries that extensions need, without + * modifying linear memory (which will be overwritten by the snapshot). + */ +export async function restoreExtensions(descriptors, extensionMeta, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy) { + const loaded = []; + for (const meta of extensionMeta) { + // Find the descriptor for this extension + const descriptor = descriptors.find((d) => d.name === meta.name); + if (!descriptor) { + throw new Error(`Extension "${meta.name}" required by snapshot but not provided`); + } + // Load with fixed bases + const ext = await loadExtension(descriptor, mainExports, wasiBuiltins, wasiUserOverrides, memoryProxy, { + memoryBase: meta.memoryBase, + tableBase: meta.tableBase, + }); + ext.initFn = meta.initFn; + loaded.push(ext); + } + return loaded; +} +//# sourceMappingURL=extensions.js.map \ No newline at end of file diff --git a/tests/corpus/webassembly-dylink/main.mjs b/tests/corpus/webassembly-dylink/main.mjs new file mode 100644 index 000000000..42237b1f1 --- /dev/null +++ b/tests/corpus/webassembly-dylink/main.mjs @@ -0,0 +1,101 @@ +// quickjs-wasi's WebAssembly extension loader, run through its own code: +// extensions.js is dist/extensions.js of quickjs-wasi 3.6.2, unmodified +// (MIT, LICENSE.quickjs-wasi). It parses the dylink.0 custom section +// (WebAssembly.Module.customSections), reads the extension's imports and +// exports (Module.imports, Module.exports), allocates the extension's data +// in the main module's memory, grows the shared function table and stores +// main-module functions in it (table.grow, table.set), creates +// __memory_base, __table_base and GOT globals (new WebAssembly.Global), and +// instantiates the extension against the main module's memory, table and +// stack pointer. The two modules stand in for quickjs.wasm and an +// extension .so; their text is in tests/wasm-spec/corpus/dylink-*.wat. +import { initExtension, loadExtension, parseDylink } from "./extensions.js"; + +const mainBytes = Buffer.from( + "AGFzbQEAAAABEANgAX8Bf2AAAX9gAn9/AX8DBwYAAQECAgAEBAFwAAQFAwEAAgYTA38BQYCABAt/AUGACAt/AUEACweOAQkGbWVtb3J5AgAZX19pbmRpcmVjdF9mdW5jdGlvbl90YWJsZQEAD19fc3RhY2tfcG9pbnRlcgMABm1hbGxvYwAAE3Fqc19nZXRfY29udGV4dF9wdHIAARNxanNfZ2V0X3J1bnRpbWVfcHRyAAILanNfcmVnaXN0ZXIAAwdqc19jYWxsAAQGaGVscGVyAAUKXAYXAQF/IwEhASMBIABqQQ9qQXBxJAEgAQsFAEHkAAsFAEHIAQsZAEGABCMCQQRsaiABNgIAIwJBAWokAiMCCxMAIAFBgAQgAEEEbGooAgARAAALCAAgAEHoB2oL", + "base64", +); +const extBytes = Buffer.from( + "AGFzbQEAAAABEwRgAX8Bf2ACf38Bf2AAAGAAAX8C0AEKA2VudgZtZW1vcnkCAAEDZW52GV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBcAAAA2Vudg9fX3N0YWNrX3BvaW50ZXIDfwEDZW52DV9fbWVtb3J5X2Jhc2UDfwADZW52DF9fdGFibGVfYmFzZQN/AAdHT1QubWVtB2NvdW50ZXIDfwEIR09ULmZ1bmMGaGVscGVyA38BA2Vudgtqc19yZWdpc3RlcgABA2Vudg5taXNzaW5nX3N5bWJvbAAAA2VudgtzZWxmX3N5bWJvbAAAAwwLAAACAgEDAAAAAwMHogEJGF9fd2FzbV9hcHBseV9kYXRhX3JlbG9jcwAFEV9fd2FzbV9jYWxsX2N0b3JzAAYRcWpzX2V4dF9kZW1vX2luaXQABxVxanNfZXh0X2RlbW9fdmVyc2lvbnMACAtzZWxmX3N5bWJvbAAJCHVzZV9zZWxmAAoLdXNlX21pc3NpbmcACw1zdGFja19wb2ludGVyAAwMYnVtcF9jb3VudGVyAA0JCAEAIwILAgMECnYLBwAgAEEDbAsJACAAIwQRAAALCQAjAUEgaiQDCwkAIwNBBTYCAAsZACAAIwIQABogACMCQQFqEAAaIAFByAFrCwQAIwELBwAgAEEBagsGACAAEAILBgAgABABCwQAIwALFAAjAyMDKAIAQQFqNgIAIwMoAgALCxwBACMBCxZuYW1lPWRlbW8KdmVyc2lvbj0xLjIAABoIZHlsaW5rLjABBDACAgACCQEHbGliYy5zbw==", + "base64", +); +// A module without a dylink.0 section (tests/wasm-spec/corpus/jsapi-own.wat). +const plainBytes = Buffer.from( + "AGFzbQEAAAABBgFgAX8BfwMCAQAEBQFwAQIIBQQBAQEEBhICfwFBBwt8AEQAAAAAAAD4PwsHKgcDbWVtAgAEbWVtMgIAA3RhYgEAAWcDAAJnMgMABWZpeGVkAwECaWQAAAkHAQBBAQsBAAoGAQQAIAAL", + "base64", +); + +const failure = (e) => (e instanceof Error ? `${e.name}: ${e.message}` : `threw ${String(e)}`); + +const { instance } = await WebAssembly.instantiate(mainBytes, {}); +const mainExports = instance.exports; +const table = mainExports.__indirect_function_table; + +console.log("dylink", JSON.stringify(parseDylink(new WebAssembly.Module(extBytes)))); +console.log("plain dylink", parseDylink(new WebAssembly.Module(plainBytes))); + +const ext = await loadExtension({ name: "demo", wasm: extBytes }, mainExports, {}, {}, null); +console.log("loaded", ext.name, ext.memoryBase, ext.tableBase, ext.initFn); +console.log("versions", JSON.stringify(ext.versions)); +console.log("dylink", JSON.stringify(ext.dylink)); +console.log("table length", table.length, table.get(6) === mainExports.helper); +// The extension's own functions in the shared table, called from here. +const triple = table.get(4); +console.log("table functions", triple(14), table.get(5) === table.get(5), triple.name, triple.length); +console.log("module", ext.module instanceof WebAssembly.Module, ext.instance instanceof WebAssembly.Instance); + +initExtension(ext, mainExports); +console.log("js_call", mainExports.js_call(0, 7), mainExports.js_call(1, 5)); +const x = ext.instance.exports; +console.log("self symbol", x.use_self(41)); +try { + x.use_missing(1); +} catch (e) { + console.log("missing symbol", failure(e)); +} +console.log("stack pointer", x.stack_pointer(), mainExports.__stack_pointer.value); +mainExports.__stack_pointer.value = 60000; +console.log("shared stack pointer", x.stack_pointer()); +console.log("counter", x.bump_counter(), x.bump_counter()); +console.log("memory", new Uint32Array(mainExports.memory.buffer, ext.memoryBase + 32, 1)[0]); + +// Snapshot restore (what restoreExtensions does per extension): the same +// bases, from a compiled Module, without init. +const restored = await loadExtension( + { name: "demo", wasm: new WebAssembly.Module(extBytes) }, + mainExports, + {}, + {}, + null, + { memoryBase: ext.memoryBase, tableBase: ext.tableBase }, +); +console.log("restored", restored.memoryBase, restored.tableBase, table.length); +console.log("restored counter", restored.instance.exports.bump_counter()); + +// A second extension instance gets its own bases. +const second = await loadExtension({ name: "demo", wasm: extBytes }, mainExports, {}, {}, null); +console.log("second", second.memoryBase, second.tableBase, table.length); +initExtension(second, mainExports); +console.log("js_call", mainExports.js_call(2, 10), mainExports.js_call(3, 10)); + +async function tryLoad(label, wasm, exports) { + try { + await loadExtension({ name: label, wasm }, exports, {}, {}, null); + console.log(label, "loaded"); + } catch (e) { + console.log(label, failure(e)); + } +} +await tryLoad("plain", plainBytes, mainExports); +await tryLoad("no memory", extBytes, { ...mainExports, memory: undefined }); +await tryLoad("no table", extBytes, { ...mainExports, __indirect_function_table: undefined }); +try { + initExtension({ ...ext, initFn: "qjs_ext_none_init" }, mainExports); +} catch (e) { + console.log("init", failure(e)); +} +try { + initExtension(ext, { ...mainExports, qjs_get_runtime_ptr: () => 201 }); +} catch (e) { + console.log("init", failure(e)); +} diff --git a/tests/corpus/webassembly-externref-lifetimes.mjs b/tests/corpus/webassembly-externref-lifetimes.mjs new file mode 100644 index 000000000..6d61d9c93 --- /dev/null +++ b/tests/corpus/webassembly-externref-lifetimes.mjs @@ -0,0 +1,44 @@ +// Externref values the guest holds stay alive until it is done with them: +// an import's result returned through an export, and a table's or global's +// reference read into a local before the guest or JavaScript replaces it. +// Each object is created fresh, so the runtime holds the only reference. +// (The module's text is in tests/wasm-spec/corpus/externref-lifetimes.wat.) +const bytes = Buffer.from("AGFzbQEAAAABCAJgAAFvYAAAAhgCA2VudgRtYWtlAAADZW52BWNsZWFyAAEDBQQAAAAABAQBbwABBgYBbwHQbwsHQQYEc2xvdAMABWNlbGxzAQAEcGFzcwACCnRha2VHbG9iYWwAAwl0YWtlVGFibGUABA50YWtlQWNyb3NzQ2FsbAAFCjUEBAAQAAsOAQFvIwAhANBvJAAgAAsSAQFvQQAlACEAQQDQbyYAIAALDAEBbyMAIQAQASAACw==", "base64"); + +let made = 0; +let exports; +const env = { + make: () => ({ id: ++made, label: `made ${made}`, items: [made, made * 2] }), + clear: () => { + exports.slot.value = null; + }, +}; +exports = new WebAssembly.Instance(new WebAssembly.Module(bytes), { env }).exports; + +const describe = (v) => (v === null ? "null" : `${v.id} ${v.label} ${v.items.join("+")}`); +const fresh = (n) => ({ id: n, label: `fresh ${n}`, items: [n, n + 1] }); + +const passed = []; +for (let i = 0; i < 200; i++) passed.push(describe(exports.pass())); +console.log("pass", passed.length, passed[0], passed[199]); + +const fromGlobal = []; +for (let i = 0; i < 200; i++) { + exports.slot.value = fresh(i); + fromGlobal.push(describe(exports.takeGlobal())); +} +console.log("global", fromGlobal[0], fromGlobal[199], describe(exports.slot.value)); + +const fromTable = []; +for (let i = 0; i < 200; i++) { + exports.cells.set(0, fresh(1000 + i)); + fromTable.push(describe(exports.takeTable())); +} +console.log("table", fromTable[0], fromTable[199], describe(exports.cells.get(0))); + +const acrossCall = []; +for (let i = 0; i < 200; i++) { + exports.slot.value = fresh(2000 + i); + acrossCall.push(describe(exports.takeAcrossCall())); +} +console.log("across call", acrossCall[0], acrossCall[199], describe(exports.slot.value)); diff --git a/tests/corpus/webassembly-feature-probe.js b/tests/corpus/webassembly-feature-probe.js index 7a164fd90..053e5c0c6 100644 --- a/tests/corpus/webassembly-feature-probe.js +++ b/tests/corpus/webassembly-feature-probe.js @@ -1,7 +1,6 @@ -// @deferred-fences: 2 // A JavaScript dependency's WebAssembly glue: the feature probe answers as -// in Node, and the loader that would instantiate a module is never reached -// (reaching it would throw scriptc's located WebAssembly fence). +// in Node, and the loader that would instantiate a module compiles (it is +// not reached here). function supportsWasm() { return typeof WebAssembly === "object" && typeof WebAssembly.instantiate === "function"; } diff --git a/tests/corpus/webassembly-jsapi.mjs b/tests/corpus/webassembly-jsapi.mjs new file mode 100644 index 000000000..ad9739972 --- /dev/null +++ b/tests/corpus/webassembly-jsapi.mjs @@ -0,0 +1,356 @@ +// The WebAssembly JavaScript API's constructors and reflection: new +// WebAssembly.Memory, Table and Global (descriptors, defaults, conversions +// and every error), using them as imports (limits, types and mutability +// checked with Node's LinkError messages), the identity of exported +// objects, WebAssembly.Module.imports, exports and customSections, and the +// classes called without new. The modules are inline base64 literals +// (their text is in tests/wasm-spec/corpus/jsapi.wat and jsapi-own.wat). +const jsapi = Buffer.from("AGFzbQEAAAABKglgAX8Bf2ABfwBgAAF/YAF8AXxgAX4AYAABb2ABbwBgAn9/AX9gAn9/AAJbBwNlbnYDbG9nAAEDZW52Bm1lbW9yeQIBAQQDZW52BXRhYmxlAXABAggDZW52B2NvdW50ZXIDfwEDZW52BXNjYWxlA3wAA2VudgNiaWcDfgEDZW52A3JlZgNvAQMPDgAAAgMEBQYHCAACAAIBB5UBEQRidW1wAAMGc2NhbGVkAAQGYmlnQWRkAAUGZ2V0UmVmAAYGc2V0UmVmAAcFYXBwbHkACAVzdG9yZQAJBGxvYWQACgRzaXplAAsEZ3JvdwAMCXRhYmxlU2l6ZQANA3NheQAOBm1lbW9yeQIABXRhYmxlAQAHY291bnRlcgMAC2Fsc29Db3VudGVyAwAGZG91YmxlAAEJCAEAQQALAgECCnAOBwAgAEECbAsHACAAIABsCwsAIwBBAWokACMACwcAIAAjAaILCQAjAiAAfCQCCwQAIwMLBgAgACQDCwkAIAEgABEAAAsJACAAIAE6AAALBwAgAC0AAAsEAD8ACwYAIABAAAsFAPwQAAsGACAAEAALAAwIZHlsaW5rLjABAgMACgRtZXRhaGVsbG8ACwhkeWxpbmsuMAQFAAEA", "base64"); +const own = Buffer.from("AGFzbQEAAAABBgFgAX8BfwMCAQAEBQFwAQIIBQQBAQEEBhICfwFBBwt8AEQAAAAAAAD4PwsHKgcDbWVtAgAEbWVtMgIAA3RhYgEAAWcDAAJnMgMABWZpeGVkAwECaWQAAAkHAQBBAQsBAAoGAQQAIAAL", "base64"); + +const show = (v) => { + if (typeof v === "bigint") return `${v}n`; + if (typeof v === "number") return Object.is(v, -0) ? "-0" : String(v); + if (typeof v === "function") return "function"; + if (Array.isArray(v)) return `[${v.map(show).join(", ")}]`; + if (v instanceof WebAssembly.Memory) return "Memory"; + if (v instanceof WebAssembly.Table) return "Table"; + if (v instanceof WebAssembly.Global) return "Global"; + if (v instanceof ArrayBuffer) return `ArrayBuffer(${v.byteLength})`; + if (v !== null && typeof v === "object") return JSON.stringify(v); + return String(v); +}; +const attempt = (label, f) => { + try { + console.log(label, show(f())); + } catch (e) { + console.log(label, e instanceof Error ? `${e.name}: ${e.message}` : `threw ${show(e)}`); + } +}; + +// ── WebAssembly.Global ──────────────────────────────────────────────────── +console.log("Global"); +attempt("i32 default", () => new WebAssembly.Global({ value: "i32" }).value); +attempt("i64 default", () => new WebAssembly.Global({ value: "i64" }).value); +attempt("f32", () => new WebAssembly.Global({ value: "f32" }, 1.1).value); +attempt("f64", () => new WebAssembly.Global({ value: "f64" }, 1.1).value); +attempt("f64 undefined", () => new WebAssembly.Global({ value: "f64" }, undefined).value); +attempt("f64 null", () => new WebAssembly.Global({ value: "f64" }, null).value); +attempt("i32 string", () => new WebAssembly.Global({ value: "i32" }, "7").value); +attempt("i32 wraps", () => new WebAssembly.Global({ value: "i32" }, 2 ** 32 + 3).value); +attempt("i32 object", () => new WebAssembly.Global({ value: "i32" }, { valueOf: () => 9 }).value); +attempt("i64 bigint", () => new WebAssembly.Global({ value: "i64" }, 2n ** 63n).value); +attempt("i64 string", () => new WebAssembly.Global({ value: "i64" }, "12").value); +attempt("i64 number", () => new WebAssembly.Global({ value: "i64" }, 5).value); +attempt("i64 null", () => new WebAssembly.Global({ value: "i64" }, null).value); +attempt("i32 bigint", () => new WebAssembly.Global({ value: "i32" }, 5n).value); +attempt("externref default", () => new WebAssembly.Global({ value: "externref" }).value); +attempt("externref object", () => new WebAssembly.Global({ value: "externref" }, { a: 1 }).value); +attempt("anyfunc default", () => new WebAssembly.Global({ value: "anyfunc" }).value); +attempt("anyfunc arrow", () => new WebAssembly.Global({ value: "anyfunc" }, () => 1)); +attempt("anyfunc number", () => new WebAssembly.Global({ value: "anyfunc" }, 1)); +attempt("funcref name", () => new WebAssembly.Global({ value: "funcref" })); +attempt("v128", () => new WebAssembly.Global({ value: "v128" })); +attempt("bad type", () => new WebAssembly.Global({ value: "i16" })); +attempt("no value", () => new WebAssembly.Global({})); +attempt("no descriptor", () => new WebAssembly.Global()); +attempt("number descriptor", () => new WebAssembly.Global(1)); +attempt("toString type", () => new WebAssembly.Global({ value: { toString: () => "f64" } }, 2.5).value); +const order = []; +new WebAssembly.Global( + { + get value() { + order.push("value"); + return { toString: () => (order.push("value toString"), "f64") }; + }, + get mutable() { + order.push("mutable"); + return false; + }, + }, + { valueOf: () => (order.push("valueOf"), 1) }, +); +console.log("order", order.join(",")); +const immutable = new WebAssembly.Global({ value: "i32" }, 4); +attempt("immutable set", () => { + immutable.value = 5; +}); +console.log("immutable after", immutable.value, immutable.valueOf()); +const mutable = new WebAssembly.Global({ value: "i32", mutable: 1 }, 4); +mutable.value = 2.9; +console.log("mutable", mutable.value, mutable.valueOf()); +mutable.value = "x"; +console.log("mutable NaN", mutable.value); +const mutable64 = new WebAssembly.Global({ value: "i64", mutable: true }); +mutable64.value = true; +console.log("mutable64", show(mutable64.value)); +attempt("i64 set number", () => { + mutable64.value = 3; +}); +const mutableF32 = new WebAssembly.Global({ value: "f32", mutable: true }, 1); +mutableF32.value = undefined; +console.log("f32 set undefined", mutableF32.value); +const mutableRef = new WebAssembly.Global({ value: "externref", mutable: true }, "start"); +mutableRef.value = undefined; +console.log("externref set undefined", mutableRef.value); +const mutableFunc = new WebAssembly.Global({ value: "anyfunc", mutable: true }); +attempt("anyfunc set arrow", () => { + mutableFunc.value = () => 2; +}); +console.log(String(immutable), Object.prototype.toString.call(immutable), immutable instanceof WebAssembly.Global); +console.log(immutable); +// @ts-ignore: called without new, as Node reports +attempt("Global()", () => WebAssembly.Global({ value: "i32" })); + +// ── WebAssembly.Memory ──────────────────────────────────────────────────── +console.log("Memory"); +attempt("bytes", () => new WebAssembly.Memory({ initial: 1 }).buffer.byteLength); +attempt("zero", () => new WebAssembly.Memory({ initial: 0 }).buffer.byteLength); +attempt("string initial", () => new WebAssembly.Memory({ initial: "2" }).buffer.byteLength); +attempt("fractional", () => new WebAssembly.Memory({ initial: 1.9 }).buffer.byteLength); +attempt("null initial", () => new WebAssembly.Memory({ initial: null }).buffer.byteLength); +attempt("missing", () => new WebAssembly.Memory({})); +attempt("minimum only", () => new WebAssembly.Memory({ minimum: 1 })); +attempt("undefined initial", () => new WebAssembly.Memory({ initial: undefined })); +attempt("negative", () => new WebAssembly.Memory({ initial: -1 })); +attempt("negative fraction", () => new WebAssembly.Memory({ initial: -0.5 })); +attempt("NaN", () => new WebAssembly.Memory({ initial: NaN })); +attempt("Infinity", () => new WebAssembly.Memory({ initial: Infinity })); +attempt("2^32", () => new WebAssembly.Memory({ initial: 2 ** 32 })); +attempt("too many pages", () => new WebAssembly.Memory({ initial: 65537 })); +attempt("max below initial", () => new WebAssembly.Memory({ initial: 20, maximum: 1 })); +attempt("max too large", () => new WebAssembly.Memory({ initial: 1, maximum: 65537 })); +attempt("max 2^32", () => new WebAssembly.Memory({ initial: 1, maximum: 2 ** 32 })); +attempt("max null", () => new WebAssembly.Memory({ initial: 1, maximum: null })); +attempt("bigint initial", () => new WebAssembly.Memory({ initial: 1n })); +attempt("throwing initial", () => + new WebAssembly.Memory({ + initial: { + valueOf() { + throw new Error("boom"); + }, + }, + }), +); +attempt("shared without maximum", () => new WebAssembly.Memory({ initial: 1, shared: true })); +attempt("no descriptor", () => new WebAssembly.Memory()); +attempt("string descriptor", () => new WebAssembly.Memory("x")); +const memoryOrder = []; +new WebAssembly.Memory({ + get initial() { + memoryOrder.push("initial"); + return { valueOf: () => (memoryOrder.push("initial valueOf"), 1) }; + }, + get maximum() { + memoryOrder.push("maximum"); + return { valueOf: () => (memoryOrder.push("maximum valueOf"), 3) }; + }, + get shared() { + memoryOrder.push("shared"); + return false; + }, +}); +console.log("order", memoryOrder.join(",")); +const grown = new WebAssembly.Memory({ initial: 1, maximum: 2 }); +const before = grown.buffer; +new Uint8Array(before)[5] = 7; +console.log("same buffer", grown.buffer === grown.buffer); +attempt("grow", () => grown.grow(1)); +console.log("after grow", grown.buffer.byteLength, new Uint8Array(grown.buffer)[5], before === grown.buffer); +attempt("grow past maximum", () => grown.grow(1)); +attempt("grow negative", () => grown.grow(-1)); +attempt("grow missing", () => grown.grow()); +attempt("grow NaN", () => grown.grow(NaN)); +attempt("grow 2^32", () => grown.grow(2 ** 32)); +attempt("grow zero", () => grown.grow(0)); +attempt("unbounded grow", () => new WebAssembly.Memory({ initial: 1, maximum: undefined }).grow(3)); +console.log(String(grown), grown instanceof WebAssembly.Memory); +console.log(grown); +// @ts-ignore: called without new, as Node reports +attempt("Memory()", () => WebAssembly.Memory({ initial: 1 })); + +// ── WebAssembly.Table ───────────────────────────────────────────────────── +console.log("Table"); +attempt("anyfunc", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }).get(1)); +attempt("externref", () => new WebAssembly.Table({ initial: 1, element: "externref" }).get(0)); +attempt("externref init", () => new WebAssembly.Table({ initial: 2, element: "externref" }, "hi").get(1)); +attempt("externref undefined", () => new WebAssembly.Table({ initial: 2, element: "externref" }, undefined).get(1)); +attempt("anyfunc null", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, null).get(1)); +attempt("anyfunc arrow", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, () => 1)); +attempt("anyfunc number", () => new WebAssembly.Table({ initial: 2, element: "anyfunc" }, 5)); +attempt("funcref name", () => new WebAssembly.Table({ initial: 1, element: "funcref" })); +attempt("i32 element", () => new WebAssembly.Table({ initial: 1, element: "i32" })); +attempt("no element", () => new WebAssembly.Table({ initial: 1 })); +attempt("no initial", () => new WebAssembly.Table({ element: "anyfunc" })); +// @ts-ignore: an incomplete descriptor +attempt("empty", () => new WebAssembly.Table({})); +attempt("no descriptor", () => new WebAssembly.Table()); +attempt("max below initial", () => new WebAssembly.Table({ initial: 2, maximum: 1, element: "anyfunc" })); +attempt("bad init and descriptor", () => new WebAssembly.Table({ initial: 3, maximum: 2, element: "anyfunc" }, 37)); +attempt("too large", () => new WebAssembly.Table({ initial: 10000001, element: "anyfunc" })); +attempt("large maximum", () => new WebAssembly.Table({ initial: 1, maximum: 10000001, element: "anyfunc" }).length); +attempt("max 2^32", () => new WebAssembly.Table({ initial: 1, maximum: 2 ** 32, element: "anyfunc" })); +attempt("toString element", () => new WebAssembly.Table({ element: { toString: () => "anyfunc" }, initial: 1 }).length); +const tableOrder = []; +new WebAssembly.Table({ + get element() { + tableOrder.push("element"); + return { toString: () => (tableOrder.push("element toString"), "anyfunc") }; + }, + get initial() { + tableOrder.push("initial"); + return { valueOf: () => (tableOrder.push("initial valueOf"), 1) }; + }, + get maximum() { + tableOrder.push("maximum"); + return { valueOf: () => (tableOrder.push("maximum valueOf"), 3) }; + }, +}); +console.log("order", tableOrder.join(",")); +const refs = new WebAssembly.Table({ initial: 1, maximum: 4, element: "externref" }, 3); +attempt("set default", () => { + refs.set(0); + return refs.get(0); +}); +attempt("grow default", () => [refs.grow(1), refs.get(1), refs.length]); +attempt("grow value", () => [refs.grow(1, "z"), refs.get(2), refs.length]); +attempt("grow past maximum", () => refs.grow(2)); +attempt("get negative", () => refs.get(-1)); +attempt("get missing", () => refs.get()); +attempt("get out of range", () => refs.get(5)); +attempt("set out of range", () => refs.set(9, 1)); +const funcs = new WebAssembly.Table({ initial: 1, element: "anyfunc" }); +attempt("set arrow", () => funcs.set(0, () => 1)); +attempt("set number", () => funcs.set(0, 7)); +attempt("grow arrow", () => funcs.grow(1, () => 1)); +attempt("grow negative", () => funcs.grow(-1)); +console.log(String(funcs), funcs instanceof WebAssembly.Table); +console.log(funcs); +// @ts-ignore: called without new, as Node reports +attempt("Table()", () => WebAssembly.Table({ initial: 1, element: "anyfunc" })); + +// ── WebAssembly.Module's static functions ──────────────────────────────── +console.log("Module"); +const module = new WebAssembly.Module(jsapi); +console.log(JSON.stringify(WebAssembly.Module.imports(module))); +console.log(JSON.stringify(WebAssembly.Module.exports(module))); +const firstImport = WebAssembly.Module.imports(module)[0]; +console.log(Object.keys(firstImport).join(","), Array.isArray(WebAssembly.Module.imports(module))); +firstImport.module = "changed"; +console.log("fresh", WebAssembly.Module.imports(module)[0].module); +const dylink = WebAssembly.Module.customSections(module, "dylink.0"); +console.log( + "dylink.0", + dylink.length, + dylink.map((b) => `${show(b)} ${[...new Uint8Array(b)].join(" ")}`).join(" | "), +); +console.log("meta", new TextDecoder().decode(WebAssembly.Module.customSections(module, "meta")[0])); +console.log("empty name", WebAssembly.Module.customSections(module, "").length); +console.log("missing", WebAssembly.Module.customSections(module, "nope").length); +console.log("toString", WebAssembly.Module.customSections(module, { toString: () => "meta" }).length); +console.log("null", WebAssembly.Module.customSections(module, null).length); +const copy = WebAssembly.Module.customSections(module, "meta")[0]; +new Uint8Array(copy)[0] = 0; +console.log( + "copies", + new Uint8Array(WebAssembly.Module.customSections(module, "meta")[0])[0], + copy === WebAssembly.Module.customSections(module, "meta")[0], +); +attempt("customSections missing name", () => WebAssembly.Module.customSections(module)); +attempt("customSections symbol", () => WebAssembly.Module.customSections(module, Symbol("x"))); +attempt("customSections number", () => WebAssembly.Module.customSections(1, "x")); +attempt("imports object", () => WebAssembly.Module.imports({})); +attempt("exports missing", () => WebAssembly.Module.exports()); +attempt("imports stray", () => WebAssembly.Module.imports(module, 1).length); +console.log("own", JSON.stringify(WebAssembly.Module.imports(new WebAssembly.Module(own)))); +console.log("own", JSON.stringify(WebAssembly.Module.exports(new WebAssembly.Module(own)))); +// @ts-ignore: called without new, as Node reports +attempt("Module()", () => WebAssembly.Module(jsapi)); +// @ts-ignore: called without new, as Node reports +attempt("Instance()", () => WebAssembly.Instance(module, {})); + +// ── constructed objects as imports ──────────────────────────────────────── +console.log("imports"); +const logged = []; +const memory = new WebAssembly.Memory({ initial: 1, maximum: 4 }); +const table = new WebAssembly.Table({ initial: 2, maximum: 8, element: "anyfunc" }); +const counter = new WebAssembly.Global({ value: "i32", mutable: true }, 10); +const big = new WebAssembly.Global({ value: "i64", mutable: true }, 1n); +const ref = new WebAssembly.Global({ value: "externref", mutable: true }, "first"); +const env = { log: (v) => logged.push(v), memory, table, counter, scale: 2.5, big, ref }; +const instance = new WebAssembly.Instance(module, { env }); +const x = instance.exports; +console.log( + "identity", + x.memory === memory, + x.table === table, + x.counter === counter, + x.alsoCounter === counter, +); +attempt("bump", () => [x.bump(), x.bump(), counter.value]); +counter.value = 100; +attempt("bump after set", () => x.bump()); +attempt("scaled", () => x.scaled(4)); +x.bigAdd(41n); +console.log("big", show(big.value)); +attempt("getRef", () => x.getRef()); +x.setRef({ hello: "world" }); +console.log("ref", JSON.stringify(ref.value)); +ref.value = 42; +attempt("getRef after set", () => x.getRef()); +attempt("apply double", () => x.apply(0, 21)); +attempt("apply square", () => x.apply(1, 12)); +console.log("table get", table.get(0) === x.double); +// A function the module does not export, reached through the table. +const square = table.get(1); +console.log("unexported", square(5), square === table.get(1), square.name, square.length, typeof square); +x.store(3, 99); +console.log("memory", new Uint8Array(memory.buffer)[3], x.load(3)); +new Uint8Array(memory.buffer)[4] = 55; +console.log("memory from js", x.load(4)); +attempt("grow in wasm", () => [x.grow(1), x.size(), memory.buffer.byteLength]); +attempt("grow from js", () => [memory.grow(1), x.size(), memory.buffer.byteLength]); +attempt("table grow", () => [table.grow(2), x.tableSize(), table.length]); +const global = new WebAssembly.Global({ value: "anyfunc", mutable: true }, x.double); +console.log("funcref global", global.value === x.double); +global.value = null; +console.log("funcref null", global.value); +table.set(1, x.double); +attempt("apply after set", () => x.apply(1, 8)); +x.say(5); +console.log("logged", logged.join(",")); + +// Another instance's exports as imports, and the same objects again. +const provider = new WebAssembly.Instance(new WebAssembly.Module(own)).exports; +console.log("own identity", provider.mem === provider.mem2, provider.g === provider.g2, provider.mem instanceof WebAssembly.Memory); +const second = new WebAssembly.Instance(module, { + env: { ...env, memory: provider.mem, table: provider.tab, counter: provider.g }, +}).exports; +console.log("re-exported", second.memory === provider.mem, second.table === provider.tab, second.counter === provider.g); +attempt("bump provider global", () => [second.bump(), provider.g.value]); +attempt("apply provider table", () => second.apply(1, 3)); + +// ── link errors ─────────────────────────────────────────────────────────── +console.log("link errors"); +const link = (label, overrides) => + attempt(label, () => new WebAssembly.Instance(module, { env: { ...env, ...overrides } }) && "linked"); +link("memory too small", { memory: new WebAssembly.Memory({ initial: 0, maximum: 4 }) }); +link("memory without maximum", { memory: new WebAssembly.Memory({ initial: 1 }) }); +link("memory maximum too large", { memory: new WebAssembly.Memory({ initial: 1, maximum: 5 }) }); +link("memory larger initial", { memory: new WebAssembly.Memory({ initial: 3, maximum: 4 }) }); +link("memory number", { memory: 5 }); +link("table too small", { table: new WebAssembly.Table({ initial: 1, maximum: 8, element: "anyfunc" }) }); +link("table without maximum", { table: new WebAssembly.Table({ initial: 2, element: "anyfunc" }) }); +link("table maximum too large", { table: new WebAssembly.Table({ initial: 2, maximum: 12, element: "anyfunc" }) }); +link("table externref", { table: new WebAssembly.Table({ initial: 2, maximum: 8, element: "externref" }) }); +link("table small externref", { table: new WebAssembly.Table({ initial: 1, maximum: 8, element: "externref" }) }); +link("table number", { table: 3 }); +link("global immutable", { counter: new WebAssembly.Global({ value: "i32" }, 1) }); +link("global type", { counter: new WebAssembly.Global({ value: "i64", mutable: true }, 1n) }); +link("global both", { counter: new WebAssembly.Global({ value: "f64" }, 1) }); +link("global number", { counter: 1 }); +link("immutable global object", { scale: new WebAssembly.Global({ value: "f64" }, 4) }); +link("immutable global mutable", { scale: new WebAssembly.Global({ value: "f64", mutable: true }, 4) }); +link("immutable global bigint", { scale: 4n }); +link("i64 number", { big: 4 }); +link("externref anything", { ref: new WebAssembly.Global({ value: "externref", mutable: true }, null) }); diff --git a/tests/corpus/webassembly-lifetimes.mjs b/tests/corpus/webassembly-lifetimes.mjs new file mode 100644 index 000000000..6a7adc860 --- /dev/null +++ b/tests/corpus/webassembly-lifetimes.mjs @@ -0,0 +1,43 @@ +// Lifetimes across the WebAssembly boundary: an externref read from a table +// or global outlives the slot being cleared, a fresh import result reaches +// JavaScript from an export, a load with an explicit memory index reads its +// own offset, and a Memory's buffer outlives the Memory. The module's text +// is in tests/wasm-spec/corpus/lifetimes.wat. +const lifetimes = Buffer.from( + "AGFzbQEAAAABDQNgAW8AYAABb2AAAX8CDAEDZW52BG1ha2UAAQMHBgABAAEBAgQEAW8AAQUDAQABBgYBbwHQbwsHLAYDcHV0AAEEdGFrZQACBHB1dGcAAwV0YWtlZwAEBWZyZXNoAAUEbG9hZAAGCjsGCABBACAAJgALEgEBb0EAJQAhAEEA0G8mACAACwYAIAAkAAsIACMA0G8kAAsEABAACwgAQQAoQgAICwsKAQBBCAsEYWJjZA==", + "base64", +); + +const { instance } = await WebAssembly.instantiate(lifetimes, { + env: { make: () => ({ made: [1, 2, 3] }) }, +}); +const x = instance.exports; + +x.put({ from: "table" }); +console.log(x.take()); +x.putg({ from: "global" }); +console.log(x.takeg()); +console.log(x.fresh()); +console.log(x.fresh().made.length); +console.log(x.load().toString(16)); + +function viewOfDroppedMemory() { + const memory = new WebAssembly.Memory({ initial: 1 }); + const bytes = new Uint8Array(memory.buffer); + bytes[0] = 7; + bytes[65535] = 9; + return bytes; +} +const view = viewOfDroppedMemory(); +const filler = []; +for (let i = 0; i < 32; i++) filler.push(new Uint8Array(65536).fill(1)); +view[1] = 5; +console.log(view.length, view[0], view[1], view[65535], filler.length); + +function bufferOfDroppedMemory() { + const memory = new WebAssembly.Memory({ initial: 1, maximum: 4 }); + new Uint8Array(memory.buffer)[3] = 42; + return memory.buffer; +} +const buffer = bufferOfDroppedMemory(); +console.log(buffer.byteLength, new Uint8Array(buffer)[3]); diff --git a/tests/corpus/webassembly-spec-control/data.mjs b/tests/corpus/webassembly-spec-control/data.mjs new file mode 100644 index 000000000..0be8faedb --- /dev/null +++ b/tests/corpus/webassembly-spec-control/data.mjs @@ -0,0 +1,48 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * block.wast, br.wast, br_if.wast, br_table.wast, call.wast, call_indirect.wast, fac.wast, forward.wast, func.wast, func_ptrs.wast, if.wast, labels.wast, loop.wast, local_get.wast, local_set.wast, local_tee.wast, nop.wast, return.wast, select.wast, stack.wast, switch.wast, unreachable.wast, unwind.wast, left-to-right.wast, global.wast, start.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABPQtgAn9/AX9gAABgAAF/YAF/AGADf3x/A398f2AAA39+f2ABfwF/YAACf39gAAJ9fWAAA39/fmACf38Cf38DODcBAQICAgICAgICAgIBAgICAgICAAICAgEBAgYCAgECAgICAgICAgICAgICAgkCAgICAgICAgIBBAUBcAEBAQUDAQABBgYBfwFBCgsH0AY0BWVtcHR5AAEIc2luZ3VsYXIAAgVtdWx0aQADBm5lc3RlZAAEBGRlZXAABQ9hcy1zZWxlY3QtZmlyc3QABg1hcy1zZWxlY3QtbWlkAAcOYXMtc2VsZWN0LWxhc3QACA1hcy1sb29wLWZpcnN0AAkLYXMtbG9vcC1taWQACgxhcy1sb29wLWxhc3QACw9hcy1pZi1jb25kaXRpb24ADAphcy1pZi10aGVuAA0KYXMtaWYtZWxzZQAODmFzLWJyX2lmLWZpcnN0AA8NYXMtYnJfaWYtbGFzdAAQEWFzLWJyX3RhYmxlLWZpcnN0ABEQYXMtYnJfdGFibGUtbGFzdAASFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAFBRhcy1jYWxsX2luZGlyZWN0LW1pZAAVFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAWDmFzLXN0b3JlLWZpcnN0ABcNYXMtc3RvcmUtbGFzdAAYFGFzLW1lbW9yeS5ncm93LXZhbHVlABkNYXMtY2FsbC12YWx1ZQAbD2FzLXJldHVybi12YWx1ZQAcD2FzLWRyb3Atb3BlcmFuZAAdC2FzLWJyLXZhbHVlAB4SYXMtbG9jYWwuc2V0LXZhbHVlAB8SYXMtbG9jYWwudGVlLXZhbHVlACATYXMtZ2xvYmFsLnNldC12YWx1ZQAhD2FzLWxvYWQtb3BlcmFuZAAiEGFzLXVuYXJ5LW9wZXJhbmQAIxFhcy1iaW5hcnktb3BlcmFuZAAkD2FzLXRlc3Qtb3BlcmFuZAAlEmFzLWNvbXBhcmUtb3BlcmFuZAAmEmFzLWJpbmFyeS1vcGVyYW5kcwAnE2FzLWNvbXBhcmUtb3BlcmFuZHMAKBFhcy1taXhlZC1vcGVyYW5kcwApCmJyZWFrLWJhcmUAKgticmVhay12YWx1ZQArEWJyZWFrLW11bHRpLXZhbHVlACwOYnJlYWstcmVwZWF0ZWQALQticmVhay1pbm5lcgAuBXBhcmFtAC8GcGFyYW1zADAJcGFyYW1zLWlkADELcGFyYW0tYnJlYWsAMgxwYXJhbXMtYnJlYWsAMw9wYXJhbXMtaWQtYnJlYWsANAdlZmZlY3RzADUIdHlwZS11c2UANgkHAQBBAAsBEwrSCDcCAAsIAAJACwJACwsLAAJAAQsCf0EHCws+AAJAEAAQABAAEAALAn8QABAAEABBBxAACxoCBRAAEAAQAEEIEAAQABAAEABCBxAAEAAQABAAQQkQAAsaGgsVAAJ/AkAQAAJACwELAn8QAEEJCwsLeQACfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/An8CfwJ/EABBlgELCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsMAAJ/QQELQQJBAxsLDABBAgJ/QQELQQMbCwwAQQJBAwJ/QQELGwsOAAN/An9BAQsQABAACwsOAAN/EAACf0EBCxAACwsOAAN/EAAQAAJ/QQELCwsMAAJ/QQELBEAQAAsLDwBBAQR/An9BAQsFQQILCw8AQQEEf0ECBQJ/QQELCwsOAAJ/An9BAQtBAg0ACwsOAAJ/QQICf0EBCw0ACwsQAAJ/An9BAQtBAg4BAAALCxAAAn9BAgJ/QQELDgEAAAsLBAAgAAsRAAJ/An9BAQtBAkEAEQAACwsRAAJ/QQICf0EBC0EAEQAACwsRAAJ/QQFBAgJ/QQALEQAACwsMAAJ/QQELQQE2AgALDABBCgJ/QQELNgIACwkAAn9BAQtAAAsEACAACwkAAn9BAQsQGgsIAAJ/QQELDwsIAAJ/QQELGgsMAAJ/An9BAQsMAAsLDQEBfwJ/QQELIQAgAAsLAQF/An9BAQsiAAsLAAJ/QQELJAAjAAsKAAJ/QQELKAIACwoAAn8QAEENC2gLEQACfxAAQQMLAn8QAEEEC2wLCgACfxAAQQ0LRQsXAAJ9EABDAABAQAsCfRAAQwAAQEALXgsOAAIHEABBAxAAQQQLbAsUAAIIEABDAABAQBAAQwAAQEALXgsRAAIHEABBAxAAQQQLQQVqbAsmAAJADAAACwJAQQENAAALAkBBAA4AAAALAkBBAQ4CAAAAAAtBEwsLAAJ/QRIMAEETCwsTAAIJQRJBbkISDABBE0FtQhMLCzEAAn9BEgwAQRMMAEEUQQANABpBFEEBDQAaQRUMAEEWQQQOAABBF0EBDgIAAABBFQsLRQEBf0EAIQAgAAJ/An9BAQwBCwtqIQAgAAJ/AkAMAAtBAgtqIQAgAAJ/QQQMAGgLaiEAIAACfwJ/QQgMAQtoC2ohACAACwoAQQECBkECagsLCgBBAUECAgBqCwsKAEEBQQICCgtqCwwAQQECBkECagwACwsMAEEBQQICAGoMAAsLDABBAUECAgoMAAtqCy8BAX8CQEEBIQAgAEEDbCEAIABBBWshACAAQQdsIQAMACAAQeQAbCEACyAAQXJGCz0AAkALAn9BAAsCAxoLQQBEAAAAAAAAAABBAAIECxoaGgJ/QQALAgMaC0EARAAAAAAAAAAAQQACBAsaGhoL", + "AGFzbQEAAAABKwlgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGAAAnx8YAACf35gAn9/AX8DS0oBAQEBAQEBAQECAwQFBgEBAQICAgICAQICAQICAwcCCAgICAICAAICAgICAgICAgICAgQDAgICAgICBAIDAgICAgICAgICAgICAgQFAXABAQEFAwEAAQYGAX8BQQoLB5IKSAh0eXBlLWkzMgABCHR5cGUtaTY0AAIIdHlwZS1mMzIAAwh0eXBlLWY2NAAEDHR5cGUtaTMyLWkzMgAFDHR5cGUtaTY0LWk2NAAGDHR5cGUtZjMyLWYzMgAHDHR5cGUtZjY0LWY2NAAIDnR5cGUtaTMyLXZhbHVlAAkOdHlwZS1pNjQtdmFsdWUACg50eXBlLWYzMi12YWx1ZQALDnR5cGUtZjY0LXZhbHVlAAwSdHlwZS1mNjQtZjY0LXZhbHVlAA0OYXMtYmxvY2stZmlyc3QADgxhcy1ibG9jay1taWQADw1hcy1ibG9jay1sYXN0ABAOYXMtYmxvY2stdmFsdWUAEQ1hcy1sb29wLWZpcnN0ABILYXMtbG9vcC1taWQAEwxhcy1sb29wLWxhc3QAFAthcy1ici12YWx1ZQAVDWFzLWJyX2lmLWNvbmQAFg5hcy1icl9pZi12YWx1ZQAXE2FzLWJyX2lmLXZhbHVlLWNvbmQAGBFhcy1icl90YWJsZS1pbmRleAAZEWFzLWJyX3RhYmxlLXZhbHVlABoXYXMtYnJfdGFibGUtdmFsdWUtaW5kZXgAGw9hcy1yZXR1cm4tdmFsdWUAHBBhcy1yZXR1cm4tdmFsdWVzAB0KYXMtaWYtY29uZAAeCmFzLWlmLXRoZW4AHwphcy1pZi1lbHNlACAPYXMtc2VsZWN0LWZpcnN0ACEQYXMtc2VsZWN0LXNlY29uZAAiDmFzLXNlbGVjdC1jb25kACMNYXMtc2VsZWN0LWFsbAAkDWFzLWNhbGwtZmlyc3QAJgthcy1jYWxsLW1pZAAnDGFzLWNhbGwtbGFzdAAoC2FzLWNhbGwtYWxsACkVYXMtY2FsbF9pbmRpcmVjdC1mdW5jACoWYXMtY2FsbF9pbmRpcmVjdC1maXJzdAArFGFzLWNhbGxfaW5kaXJlY3QtbWlkACwVYXMtY2FsbF9pbmRpcmVjdC1sYXN0AC0UYXMtY2FsbF9pbmRpcmVjdC1hbGwALhJhcy1sb2NhbC5zZXQtdmFsdWUALxJhcy1sb2NhbC50ZWUtdmFsdWUAMBNhcy1nbG9iYWwuc2V0LXZhbHVlADEPYXMtbG9hZC1hZGRyZXNzADIQYXMtbG9hZE4tYWRkcmVzcwAzEGFzLXN0b3JlLWFkZHJlc3MANA5hcy1zdG9yZS12YWx1ZQA1DWFzLXN0b3JlLWJvdGgANhFhcy1zdG9yZU4tYWRkcmVzcwA3D2FzLXN0b3JlTi12YWx1ZQA4DmFzLXN0b3JlTi1ib3RoADkQYXMtdW5hcnktb3BlcmFuZAA6DmFzLWJpbmFyeS1sZWZ0ADsPYXMtYmluYXJ5LXJpZ2h0ADwOYXMtYmluYXJ5LWJvdGgAPQ9hcy10ZXN0LW9wZXJhbmQAPg9hcy1jb21wYXJlLWxlZnQAPxBhcy1jb21wYXJlLXJpZ2h0AEAPYXMtY29tcGFyZS1ib3RoAEESYXMtY29udmVydC1vcGVyYW5kAEITYXMtbWVtb3J5Lmdyb3ctc2l6ZQBDEm5lc3RlZC1ibG9jay12YWx1ZQBED25lc3RlZC1ici12YWx1ZQBFEm5lc3RlZC1icl9pZi12YWx1ZQBGF25lc3RlZC1icl9pZi12YWx1ZS1jb25kAEcVbmVzdGVkLWJyX3RhYmxlLXZhbHVlAEgbbmVzdGVkLWJyX3RhYmxlLXZhbHVlLWluZGV4AEkJBwEAQQALASUK5QhKAgALCQACQAwAaBoLCwkAAkAMAHoaCwsJAAJADACMGgsLCQACQAwAmhoLCwkAAkAMAGoaCwsJAAJADAB8GgsLCQACQAwAkhoLCwkAAkAMAKAaCwsKAAJ/QQEMAGgLCwoAAn5CAgwAegsLDQACfUMAAEBADACMCwsRAAJ8RAAAAAAAABBADACaCwsjAAIGRAAAAAAAABBARAAAAAAAABRADACgRAAAAAAAABhACwsJAAJADAAQAAsLCwACQBAADAAQAAsLCgACQAEQAAwACwsMAAJ/ARAAQQIMAAsLDgACfwN/QQMMAUECCwsLEAACfwN/EABBBAwBQQILCwsPAAJ/A38BEABBBQwBCwsLCwACf0EJDAAMAAsLCQACQAwADQALCxAAAn9BCAwAQQENABpBBwsLEAACf0EGQQkMAA0AGkEHCwsMAAJADAAOAgAAAAsLEgACf0EKDABBAQ4CAAAAQQcLCxEAAn9BBkELDAAOAQAAQQcLCwoAAn5CBwwADwsLDgBBAgJ+QQFCBwwADwsLEQACf0ECDAAEf0EABUEBCwsLEQACfyAABH9BAwwBBSABCwsLEQACfyAABH8gAQVBBAwBCwsLDgACf0EFDAAgACABGwsLDgACfyAAQQYMACABGwsLDgACf0EAQQFBBwwAGwsLCgACf0EIDAAbCwsEAEF/Cw8AAn9BDAwAQQJBAxAlCwsPAAJ/QQFBDQwAQQMQJQsLDwACf0EBQQJBDgwAECULCwsAAn9BDwwAECULCxIAAn9BFAwAQQFBAkEDEQAACwsSAAJ/QQBBFQwAQQJBAxEAAAsLEgACf0EAQQFBFgwAQQMRAAALCxIAAn9BAEEBQQJBFwwAEQAACwsMAAJ/QRgMABEAAAsLDwEBfQJ/QREMACEAQX8LCw0BAX8Cf0EBDAAiAAsLCwACf0EBDAAkAAsLDwACfUOamdk/DAAqAgALCwwAAn5CHgwAMAAACwsXAAJ/QR4MAEQAAAAAAAAcQDkDAEF/CwsQAAJ/QQJBHwwANwMAQX8LCw4AAn9BIAwANwMAQX8LCxAAAn9BIAwAQQc6AABBfwsLEAACf0ECQSEMAD0BAEF/CwsOAAJ/QSIMAD0BAEF/CwsNAAJ9Q5qZWUAMAIwLCwwAAn9BAwwAQQpqCwsMAAJ+QgpCLQwAfQsLCgACf0EuDABqCwsKAAJ/QSwMAEULCxMAAn9BKwwARAAAAAAAACRAZQsLDwACf0MAACBBQSoMAFwLCwoAAn9BLAwAZQsLCgACf0EpDACnCwsLAAJ/QSgMAEAACwsRAEEBAn8QAEEEQQgMAGoLagsaAEEBAn9BAhoCf0EEGkEIDAEMAAsaQRALagsfAEEBAn9BAhoCf0EEGkEIDAFBAQ0AGkEgCxpBEAtqCxYAQQECf0ECGkEEQQgMAA0AGkEQC2oLHQBBAQJ/QQIaAn9BBBpBCAwBQQEOAAALGkEQC2oLFgBBAQJ/QQIaQQRBCAwADgAAQRALags=", + "AGFzbQEAAAABKQlgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGABfwF/YAF/AGACf38AA0A/AQEBAQECAwQFBgYHBgYGBgYHAgECBgECAgMGCAgGBgIAAgICAAICAgIGBgYCAgICAgIFAgICAgICBgYGBgYGBAUBcAEBAQUDAQABBgYBfwFBCgsH0Qg8CHR5cGUtaTMyAAEIdHlwZS1pNjQAAgh0eXBlLWYzMgADCHR5cGUtZjY0AAQOdHlwZS1pMzItdmFsdWUABQ50eXBlLWk2NC12YWx1ZQAGDnR5cGUtZjMyLXZhbHVlAAcOdHlwZS1mNjQtdmFsdWUACA5hcy1ibG9jay1maXJzdAAJDGFzLWJsb2NrLW1pZAAKDWFzLWJsb2NrLWxhc3QACxRhcy1ibG9jay1maXJzdC12YWx1ZQAMEmFzLWJsb2NrLW1pZC12YWx1ZQANE2FzLWJsb2NrLWxhc3QtdmFsdWUADg1hcy1sb29wLWZpcnN0AA8LYXMtbG9vcC1taWQAEAxhcy1sb29wLWxhc3QAEQthcy1ici12YWx1ZQASDWFzLWJyX2lmLWNvbmQAEw5hcy1icl9pZi12YWx1ZQAUE2FzLWJyX2lmLXZhbHVlLWNvbmQAFRFhcy1icl90YWJsZS1pbmRleAAWEWFzLWJyX3RhYmxlLXZhbHVlABcXYXMtYnJfdGFibGUtdmFsdWUtaW5kZXgAGA9hcy1yZXR1cm4tdmFsdWUAGQphcy1pZi1jb25kABoKYXMtaWYtdGhlbgAbCmFzLWlmLWVsc2UAHA9hcy1zZWxlY3QtZmlyc3QAHRBhcy1zZWxlY3Qtc2Vjb25kAB4OYXMtc2VsZWN0LWNvbmQAHw1hcy1jYWxsLWZpcnN0ACELYXMtY2FsbC1taWQAIgxhcy1jYWxsLWxhc3QAIxVhcy1jYWxsX2luZGlyZWN0LWZ1bmMAJRZhcy1jYWxsX2luZGlyZWN0LWZpcnN0ACYUYXMtY2FsbF9pbmRpcmVjdC1taWQAJxVhcy1jYWxsX2luZGlyZWN0LWxhc3QAKBJhcy1sb2NhbC5zZXQtdmFsdWUAKRJhcy1sb2NhbC50ZWUtdmFsdWUAKhNhcy1nbG9iYWwuc2V0LXZhbHVlACsPYXMtbG9hZC1hZGRyZXNzACwQYXMtbG9hZE4tYWRkcmVzcwAtEGFzLXN0b3JlLWFkZHJlc3MALg5hcy1zdG9yZS12YWx1ZQAvEWFzLXN0b3JlTi1hZGRyZXNzADAPYXMtc3RvcmVOLXZhbHVlADEQYXMtdW5hcnktb3BlcmFuZAAyDmFzLWJpbmFyeS1sZWZ0ADMPYXMtYmluYXJ5LXJpZ2h0ADQPYXMtdGVzdC1vcGVyYW5kADUPYXMtY29tcGFyZS1sZWZ0ADYQYXMtY29tcGFyZS1yaWdodAA3E2FzLW1lbW9yeS5ncm93LXNpemUAOBJuZXN0ZWQtYmxvY2stdmFsdWUAOQ9uZXN0ZWQtYnItdmFsdWUAOhJuZXN0ZWQtYnJfaWYtdmFsdWUAOxduZXN0ZWQtYnJfaWYtdmFsdWUtY29uZAA8FW5lc3RlZC1icl90YWJsZS12YWx1ZQA9G25lc3RlZC1icl90YWJsZS12YWx1ZS1pbmRleAA+CQcBAEEACwEkCtoIPwIACw0AAkBBAEEBDQBoGgsLDQACQEIAQQENAHoaCwsQAAJAQwAAAABBAQ0AjBoLCxQAAkBEAAAAAAAAAABBAQ0AmhoLCwwAAn9BAUEBDQBoCwsMAAJ+QgJBAQ0AegsLDwACfUMAAEBAQQENAIwLCxMAAnxEAAAAAAAAEEBBAQ0AmgsLDgACQCAADQBBAg8LQQMLEAACQBAAIAANAEECDwtBAwsNAAJAEAAQACAADQALCw8AAn9BCiAADQAaQQsPCwsRAAJ/EABBFCAADQAaQRUPCwsPAAJ/EAAQAEELIAANAAsLEQACQANAIAANAUECDwsLQQMLEwACQANAEAAgAA0BQQIPCwtBBAsLAANAEAAgAA0BCwsNAAJ/QQFBAg0ADAALCw0AAkBBAUEBDQANAAsLEgACf0EBQQINAEEDDQAaQQQLCxIAAn9BAkEBIAANAA0AGkEECwsQAAJAQQFBAg0ADgIAAAALCxQAAn9BAUECDQBBAw4CAAAAQQQLCxMAAn9BAkEBQQMNAA4BAABBBAsLDAACfkIBQQINAA8LCxMAAn9BASAADQAEf0ECBUEDCwsLEQACQCAABEAgAQ0BBRAACwsLEQACQCAABEAQAAUgAQ0BCwsLEAACf0EDQQoNAEECIAAbCwsQAAJ/QQFBA0EKDQAgABsLCxAAAn9BAUECQQNBCg0AGwsLBABBfwsRAAJ/QQxBAQ0AQQJBAxAgCwsRAAJ/QQFBDUEBDQBBAxAgCwsRAAJ/QQFBAkEOQQENABAgCwsEACAACxQAAn9BBEEKDQBBAUECQQARAAALCxQAAn9BAUEEQQoNAEECQQARAAALCxQAAn9BAUECQQRBCg0AQQARAAALCxQAAn9BAUECQQNBBEEKDQARAAALCxEBAX8Cf0ERIAANACEAQX8LCxAAAn9BASAADQAiAEF/DwsLEAACf0EBIAANACQAQX8PCwsOAAJ/QQFBAQ0AKAIACwsOAAJ/QR5BAQ0ALAAACwsSAAJ/QR5BAQ0AQQc2AgBBfwsLEgACf0ECQR9BAQ0ANgIAQX8LCxIAAn9BIEEBDQBBBzoAAEF/CwsSAAJ/QQJBIUEBDQA7AQBBfwsLEwACfEQAAAAAAADwP0EBDQCaCwsOAAJ/QQFBAQ0AQQpqCwsOAAJ/QQpBAUEBDQBrCwsMAAJ/QQBBAQ0ARQsLDgACf0EBQQENAEEKTQsLDgACf0EKQQFBKg0ARwsLDQACf0EBQQENAEAACwsaAEEBAn9BAhpBBAJ/QQggAA0BGkEQC2oLagsbAEEBAn9BAhoCf0EIIAANARpBBAsMAEEQC2oLHgBBAQJ/QQIaAn9BCCAADQEaQQQLQQENABpBEAtqCx4AQQECf0ECGkEEAn9BCCAADQEaQQELDQAaQRALagseAEEBAn9BAhoCf0EIIAANARpBBAtBAQ4AAEEQC2oLHgBBAQJ/QQIaQQQCf0EIIAANARpBAQsOAABBEAtqCw==", + "AGFzbQEAAAABLAlgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGABfwF/YAJ/fwF/YAJ/bwFvA0dGAQEBAQECAwQFBgYGBgYGBgEBAQICAgICAQICAQICAwIHBwcHAgACAgICAgICAgICBAMCAgICBAIDAgICAgIGBgYGBgYGCAQFAXABAQEFAwEAAQYGAX8BQQoLB7sJRAh0eXBlLWkzMgABCHR5cGUtaTY0AAIIdHlwZS1mMzIAAwh0eXBlLWY2NAAEDnR5cGUtaTMyLXZhbHVlAAUOdHlwZS1pNjQtdmFsdWUABg50eXBlLWYzMi12YWx1ZQAHDnR5cGUtZjY0LXZhbHVlAAgFZW1wdHkACQtlbXB0eS12YWx1ZQAKCXNpbmdsZXRvbgALD3NpbmdsZXRvbi12YWx1ZQAMCG11bHRpcGxlAA0ObXVsdGlwbGUtdmFsdWUADgVsYXJnZQAPDmFzLWJsb2NrLWZpcnN0ABAMYXMtYmxvY2stbWlkABENYXMtYmxvY2stbGFzdAASDmFzLWJsb2NrLXZhbHVlABMNYXMtbG9vcC1maXJzdAAUC2FzLWxvb3AtbWlkABUMYXMtbG9vcC1sYXN0ABYLYXMtYnItdmFsdWUAFw1hcy1icl9pZi1jb25kABgOYXMtYnJfaWYtdmFsdWUAGRNhcy1icl9pZi12YWx1ZS1jb25kABoRYXMtYnJfdGFibGUtaW5kZXgAGxFhcy1icl90YWJsZS12YWx1ZQAcF2FzLWJyX3RhYmxlLXZhbHVlLWluZGV4AB0PYXMtcmV0dXJuLXZhbHVlAB4KYXMtaWYtY29uZAAfCmFzLWlmLXRoZW4AIAphcy1pZi1lbHNlACEPYXMtc2VsZWN0LWZpcnN0ACIQYXMtc2VsZWN0LXNlY29uZAAjDmFzLXNlbGVjdC1jb25kACQNYXMtY2FsbC1maXJzdAAmC2FzLWNhbGwtbWlkACcMYXMtY2FsbC1sYXN0ACgWYXMtY2FsbF9pbmRpcmVjdC1maXJzdAApFGFzLWNhbGxfaW5kaXJlY3QtbWlkACoVYXMtY2FsbF9pbmRpcmVjdC1sYXN0ACsVYXMtY2FsbF9pbmRpcmVjdC1mdW5jACwSYXMtbG9jYWwuc2V0LXZhbHVlAC0SYXMtbG9jYWwudGVlLXZhbHVlAC4TYXMtZ2xvYmFsLnNldC12YWx1ZQAvD2FzLWxvYWQtYWRkcmVzcwAwEGFzLWxvYWROLWFkZHJlc3MAMRBhcy1zdG9yZS1hZGRyZXNzADIOYXMtc3RvcmUtdmFsdWUAMxFhcy1zdG9yZU4tYWRkcmVzcwA0D2FzLXN0b3JlTi12YWx1ZQA1EGFzLXVuYXJ5LW9wZXJhbmQANg5hcy1iaW5hcnktbGVmdAA3D2FzLWJpbmFyeS1yaWdodAA4D2FzLXRlc3Qtb3BlcmFuZAA5D2FzLWNvbXBhcmUtbGVmdAA6EGFzLWNvbXBhcmUtcmlnaHQAOxJhcy1jb252ZXJ0LW9wZXJhbmQAPBNhcy1tZW1vcnkuZ3Jvdy1zaXplAD0SbmVzdGVkLWJsb2NrLXZhbHVlAD4PbmVzdGVkLWJyLXZhbHVlAD8SbmVzdGVkLWJyX2lmLXZhbHVlAEAXbmVzdGVkLWJyX2lmLXZhbHVlLWNvbmQAQRVuZXN0ZWQtYnJfdGFibGUtdmFsdWUAQhtuZXN0ZWQtYnJfdGFibGUtdmFsdWUtaW5kZXgAQxpuZXN0ZWQtYnJfdGFibGUtbG9vcC1ibG9jawBEDm1lZXQtZXh0ZXJucmVmAEUJBwEAQQALASUK04kBRgIACw0AAkBBAA4BAABoGgsLDQACQEEADgEAAHoaCwsNAAJAQQAOAQAAjBoLCw0AAkBBAA4BAACaGgsLDgACf0EBQQAOAQAAaAsLDgACfkICQQAOAQAAegsLEQACfUMAAEBAQQAOAQAAjAsLFQACfEQAAAAAAAAQQEEADgEAAJoLCw8AAkAgAA4AAEEVDwtBFgsOAAJ/QSEgAA4AAEEfCwsWAAJAAkAgAA4BAQBBFQ8LQRQPC0EWCxYAAn8Cf0EhIAAOAQABQR8PCxpBIAsLMQACQAJAAkACQAJAIAAOBAMCAQAEQeMADwtB5AAPC0HlAA8LQeYADwtB5wAPC0HoAAtNAQF/An8CfwJ/An8Cf0HIASAADgQDAgEABCABQeMAag8LIQEgAUEKag8LIQEgAUELag8LIQEgAUEMag8LIQEgAUENag8LIQEgAUEOagusfgACQAJAIAAOlX4AAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABQX8PC0EADwtBAQ8LDgACQEEADgIAAAAQAAsLEAACQBAAQQAOAgAAABAACwsPAAJAARAAQQAOAgAAAAsLEQACfwEQAEECQQAOAgAAAAsLDwADf0EDQQAOAQEBQQELCxIAA38QAEEEQX8OAgEBAUECCwsRAAN/ARAAQQVBAQ4CAQEBCwsOAAJ/QQlBAA4AAAwACwsOAAJAQQEOAgAAAA0ACwsTAAJ/QQhBAA4AAEEBDQAaQQcLCxQAAn9BBkEJQQAOAQAADQAaQQcLCw8AAkBBAQ4AAA4CAAAACwsVAAJ/QQpBAA4AAEEBDgIAAABBBwsLFAACf0EGQQtBAQ4AAA4BAABBBwsLDQACfkIHQQAOAAAPCwsUAAJ/QQJBAA4AAAR/QQAFQQELCwsUAAJ/IAAEf0EDQQAOAAEFIAELCwsVAAJ/IAAEfyABBUEEQQAOAQEACwsLEQACf0EFQQAOAAAgACABGwsLEQACfyAAQQZBAQ4AACABGwsLEQACf0EAQQFBB0EBDgAAGwsLBABBfwsSAAJ/QQxBAQ4AAEECQQMQJQsLEgACf0EBQQ1BAQ4AAEEDECULCxIAAn9BAUECQQ5BAQ4AABAlCwsVAAJ/QRRBAQ4AAEEBQQJBAxEAAAsLFQACf0EAQRVBAQ4AAEECQQMRAAALCxUAAn9BAEEBQRZBAQ4AAEEDEQAACwsVAAJ/QQBBAUECQRdBAQ4AABEAAAsLEgEBfQJ/QRFBAQ4AACEAQX8LCxIBAX8Cf0EBQQEOAAAhAEF/CwsQAAJ/QQFBAQ4AACQAQX8LCxIAAn1DmpnZP0EBDgAAKgIACwsPAAJ+Qh5BAQ4AADAAAAsLGgACf0EeQQEOAABEAAAAAAAAHEA5AwBBfwsLEwACf0ECQR9BAQ4AADcDAEF/CwsTAAJ/QSBBAA4AAEEHOgAAQX8LCxMAAn9BAkEhQQAOAAA9AQBBfwsLEAACfUOamVlAQQAOAACMCwsQAAJ/QQNBAA4BAABBCmoLCw8AAn5CCkItQQAOAAB9CwsNAAJ/QSxBAA4AAEULCxcAAn9BK0EADgEAAEQAAAAAAAAkQGULCxIAAn9DAAAgQUEqQQAOAABcCwsNAAJ/QSlBAA4AAKcLCw4AAn9BKEEADgAAQAALCyMAAn9BfxpBAQJ/QQICf0EEGkEIQRAgAA4CAAECagtqC2oLCyIAAn9BAQJ/QQIaAn9BBBpBCCAADgICAQAMAAsaQRALagsLJwACf0EBAn9BAhoCf0EEGkEIIAAOAgABAkEBDQAaQSALGkEQC2oLCx4AAn9BAQJ/QQIaQQRBCCAADgIAAQANABpBEAtqCwsnAAJ/QQECf0ECGgJ/QQQaQQggAA4CAAECQQEOAABBIAsaQRALagsLHgACf0EBAn9BAhpBBEEIIAAOAgABAA4AAEEQC2oLCyIAA38CQCAADgIBAAALQQALIQADfwJAIAAOAgABAQtBAwsLEQACbwJvIAEgAA4CAQABCwsL", + "AGFzbQEAAAAB6QEZYAJ/fwF/YAABf2AAAX5gAAF9YAABfGAAAn9+YAF/AX9gAX4BfmABfQF9YAF8AXxgAn98An98YAJ/fwJ/f2ACfXwCfH1gAnx/An98YAJ9fwF/YAJ/fgF+YAJ8fQF9YAJ+fAF8YAACf3xgAAJ/f2AAAnx9YAJ+fgF+YAF+AX9gAABgZH1/f3x9fX18fX9/fXx+fn9+fn1+fn5/fX19fH1/fn18fH1/fX18fnx/fn18f39/fnx/fn58fHx8fHx/fXx8f359fX1/fHx8fHx9fn5/f399fH9+fX19f399fH59fH19fX99fn8BfwNPTgECAwQFBgcICQoLDA0ODxARAQIDBAUBAgMEAQIDBBITFBIBARMHFQcWFhcXFwEBAQEBAQEBAAEBARcXAQEXAQEBAQEGCAMBAQEBAQIYBgQFAXABAQEFAwEAAQYGAX8BQQoLB8oHOAh0eXBlLWkzMgARCHR5cGUtaTY0ABIIdHlwZS1mMzIAEwh0eXBlLWY2NAAUDHR5cGUtaTMyLWk2NAAVDnR5cGUtZmlyc3QtaTMyABYOdHlwZS1maXJzdC1pNjQAFw50eXBlLWZpcnN0LWYzMgAYDnR5cGUtZmlyc3QtZjY0ABkPdHlwZS1zZWNvbmQtaTMyABoPdHlwZS1zZWNvbmQtaTY0ABsPdHlwZS1zZWNvbmQtZjMyABwPdHlwZS1zZWNvbmQtZjY0AB0QdHlwZS1hbGwtaTMyLWY2NAAeEHR5cGUtYWxsLWkzMi1pMzIAHxB0eXBlLWFsbC1mMzItZjY0ACAQdHlwZS1hbGwtZjY0LWkzMgAhFmFzLWJpbmFyeS1hbGwtb3BlcmFuZHMAIhFhcy1taXhlZC1vcGVyYW5kcwAjFGFzLWNhbGwtYWxsLW9wZXJhbmRzACQDZmFjACUHZmFjLWFjYwAmA2ZpYgAnBGV2ZW4AKANvZGQAKQdydW5hd2F5ACoObXV0dWFsLXJ1bmF3YXkAKw9hcy1zZWxlY3QtZmlyc3QALQ1hcy1zZWxlY3QtbWlkAC4OYXMtc2VsZWN0LWxhc3QALw9hcy1pZi1jb25kaXRpb24AMA5hcy1icl9pZi1maXJzdAAxDWFzLWJyX2lmLWxhc3QAMhFhcy1icl90YWJsZS1maXJzdAAzEGFzLWJyX3RhYmxlLWxhc3QANBZhcy1jYWxsX2luZGlyZWN0LWZpcnN0ADYUYXMtY2FsbF9pbmRpcmVjdC1taWQANxVhcy1jYWxsX2luZGlyZWN0LWxhc3QAOA5hcy1zdG9yZS1maXJzdAA5DWFzLXN0b3JlLWxhc3QAOhRhcy1tZW1vcnkuZ3Jvdy12YWx1ZQA7D2FzLXJldHVybi12YWx1ZQA8D2FzLWRyb3Atb3BlcmFuZAA9C2FzLWJyLXZhbHVlAD4SYXMtbG9jYWwuc2V0LXZhbHVlAD8SYXMtbG9jYWwudGVlLXZhbHVlAEATYXMtZ2xvYmFsLnNldC12YWx1ZQBBD2FzLWxvYWQtb3BlcmFuZABCEGFzLXVuYXJ5LW9wZXJhbmQARQ5hcy1iaW5hcnktbGVmdABGD2FzLWJpbmFyeS1yaWdodABHD2FzLXRlc3Qtb3BlcmFuZABID2FzLWNvbXBhcmUtbGVmdABJEGFzLWNvbXBhcmUtcmlnaHQAShJhcy1jb252ZXJ0LW9wZXJhbmQASx5yZXR1cm4tZnJvbS1sb25nLWFyZ3VtZW50LWxpc3QATQkHAQBBAAsBNQr2CU4FAEGyAgsFAELkAgsHAEMAIHNFCwsARAAAAAAAyK5ACwgAQbICQuQCCwQAIAALBAAgAAsEACAACwQAIAALBgAgACABCwYAIAEgAAsGACABIAALBgAgASAACwQAIAELBAAgAQsEACABCwQAIAELBAAQAAsEABABCwQAEAILBAAQAwsEABAECwYAQSAQBQsHAELAABAGCwkAQ8P1qD8QBwsNAEQ9CtejcD36PxAICwsAQ2ZmAEJBIBANCwkAQSBCwAAQDgsSAEQAAAAAAABQQEMAAABCEA8LEABCwABEZmZmZmYGUEAQEAsPAEEgRD0K16NwPfo/EAkLCABBAUECEAoLEgBDAACAP0QAAAAAAAAAQBALCw8ARAAAAAAAAPA/QQIQDAsJAEEDQQQQCmoLDABBA0EEEApBBWpsCwoAQQNBBBAKEAoLFQAgAFAEfkIBBSAAIABCAX0QJX4LCxcAIABQBH4gAQUgAEIBfSAAIAF+ECYLCxwAIABCAVgEfkIBBSAAQgJ9ECcgAEIBfRAnfAsLEgAgAFAEf0EsBSAAQgF9ECkLCxMAIABQBH9B4wAFIABCAX0QKAsLBAAQKgsEABAsCwQAECsLCQAQAEECQQMbCwkAQQIQAEEDGwsJAEECQQMQABsLDAAQAAR/QQEFQQILCwsAAn8QAEECDQALCwsAAn9BAhAADQALCw0AAn8QAEECDgEAAAsLDQACf0ECEAAOAQAACwsEACAACw4AAn8QAEECQQARAAALCw4AAn9BAhAAQQARAAALCw4AAn9BAUECEAARAAALCwkAEABBATYCAAsJAEEKEAA2AgALBgAQAEAACwUAEAAPCwUAEAAaCwkAAn8QAAwACwsKAQF/EAAhACAACwgBAX8QACIACwgAEAAkACMACwcAEAAoAgALBAAgAAsEACAACw0AAn1DAAAAABBEkQsLDAACf0EBEENBCmoLCwwAAn9BCkEBEENrCwsKAAJ/QQEQQ0ULCwwAAn9BARBDQQpNCwsMAAJ/QQpBARBDRwsLCgACfkEBEEOsCwsEACBjC98DAEMAAAAAQQBBAEQAAAAAAAAAAEMAAAAAQwAAAABDAAAAAEQAAAAAAAAAAEMAAAAAQQBBAEMAAAAARAAAAAAAAAAAQgBCAEEAQgBCAEMAAAAAQgBCAEIAQQBDAAAAAEMAAAAAQwAAAABEAAAAAAAAAABDAAAAAEEAQgBDAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEMAAAAAQQBDAAAAAEMAAAAARAAAAAAAAAAAQgBEAAAAAAAAAABBAEIAQwAAAABEAAAAAAAAAABBAEEAQQBCAEQAAAAAAAAAAEEAQgBCAEQAAAAAAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEQAAAAAAAAAAEEAQwAAAABEAAAAAAAAAABEAAAAAAAAAABBAEIAQwAAAABDAAAAAEMAAAAAQQBEAAAAAAAAAABEAAAAAAAAAABEAAAAAAAAAABEAAAAAAAAAABEAAAAAAAAAABDAAAAAEIAQgBBAEEAQQBDAAAAAEQAAAAAAAAAAEEAQgBDAAAAAEMAAAAAQwAAAABBAEEAQwAAAABEAAAAAAAAAABCAEMAAAAARAAAAAAAAAAAQwAAAABDAAAAAEMAAAAAQQBDAAAAAEIAIAAQTAs=", + "AGFzbQEAAAABmAEdYAAAYAABf2AAAX5gAAF9YAABfGAAAnx/YAF/AX9gAX4BfmABfQF9YAF8AXxgAn98An98YAJ/fgJ+f2ACfX8Bf2ACf34BfmACfH0BfWACfnwBfGABfwF/YAF+AX5gAX0BfWABfAF8YAF+AGAEfnx/fgBgAX4Bf2AEfnx/fgF/YAACf3xgAAJ+f2ABfwF+YAF/AX1gAX8BfANRUAECAwQFBgcICQoLDQ8MDhAREhMAAQIDBAUCAQIDBAECAwQFGBkNGgYbHAcHBggJBggJBgYAAAABAQEBAgEDAQAAAQEAAwQEBAEDAQEBAQECBAUBcAEgIAUDAQABBg0BfAFEAAAAAAAAJEALB8QHOwh0eXBlLWkzMgAUCHR5cGUtaTY0ABUIdHlwZS1mMzIAFgh0eXBlLWY2NAAXDHR5cGUtZjY0LWkzMgAYCnR5cGUtaW5kZXgAGQ50eXBlLWZpcnN0LWkzMgAaDnR5cGUtZmlyc3QtaTY0ABsOdHlwZS1maXJzdC1mMzIAHA50eXBlLWZpcnN0LWY2NAAdD3R5cGUtc2Vjb25kLWkzMgAeD3R5cGUtc2Vjb25kLWk2NAAfD3R5cGUtc2Vjb25kLWYzMgAgD3R5cGUtc2Vjb25kLWY2NAAhEHR5cGUtYWxsLWY2NC1pMzIAIhB0eXBlLWFsbC1pMzItZjY0ACMQdHlwZS1hbGwtaTMyLWk2NAAkCGRpc3BhdGNoACUXZGlzcGF0Y2gtc3RydWN0dXJhbC1pNjQAJhdkaXNwYXRjaC1zdHJ1Y3R1cmFsLWkzMgAnF2Rpc3BhdGNoLXN0cnVjdHVyYWwtZjMyACgXZGlzcGF0Y2gtc3RydWN0dXJhbC1mNjQAKQdmYWMtaTY0ACoHZmliLWk2NAArB2ZhYy1pMzIALAdmYWMtZjMyAC0HZmFjLWY2NAAuB2ZpYi1pMzIALwdmaWItZjMyADAHZmliLWY2NAAxBGV2ZW4AMgNvZGQAMwdydW5hd2F5ADQObXV0dWFsLXJ1bmF3YXkANQ9hcy1zZWxlY3QtZmlyc3QANw1hcy1zZWxlY3QtbWlkADgOYXMtc2VsZWN0LWxhc3QAOQ9hcy1pZi1jb25kaXRpb24AOg5hcy1icl9pZi1maXJzdAA7DWFzLWJyX2lmLWxhc3QAPBFhcy1icl90YWJsZS1maXJzdAA9EGFzLWJyX3RhYmxlLWxhc3QAPg5hcy1zdG9yZS1maXJzdAA/DWFzLXN0b3JlLWxhc3QAQBRhcy1tZW1vcnkuZ3Jvdy12YWx1ZQBBD2FzLXJldHVybi12YWx1ZQBCD2FzLWRyb3Atb3BlcmFuZABDC2FzLWJyLXZhbHVlAEQSYXMtbG9jYWwuc2V0LXZhbHVlAEUSYXMtbG9jYWwudGVlLXZhbHVlAEYTYXMtZ2xvYmFsLnNldC12YWx1ZQBHD2FzLWxvYWQtb3BlcmFuZABIEGFzLXVuYXJ5LW9wZXJhbmQASQ5hcy1iaW5hcnktbGVmdABKD2FzLWJpbmFyeS1yaWdodABLD2FzLXRlc3Qtb3BlcmFuZABMD2FzLWNvbXBhcmUtbGVmdABNEGFzLWNvbXBhcmUtcmlnaHQAThJhcy1jb252ZXJ0LW9wZXJhbmQATwkmAQBBAAsgAAECAwUGBwgNCw4MKisyMzQ1Ng8QERIsLS4vMDEECQoK4QlQBQBBsgILBQBC5AILBwBDACBzRQsLAEQAAAAAAMiuQAsNAEQAAAAAAMiuQEEgCwQAIAALBAAgAAsEACAACwQAIAALBgAgACABCwYAIAEgAAsEACABCwQAIAELBAAgAQsEACABCwQAIAALBAAgAAsEACAACwQAIAALXQBBABEAAEIAQQARFABCAEQAAAAAAAAAAEEAQgBBABEVAEEAEQAAQQARAQBFGkEAEQEARRpCAEEAERYARRpCAEQAAAAAAAAAAEEAQgBBABEXAEUaQgBBABEHAFAaCwcAQQARAQALBwBBARECAAsHAEECEQMACwcAQQMRBAALBwBBHREFAAsKAELkAEEFEQcACwkAQSBBBBEGAAsKAELAAEEFEQcACwwAQ8P1qD9BBhEIAAsQAEQ9CtejcD36P0EHEQkACw4AQ2ZmAEJBIEEIEQwACwwAQSBCwABBCRENAAsVAEQAAAAAAABQQEMAAABCQQoRDgALEwBCwABEZmZmZmYGUEBBCxEPAAsHAEEdEQUACxIAQQFEAAAAAAAAAEBBHhEKAAsLAEEBQgJBHxELAAsJACABIAARBwALCQBCCSAAEREACwkAQQkgABEQAAsMAEMAABBBIAAREgALEABEAAAAAAAAIkAgABETAAsYACAAUAR+QgEFIAAgAEIBfUEMEQcAfgsLIgAgAEIBWAR+QgEFIABCAn1BDREHACAAQgF9QQ0RBwB8CwsYACAARQR/QQEFIAAgAEEBa0EXEQYAbAsLIwAgAEMAAAAAWwR9QwAAgD8FIAAgAEMAAIA/k0EYEQgAlAsLLwAgAEQAAAAAAAAAAGEEfEQAAAAAAADwPwUgACAARAAAAAAAAPA/oUEZEQkAogsLIgAgAEEBTQR/QQEFIABBAmtBGhEGACAAQQFrQRoRBgBqCwsuACAAQwAAgD9fBH1DAACAPwUgAEMAAABAk0EbEQgAIABDAACAP5NBGxEIAJILCz4AIABEAAAAAAAA8D9lBHxEAAAAAAAA8D8FIABEAAAAAAAAAEChQRwRCQAgAEQAAAAAAADwP6FBHBEJAKALCxUAIABFBH9BLAUgAEEBa0EPEQYACwsWACAARQR/QeMABSAAQQFrQQ4RBgALCwcAQRARAAALBwBBEhEAAAsHAEEREQAACwwAQQARAQBBAkEDGwsMAEECQQARAQBBAxsLDABBAkEDQQARAQAbCw8AQQARAQAEf0EBBUECCwsOAAJ+QQERAgBBAg0ACwsOAAJ/QQJBABEBAA0ACwsQAAJ9QQIRAwBBAg4BAAALCxAAAn9BAkEAEQEADgEAAAsLDABBABEBAEEBNgIACwwAQQpBAxEEADkDAAsJAEEAEQEAQAALCgBBAUEEEQYADwsKAEIBQQURBwAaCxEAAn1DAACAP0EGEQgADAALCxYBAXxEAAAAAAAA8D9BBxEJACEAIAALFAEBfEQAAAAAAADwP0EHEQkAIgALFABEAAAAAAAA8D9BBxEJACQAIwALCgBBABEBACgCAAsQAAJ9QwAAAABBBhEIAJELCw8AAn9BAUEEEQYAQQpqCwsPAAJ/QQpBAUEEEQYAawsLDQACf0EBQQQRBgBFCwsPAAJ/QQFBBBEGAEEKTQsLDwACf0EKQQFBBBEGAEcLCw0AAn5BAUEEEQYArAsL", + "AGFzbQEAAAABEQNgAn9/AX9gAABgA39/fwF/AwoJAAAAAAABAgICBAwDcAECAnABAwNwAAQHHAMGY2FsbC0xAAYGY2FsbC0yAAcGY2FsbC0zAAgJIwQAQQALAgABAgFBAAsAAwIDBAICQQALAAIBAgICQQMLAAEFClAJBwAgACABagsHACAAIAFrCwcAIAAgAWwLBwAgACABbgsHACAAIAFwCwIACwsAIAAgASACEQAACwsAIAAgASACEQABCwsAIAAgASACEQACCw==", + "AGFzbQEAAAABCAJgAABgAAF/AwwLAAAAAAAAAAAAAQEEBAFwAAEKZgsGAAARAAALBwAAEQAAAQsJAAARAAARAAALCQAAEQAAEQAACwwAABEAABEAABEAAAsGAAARAAALBgAAEQAACwkAABEAABEAAAsJAAARAAARAAALBwAAEQAAGwsKAAARAAAbEQAACw==", + "AGFzbQEAAAABGgRgAX4BfmABfgJ+fmACfn4Dfn5+YAJ+fgF+AwkIAAAAAAABAgAHSwYHZmFjLXJlYwAADWZhYy1yZWMtbmFtZWQAAQhmYWMtaXRlcgACDmZhYy1pdGVyLW5hbWVkAAMHZmFjLW9wdAAEB2ZhYy1zc2EABwrrAQgXACAAQgBRBH5CAQUgACAAQgF9EAB+CwsXACAAQgBRBH5CAQUgACAAQgF9EAF+CwsvAQJ+IAAhAUIBIQICQANAIAFCAFEEQAwCBSABIAJ+IQIgAUIBfSEBCwwACwsgAgsvAQJ+IAAhAUIBIQICQANAIAFCAFEEQAwCBSABIAJ+IQIgAUIBfSEBCwwACwsgAgssAQF+QgEhAQJAIABCAlMNAANAIAEgAH4hASAAQn98IQAgAEIBVQ0ACwsgAQsGACAAIAALCAAgACABIAALHABCASAAAwMQBhAGfhAGQgF9EAVCAFYNABoPCws=", + "AGFzbQEAAAABBgFgAX8BfwMDAgAABw4CBGV2ZW4AAANvZGQAAQorAhQAIABBAEYEf0EBBSAAQQFrEAELCxQAIABBAEYEf0EABSAAQQFrEAALCw==", + "AGFzbQEAAAABvAEcYAAAYAAAYAABf2ABfwBgA398fwF/YAAAYAN/fH4AYAJ/fABgBX99fn98AGAAA398fWAAAn98YAAFf31+f3xgBH99fn8Df35/YAABfmAAAX1gAAF8YAJ/fwF/YAJ+fgF+YAJ9fQF9YAJ8fAF8YAZ9f35/fH8BfGAAA39/f2AAAn9+YAF/AX9gAX8Cf35gAX8Cf39gAAJ/f2ARf359fX98fX9/f318fHx/f30QfH1/f39+fX9/fXx8f31/fANiYQAAAAAAAAAAAAAAAAAAAwMGBwgAAAIJCgsBAgMEAgMEAAUMAAINDg8CDQ4PDxAREhMQERITFAAAAg0ODwoVAAIWAAINDg8KFQIWAAINDg8KFQIWAxcYAxcYAxcZGwINDg8HlAlGAWYAAgFnAAQKdHlwZS11c2UtMQAZCnR5cGUtdXNlLTIAGgp0eXBlLXVzZS0zABsKdHlwZS11c2UtNAAcCnR5cGUtdXNlLTUAHQp0eXBlLXVzZS02AB4KdHlwZS11c2UtNwAfD2xvY2FsLWZpcnN0LWkzMgAkD2xvY2FsLWZpcnN0LWk2NAAlD2xvY2FsLWZpcnN0LWYzMgAmD2xvY2FsLWZpcnN0LWY2NAAnEGxvY2FsLXNlY29uZC1pMzIAKBBsb2NhbC1zZWNvbmQtaTY0ACkQbG9jYWwtc2Vjb25kLWYzMgAqEGxvY2FsLXNlY29uZC1mNjQAKwtsb2NhbC1taXhlZAAsD3BhcmFtLWZpcnN0LWkzMgAtD3BhcmFtLWZpcnN0LWk2NAAuD3BhcmFtLWZpcnN0LWYzMgAvD3BhcmFtLWZpcnN0LWY2NAAwEHBhcmFtLXNlY29uZC1pMzIAMRBwYXJhbS1zZWNvbmQtaTY0ADIQcGFyYW0tc2Vjb25kLWYzMgAzEHBhcmFtLXNlY29uZC1mNjQANAtwYXJhbS1taXhlZAA1BWVtcHR5ADYKdmFsdWUtdm9pZAA3CXZhbHVlLWkzMgA4CXZhbHVlLWk2NAA5CXZhbHVlLWYzMgA6CXZhbHVlLWY2NAA7DXZhbHVlLWkzMi1mNjQAPBF2YWx1ZS1pMzItaTMyLWkzMgA9EHZhbHVlLWJsb2NrLXZvaWQAPg92YWx1ZS1ibG9jay1pMzIAPxN2YWx1ZS1ibG9jay1pMzItaTY0AEAMcmV0dXJuLWVtcHR5AEEKcmV0dXJuLWkzMgBCCnJldHVybi1pNjQAQwpyZXR1cm4tZjMyAEQKcmV0dXJuLWY2NABFDnJldHVybi1pMzItZjY0AEYScmV0dXJuLWkzMi1pMzItaTMyAEcQcmV0dXJuLWJsb2NrLWkzMgBIFHJldHVybi1ibG9jay1pMzItaTY0AEkLYnJlYWstZW1wdHkASglicmVhay1pMzIASwlicmVhay1pNjQATAlicmVhay1mMzIATQlicmVhay1mNjQATg1icmVhay1pMzItZjY0AE8RYnJlYWstaTMyLWkzMi1pMzIAUA9icmVhay1ibG9jay1pMzIAURNicmVhay1ibG9jay1pMzItaTY0AFIRYnJlYWstYnJfaWYtZW1wdHkAUw9icmVhay1icl9pZi1udW0AVBNicmVhay1icl9pZi1udW0tbnVtAFUUYnJlYWstYnJfdGFibGUtZW1wdHkAVhJicmVhay1icl90YWJsZS1udW0AVxZicmVhay1icl90YWJsZS1udW0tbnVtAFgbYnJlYWstYnJfdGFibGUtbmVzdGVkLWVtcHR5AFkZYnJlYWstYnJfdGFibGUtbmVzdGVkLW51bQBaHWJyZWFrLWJyX3RhYmxlLW5lc3RlZC1udW0tbnVtAFsJbGFyZ2Utc2lnAFwOaW5pdC1sb2NhbC1pMzIAXQ5pbml0LWxvY2FsLWk2NABeDmluaXQtbG9jYWwtZjMyAF8OaW5pdC1sb2NhbC1mNjQAYAq3BmECAAsCAAsCAAsCAAsCAAsCAAsCAAsEAQF/CwQBAX8LCAMBfwF8AX4LBgIBfwF8CwwFAX8BfQF+AX8BfAsCAAsCAAsCAAsCAAsCAAsCAAsCAAsCAAsCAAsDAAALAwAACwMAAAsDAAALAgALBABBAAsCAAsEAEEACwQAQQALAgALBABBAAsCAAsCAAsQBgF9AX8BfgF/AXwBfwAACxAGAX0BfwF+AX8BfAF/AAALBgECfyAACwYBAn4gAAsGAQJ9IAALBgECfCAACwYBAn8gAQsGAQJ+IAELBgECfSABCwYBAnwgAQsoBgF9AX8BfgF/AXwBfyAAjBogAUUaIAJQGiADRRogBJoaIAVFGiAECwQAIAALBAAgAAsEACAACwQAIAALBAAgAQsEACABCwQAIAELBAAgAQscACAAjBogAUUaIAJQGiADRRogBJoaIAVFGiAECwIACwQAEAALBQBBzQALBQBC4TwLBwBDZmabQgsLAEThehSuR3FTQAsOAEHNAEQAAAAAAAAcQAsIAEEBQQJBAwsJAAJAEAAQAAsLCgACfxAAQc0ACwsLAAIWEABBAUICCwsDAA8LBgBBzgAPCwYAQsY9DwsIAENmZp1CDwsMAERSuB6F67FTQA8LDwBBzgBEUrgeheuxU0APCwkAQQFBAkEDDwsLAAJ/EABBzQALDwsMAAIWEABBAUICCw8LBAAMAAsHAEHPAAwACwcAQqs+DAALCQBDzcyfQgwACw0ARMP1KFyP8lNADAALEABBzwBEw/UoXI/yU0AMAAsKAEEBQQJBAwwACwwAAn8QAEHNAAsMAAsNAAIWEABBAUICCwwACwYAIAANAAsLAEEyIAANABpBMwsQAEEyQjMgAA0AGhpBM0I0CwkAIAAOAgAAAAsMAEEyIAAOAQAAQTMLEABBMkIzIAAOAQAAQTNCNAsMAAJAIAAOAgABAAsLEwACf0EyIAAOAgABAEEzC0ECagsXAAIaQTJBMyAADgIAAQBBM0F9C2pBNAsiACAFIAIgACAIIAcgASADIAkgBCAGIA0gCyAPIBAgDiAMCwYBAX8gAAsGAQF+IAALBgEBfSAACwYBAXwgAAs=", + "AGFzbQEAAAABDANgAX8AYAABfGAAAAMGBQEAAAECCikFCwBEAAAAAAAAAAALAgALAgALCwBEAAAAAAAAAAALCQBBABACEAMaCw==", + "AGFzbQEAAAABCgJgAAF/YAF/AX8DBQQBAQEABw0DAWYAAAFnAAIBcAADCiEEBgEBfyABCwYBAX8gAQsGACAAEAELCgEBf0EqIQAgAAs=", + "AGFzbQEAAAABHQRgAABgAABgCH5+fH58fn1/AGAIfH58fnx+fX8AAwwLAAMAAwMCAgAAAAAEBQFwAQcHB3cEGXNpZ25hdHVyZS1leHBsaWNpdC1yZXVzZWQABxlzaWduYXR1cmUtaW1wbGljaXQtcmV1c2VkAAgcc2lnbmF0dXJlLWV4cGxpY2l0LWR1cGxpY2F0ZQAJHHNpZ25hdHVyZS1pbXBsaWNpdC1kdXBsaWNhdGUACgkNAQBBAAsHBAIBBAAFBgqFAgsCAAsCAAsCAAsCAAsCAAsCAAsCAAsMAEEBEQAAQQQRAAALiQEARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEAEQMARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEECEQMARAAAAAAAAAAAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEDEQMACwcAQQERAQALTgBCAEIARAAAAAAAAAAAQgBEAAAAAAAAAABCAEMAAAAAQQBBBRECAEIAQgBEAAAAAAAAAABCAEQAAAAAAAAAAEIAQwAAAABBAEEGEQIACw==", + "AGFzbQEAAAABGwdgAABgAABgAABgAAF/YAABf2ABfwF/YAF/AAIWAQhzcGVjdGVzdAlwcmludF9pMzIABgMHBgABBAUFBgccBANvbmUAAwN0d28ABAV0aHJlZQAFBGZvdXIABgojBgIACwIACwQAQQ0LBwAgAEEBagsHACAAQQJrCwYAIAAQAAs=", + "AGFzbQEAAAABDgNgAAF/YAABf2ABfwF/AwgHAAAAAQECAgQFAXABBwcHEQIFY2FsbHQABQVjYWxsdQAGCQ0BAEEACwcAAQIDBAACCioHBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwcAIAARAAALBwAgABEBAAs=", + "AGFzbQEAAAABCgJgAAF/YAF/AX8DBAMAAAEEBQFwAQICBwkBBWNhbGx0AAIJCAEAQQALAgABChMDBABBAQsEAEECCwcAIAARAAAL", + "AGFzbQEAAAABXRBgAn9/AX9gAABgAAF/YAF/AGADf3x/A398f2ABfwF/YAF/An9/YAADf35/YAACf39gAAJ9fWABfwN/f35gAAN/f35gAn9/An9/YAN+fn8Cfn9gAn5+AX5gAX4BfgM2NQEDBQYABQUFBQUFBQUFBQUABQUFAwMFBQUFAwUFBQUFBQAFAAUFBQIFCgUFBQUFBQUNDgEBBAUBcAEBAQUDAQABBgYBfwFBCgsHzgYyBWVtcHR5AAEIc2luZ3VsYXIAAgVtdWx0aQADBm5lc3RlZAAED2FzLXNlbGVjdC1maXJzdAAFDWFzLXNlbGVjdC1taWQABg5hcy1zZWxlY3QtbGFzdAAHDWFzLWxvb3AtZmlyc3QACAthcy1sb29wLW1pZAAJDGFzLWxvb3AtbGFzdAAKD2FzLWlmLWNvbmRpdGlvbgALDmFzLWJyX2lmLWZpcnN0AAwNYXMtYnJfaWYtbGFzdAANEWFzLWJyX3RhYmxlLWZpcnN0AA4QYXMtYnJfdGFibGUtbGFzdAAPFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAERRhcy1jYWxsX2luZGlyZWN0LW1pZAASFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAATDmFzLXN0b3JlLWZpcnN0ABQNYXMtc3RvcmUtbGFzdAAVFGFzLW1lbW9yeS5ncm93LXZhbHVlABYNYXMtY2FsbC12YWx1ZQAYD2FzLXJldHVybi12YWx1ZQAZD2FzLWRyb3Atb3BlcmFuZAAaC2FzLWJyLXZhbHVlABsSYXMtbG9jYWwuc2V0LXZhbHVlABwSYXMtbG9jYWwudGVlLXZhbHVlAB0TYXMtZ2xvYmFsLnNldC12YWx1ZQAeD2FzLWxvYWQtb3BlcmFuZAAfEGFzLXVuYXJ5LW9wZXJhbmQAIBFhcy1iaW5hcnktb3BlcmFuZAAhD2FzLXRlc3Qtb3BlcmFuZAAiEmFzLWNvbXBhcmUtb3BlcmFuZAAjEmFzLWJpbmFyeS1vcGVyYW5kcwAkE2FzLWNvbXBhcmUtb3BlcmFuZHMAJRFhcy1taXhlZC1vcGVyYW5kcwAmCmJyZWFrLWJhcmUAJwticmVhay12YWx1ZQAoEWJyZWFrLW11bHRpLXZhbHVlACkFcGFyYW0AKgZwYXJhbXMAKwlwYXJhbXMtaWQALAtwYXJhbS1icmVhawAtDHBhcmFtcy1icmVhawAuD3BhcmFtcy1pZC1icmVhawAvB2VmZmVjdHMAMBJhZGQ2NF91X3dpdGhfY2FycnkAMRFhZGQ2NF91X3NhdHVyYXRlZAAyCHR5cGUtdXNlADMSYXR5cGljYWwtY29uZGl0aW9uADQJBwEAQQALARAKxgs1AgALFgAgAARACyAABEALIAAEQAsgAARACwsaACAABEABCyAABEABBQELIAAEf0EHBUEICwtnACAABEAQABAAEAALIAAEQAUQABAAEAALIAAEfxAAEABBCBAABRAAEABBCRAACyAABAcQABAAQQEQABAAEABCAhAAEAAQAEEDEAAFEAAQAEF/EAAQABAAQn4QABAAEABBfRAACxoaC1IAIAAEfyABBEAQAAJACwELIAEEQAUQAAJACwELIAEEfxAAQQkFEABBCgsFIAEEQBAAAkALAQsgAQRABRAAAkALAQsgAQR/EABBCgUQAEELCwsLFQAgAAR/EABBAQUQAEEAC0ECQQMbCxUAQQIgAAR/EABBAQUQAEEAC0EDGwsVAEECQQMgAAR/EABBAQUQAEEACxsLFwADfyAABH8QAEEBBRAAQQALEAAQAAsLFwADfxAAIAAEfxAAQQEFEABBAAsQAAsLFwADfxAAEAAgAAR/EABBAQUQAEEACwsLGAAgAAR/QQEFQQALBH8QAEECBRAAQQMLCxoAAn8gAAR/EABBAQUQAEEAC0ECDQBBAw8LCxoAAn9BAiAABH8QAEEBBRAAQQALDQBBAw8LCxkAAn8gAAR/EABBAQUQAEEAC0ECDgEAAAsLGQACf0ECIAAEfxAAQQEFEABBAAsOAQAACwsEACAACxoAAn8gAAR/EABBAQUQAEEAC0ECQQARAAALCxoAAn9BAiAABH8QAEEBBRAAQQALQQARAAALCxoAAn9BAkEAIAAEfxAAQQEFEABBAAsRAAALCxUAIAAEfxAAQQEFEABBAAtBAjYCAAsVAEECIAAEfxAAQQEFEABBAAs2AgALDgAgAAR/QQEFQQALQAALBAAgAAsOACAABH9BAQVBAAsQFwsNACAABH9BAQVBAAsPCw0AIAAEf0EBBUEACxoLEQACfyAABH9BAQVBAAsMAAsLEgEBfyAABH9BAQVBAAshACAACw4AIAAEf0EBBUEACyIACxAAIAAEf0EBBUEACyQAIwALDwAgAAR/QQsFQQoLKAIACxEAIAAEfxAAQQ0FEABBcwtoCx8AIAAEfxAAQQMFEABBfQsgAQR/EABBBAUQAEF7C2wLEQAgAAR/EABBDQUQAEEAC0ULKwAgAAR9EABDAABAQAUQAEMAAEDACyABBH0QAEMAAIBABRAAQwAAgMALXgsZACAABAgQAEEDEABBBAUQAEEDEABBfAtsCyUAIAAECRAAQwAAQEAQAEMAAEBABRAAQwAAAMAQAEMAAEDAC14LHAAgAAQIEABBAxAAQQQFEABBfRAAQXwLQQVqbAtnAEEBBEAMAAALQQEEQAwAAAUAC0EABEAABQwAAAtBAQRAQQENAAALQQEEQEEBDQAABQALQQAEQAAFQQENAAALQQEEQEEADgAAAAtBAQRAQQAOAAAABQALQQAEQAAFQQAOAAAAC0ETCxQAIAAEf0ESDABBEwVBFQwAQRQLCyQAIAAEC0ESQW5CEgwAQRNBbUITBUFuQRJCbgwAQW1BE0JtCwsQAEEBIAAEBUECagVBfmoLCw4AQQFBAiAABABqBWsLCwwAQQFBAiAABAwLagsUAEEBIAAEBUECagwABUF+agwACwsSAEEBQQIgAAQAagwABWsMAAsLDgBBAUECIAAEDAwAC2oLUQEBfwJ/QQEhASAACwRAIAFBA2whASABQQVrIQEgAUEHbCEBDAAgAUHkAGwhAQUgAUEFbCEBIAFBB2shASABQQNsIQEMACABQegHbCEBCyABCxcBAX4gACABfCACrXwhAyADIAMgAFQPCxAAIAAgAUEAEDEEDxpCfwsLVQBBAQRAC0EBBH9BAAVBAgtBAQQDGgUaC0EARAAAAAAAAAAAQQBBAQQECxoaGkEBBH9BAAVBAgtBAQQDGgUaC0EARAAAAAAAAAAAQQBBAQQECxoaGgsNAEEABEALQQFFBEALCw==", + "AGFzbQEAAAABCgJgAAF/YAF/AX8DExIAAAAAAQAAAAABAQAAAAAAAAAHmgESBWJsb2NrAAAFbG9vcDEAAQVsb29wMgACBWxvb3AzAAMFbG9vcDQABAVsb29wNQAFBWxvb3A2AAYCaWYABwNpZjIACAZzd2l0Y2gACQZyZXR1cm4ACgZicl9pZjAACwZicl9pZjEADAZicl9pZjIADQZicl9pZjMADgJicgAPCXNoYWRvd2luZwAQDHJlZGVmaW5pdGlvbgARCoMGEgsAAn9BAQwAQQALCyMBAX9BACEAAn8DfyAAQQFqIQAgAEEFRgRAIAAMAgsMAAsLCzQBAX9BACEAAn8DfyAAQQFqIQAgAEEFRgRADAELIABBCEYEQCAADAILIABBAWohAAwACwsLIwEBf0EAIQACfwN/IABBAWohACAAQQVGBEAgAAwCCyAACwsLIwEBf0EBIQECfwN/IAEgAWohASABIABLBEAgAQwCCwwACwsLCgADf0EBC0EBagsLAAN/QQANAEEDCwuEAQEBf0EAIQACQEEBBEAMAEGaBSEACyAAQQFqIQBBAQRADABBmgUhAAVB+AYhAAsgAEEBaiEAQQEEQAwAQZoFIQAFQfgGIQALIABBAWohAEEABEBB+AYhAAUMAEGaBSEACyAAQQFqIQBBAARAQfgGIQAFDABBmgUhAAsgAEEBaiEACyAAC4QBAQF/QQAhAAJAQQEEQAwAQZoFIQALIABBAWohAEEBBEAMAEGaBSEABUH4BiEACyAAQQFqIQBBAQRADABBmgUhAAVB+AYhAAsgAEEBaiEAQQAEQEH4BiEABQwAQZoFIQALIABBAWohAEEABEBB+AYhAAUMAEGaBSEACyAAQQFqIQALIAALLQACf0EKAn8CQAJAAkACQAJAIAAOBAQAAQIDCwtBAgwDC0EDDAMLC0EFC2wLCxgAAkACQAJAIAAOAQABDAILQQAPCwtBAgtWAQF/QQAhAAJ/AkBBAA0AIABBAXIhAEEBDQAgAEECciEACwJ/IABBBHIhACAAC0EADQAaIABBCHIhAAJ/IABBEHIhACAAC0EBDQAaIABBIHIhACAACwsTAAJ/An9BAQwAC0EBDQAaQQALCxgAAn9BAQRAAn9BAQwAC0EBDQEaC0EACwskAQF/An8Cf0EBIQAgAAsCf0ECIQAgAAsNABpBAAtBAGoaIAALIQACf0EBBEACf0EBDAALDAEFAkACf0EBDAALGgsLQQELCwwAAn9BAQwAQQJzCwsSAAJ/An9BAgsCf0EDDAALagsL", + "AGFzbQEAAAABTg5gAn9/AX9gAABgAAF/YAF/AGADf3x/A398f2AAA39+f2ABfwF/YAACf39gAAJ9fWAAA39/fmADf39+AGACf38Cf39gAX4BfmACfX0BfQM5OAEBAgICAgICAgECAgICAgIAAgICAQECBgICAQICAgICAgICAgICAgICCQICAgICAgICAgIMDA0BBAUBcAEBAQUDAQABBgYBfwFBAAsHyAY1BWVtcHR5AAEIc2luZ3VsYXIAAgVtdWx0aQADBm5lc3RlZAAEBGRlZXAABQ9hcy1zZWxlY3QtZmlyc3QABg1hcy1zZWxlY3QtbWlkAAcOYXMtc2VsZWN0LWxhc3QACA9hcy1pZi1jb25kaXRpb24ACQphcy1pZi10aGVuAAoKYXMtaWYtZWxzZQALDmFzLWJyX2lmLWZpcnN0AAwNYXMtYnJfaWYtbGFzdAANEWFzLWJyX3RhYmxlLWZpcnN0AA4QYXMtYnJfdGFibGUtbGFzdAAPFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAERRhcy1jYWxsX2luZGlyZWN0LW1pZAASFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAATDmFzLXN0b3JlLWZpcnN0ABQNYXMtc3RvcmUtbGFzdAAVFGFzLW1lbW9yeS5ncm93LXZhbHVlABYNYXMtY2FsbC12YWx1ZQAYD2FzLXJldHVybi12YWx1ZQAZD2FzLWRyb3Atb3BlcmFuZAAaC2FzLWJyLXZhbHVlABsSYXMtbG9jYWwuc2V0LXZhbHVlABwSYXMtbG9jYWwudGVlLXZhbHVlAB0TYXMtZ2xvYmFsLnNldC12YWx1ZQAeD2FzLWxvYWQtb3BlcmFuZAAfEGFzLXVuYXJ5LW9wZXJhbmQAIBFhcy1iaW5hcnktb3BlcmFuZAAhD2FzLXRlc3Qtb3BlcmFuZAAiEmFzLWNvbXBhcmUtb3BlcmFuZAAjEmFzLWJpbmFyeS1vcGVyYW5kcwAkE2FzLWNvbXBhcmUtb3BlcmFuZHMAJRFhcy1taXhlZC1vcGVyYW5kcwAmCmJyZWFrLWJhcmUAJwticmVhay12YWx1ZQAoEWJyZWFrLW11bHRpLXZhbHVlACkOYnJlYWstcmVwZWF0ZWQAKgticmVhay1pbm5lcgArCmNvbnQtaW5uZXIALAVwYXJhbQAtBnBhcmFtcwAuCXBhcmFtcy1pZAAvC3BhcmFtLWJyZWFrADAMcGFyYW1zLWJyZWFrADEPcGFyYW1zLWlkLWJyZWFrADIHZWZmZWN0cwAzBXdoaWxlADQDZm9yADUHbmVzdGluZwA2CHR5cGUtdXNlADcJBwEAQQALARAKgws4AgALCAADQAsDQAsLCwADQAELA39BBwsLPAADQBAAEAAQABAACwN/EAAQAEEIEAALGgMFEAAQABAAQQgQABAAEAAQAEIHEAAQABAAEABBCRAACxoaCxUAA38DQBAAAkALAQsDfxAAQQkLCwt/AAN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/A38CfwN/An8DfwJ/EABBlgELCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwwAA39BAQtBAkEDGwsMAEECA39BAQtBAxsLDABBAkEDA39BAQsbCwwAA39BAQsEQBAACwsPAEEBBH8Df0EBCwVBAgsLDwBBAQR/QQIFA39BAQsLCw4AAn8Df0EBC0ECDQALCw4AAn9BAgN/QQELDQALCxAAAn8Df0EBC0ECDgEAAAsLEAACf0ECA39BAQsOAQAACwsEACAACxEAAn8Df0EBC0ECQQARAAALCxEAAn9BAgN/QQELQQARAAALCxEAAn9BAUECA39BAAsRAAALCwwAA39BAQtBATYCAAsMAEEKA39BAQs2AgALCQADf0EBC0AACwQAIAALCQADf0EBCxAXCwgAA39BAQsPCwgAA39BAQsaCwwAAn8Df0EBCwwACwsNAQF/A39BAQshACAACwsBAX8Df0EBCyIACwsAA39BAQskACMACwoAA39BAQsoAgALCgADfxAAQQ0LaAsRAAN/EABBAwsDfxAAQQQLbAsKAAN/EABBDQtFCxcAA30QAEMAAEBACwN9EABDAABAQAteCw4AAwcQAEEDEABBBAtsCxQAAwgQAEMAAEBAEABDAABAQAteCxEAAwcQAEEDEABBBAtBBWpsCzQAAkADQAwBDAAACwsCQANAQQENAQALCwJAA0BBAA4AAQALCwJAA0BBAQ4CAQEBAAsLQRMLFwACf0EAAwMCQEESDAILQRQMAAtBEwsLJwACCUEAQQBCAAMKAkBBEkFuQhIMAgtBFEFsQhQMAAtBE0FtQhMLCzQAAn8Df0ESDAFBEwwBQRRBAA0BGkEUQQENARpBFQwBQRZBAA4AAUEXQQEOAgEBAUEVCwsLZgEBf0EAIQAgAAJ/A38Cf0EBDAILCwtqIQAgAAJ/A38Df0ECDAILCwtqIQAgAAJ/A38CfwN/QQQMAQsLCwtqIQAgAAJ/A39BCAwBaAsLaiEAIAACfwN/A39BEAwCC2gLC2ohACAACzABAX9BACEAIAADfwN/DAELC2ohACAAA38MAGgLaiEAIAADfwN/DAELaAtqIQAgAAsKAEEBAwZBAmoLCwoAQQFBAgMAagsLCgBBAUECAwsLagsVAQF/QQEDBkEEaiIAIABBCkkNAAsLGAEBf0EBQQIDAGoiAEEDIABBCkkNABoLCx4BAX9BACEAQQFBAgMLIABBAWohACAAQQpJDQALagsyAQF/AkADQEEBIQAgAEEDbCEAIABBBWshACAAQQdsIQAMASAAQeQAbCEACwsgAEFyRgslAQF+QgEhAQJAA0AgAFANASAAIAF+IQEgAEIBfSEADAALCyABCysBAn5CASEBQgIhAgJAA0AgAiAAVg0BIAEgAn4hASACQgF8IQIMAAsLIAELWgECfQJAA0AgAEMAAAAAWw0BIAEhAgJAA0AgAkMAAAAAWw0BIAJDAAAAAF0NAyADIAKSIQMgAkMAAABAkyECDAALCyADIACVIQMgAEMAAIA/kyEADAALCyADCz0AA0ALA39BAAsDAxoLQQBEAAAAAAAAAABBAAMECxoaGgN/QQALAwMaC0EARAAAAAAAAAAAQQADBAsaGhoL", + "AGFzbQEAAAABNgpgAAF/YAABfmAAAX1gAAF8YAF/AX9gAX4BfmABfQF9YAF8AXxgBX59fH9/AGAFfn18f38BfAMUEwABAgMEBQYHCAkEBAQEBAQEBAQHswITDnR5cGUtbG9jYWwtaTMyAAAOdHlwZS1sb2NhbC1pNjQAAQ50eXBlLWxvY2FsLWYzMgACDnR5cGUtbG9jYWwtZjY0AAMOdHlwZS1wYXJhbS1pMzIABA50eXBlLXBhcmFtLWk2NAAFDnR5cGUtcGFyYW0tZjMyAAYOdHlwZS1wYXJhbS1mNjQABwp0eXBlLW1peGVkAAgEcmVhZAAJDmFzLWJsb2NrLXZhbHVlAAoNYXMtbG9vcC12YWx1ZQALC2FzLWJyLXZhbHVlAAwOYXMtYnJfaWYtdmFsdWUADRNhcy1icl9pZi12YWx1ZS1jb25kAA4RYXMtYnJfdGFibGUtdmFsdWUADw9hcy1yZXR1cm4tdmFsdWUAEAphcy1pZi10aGVuABEKYXMtaWYtZWxzZQASCo4CEwYBAX8gAAsGAQF+IAALBgEBfSAACwYBAXwgAAsEACAACwQAIAALBAAgAAsEACAACywDAX0CfgF8IABQGiABjBogApoaIANFGiAERRogBYwaIAZQGiAHUBogCJoaCz8DAX0CfgF8QwAAsEAhBUIGIQZEAAAAAAAAIEAhCCAAuiABuyACIAO4IAS3IAW7IAa6IAe6IAigoKCgoKCgoAsHAAJ/IAALCwcAA38gAAsLCQACfyAADAALCwsAAn8gAEEBDQALCwsAAn8gACAADQALCx0AAkACQAJAIAAOAgABAkEADwtBAQ8LQQIPC0EDCwUAIAAPCwwAIAAEfyAABUEACwsMACAABH9BAQUgAAsL", + "AGFzbQEAAAABJQdgAABgAX8AYAF+AGABfQBgAXwAYAV+fXx/fwBgBX59fH9/AX4DFBMAAAAAAQIDBAUGAQEBAQEBAQEBB7QCEw50eXBlLWxvY2FsLWkzMgAADnR5cGUtbG9jYWwtaTY0AAEOdHlwZS1sb2NhbC1mMzIAAg50eXBlLWxvY2FsLWY2NAADDnR5cGUtcGFyYW0taTMyAAQOdHlwZS1wYXJhbS1pNjQABQ50eXBlLXBhcmFtLWYzMgAGDnR5cGUtcGFyYW0tZjY0AAcKdHlwZS1taXhlZAAIBXdyaXRlAAkOYXMtYmxvY2stdmFsdWUACg1hcy1sb29wLXZhbHVlAAsLYXMtYnItdmFsdWUADA5hcy1icl9pZi12YWx1ZQANE2FzLWJyX2lmLXZhbHVlLWNvbmQADhFhcy1icl90YWJsZS12YWx1ZQAPD2FzLXJldHVybi12YWx1ZQAQCmFzLWlmLXRoZW4AEQphcy1pZi1lbHNlABIK0gITCAEBf0EAIQALCAEBfkIAIQALCwEBfUMAAAAAIQALDwEBfEQAAAAAAAAAACEACwYAQQohAAsGAEILIQALCQBDmpkxQSEACw0ARGZmZmZmZihAIQALQAMBfQJ+AXxCACEAQwAAAAAhAUQAAAAAAAAAACECQQAhA0EAIQRDAAAAACEFQgAhBkIAIQdEAAAAAAAAAAAhCAtPAwF9An4BfEOamZm+IQFBKCEDQXkhBEMAALBAIQVCBiEGRAAAAAAAACBAIQggALogAbsgAiADuCAEtyAFuyAGuiAHuiAIoKCgoKCgoKCwCwkAAkBBASEACwsJAANAQQMhAAsLCwACQEEJIQAMAAsLDQACQEEIIQBBAQ0ACwsNAAJAQQZBCSEADQALCw4AAkBBCiEAQQEOAAALCwcAQQchAA8LCwAgAARAQQMhAAsLDAAgAARABUEBIQALCw==", + "AGFzbQEAAAABWBBgA39/fwF/YAABf2AAAX5gAAF9YAABfGABfwF/YAF+AX5gAX0BfWABfAF8YAV+fXx/fwBgBX59fH9/AX5gBX59fH9/AXxgAABgAX8AYAJ/fwF/YAF+AX8DOjkBAgMEBQYHCAkKCwwFBQUFBQUFDQUFDQUFBQUFBQ4OBQAFBQUFBQUFDAUMBQUNDQ0NBwUFBQUFDwUEBQFwAQEBBQMBAAEGBgF/AUEACwfPBzcOdHlwZS1sb2NhbC1pMzIAAA50eXBlLWxvY2FsLWk2NAABDnR5cGUtbG9jYWwtZjMyAAIOdHlwZS1sb2NhbC1mNjQAAw50eXBlLXBhcmFtLWkzMgAEDnR5cGUtcGFyYW0taTY0AAUOdHlwZS1wYXJhbS1mMzIABg50eXBlLXBhcmFtLWY2NAAHCnR5cGUtbWl4ZWQACAV3cml0ZQAJBnJlc3VsdAAKDmFzLWJsb2NrLWZpcnN0AAwMYXMtYmxvY2stbWlkAA0NYXMtYmxvY2stbGFzdAAODWFzLWxvb3AtZmlyc3QADwthcy1sb29wLW1pZAAQDGFzLWxvb3AtbGFzdAARC2FzLWJyLXZhbHVlABINYXMtYnJfaWYtY29uZAATDmFzLWJyX2lmLXZhbHVlABQTYXMtYnJfaWYtdmFsdWUtY29uZAAVEWFzLWJyX3RhYmxlLWluZGV4ABYRYXMtYnJfdGFibGUtdmFsdWUAFxdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAYD2FzLXJldHVybi12YWx1ZQAZCmFzLWlmLWNvbmQAGgphcy1pZi10aGVuABsKYXMtaWYtZWxzZQAcD2FzLXNlbGVjdC1maXJzdAAdEGFzLXNlbGVjdC1zZWNvbmQAHg5hcy1zZWxlY3QtY29uZAAfDWFzLWNhbGwtZmlyc3QAIQthcy1jYWxsLW1pZAAiDGFzLWNhbGwtbGFzdAAjFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAJBRhcy1jYWxsX2luZGlyZWN0LW1pZAAlFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAmFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAJxJhcy1sb2NhbC5zZXQtdmFsdWUAKBJhcy1sb2NhbC50ZWUtdmFsdWUAKRNhcy1nbG9iYWwuc2V0LXZhbHVlACoPYXMtbG9hZC1hZGRyZXNzACsQYXMtbG9hZE4tYWRkcmVzcwAsEGFzLXN0b3JlLWFkZHJlc3MALQ5hcy1zdG9yZS12YWx1ZQAuEWFzLXN0b3JlTi1hZGRyZXNzAC8PYXMtc3RvcmVOLXZhbHVlADAQYXMtdW5hcnktb3BlcmFuZAAxDmFzLWJpbmFyeS1sZWZ0ADIPYXMtYmluYXJ5LXJpZ2h0ADMPYXMtdGVzdC1vcGVyYW5kADQPYXMtY29tcGFyZS1sZWZ0ADUQYXMtY29tcGFyZS1yaWdodAA2EmFzLWNvbnZlcnQtb3BlcmFuZAA3E2FzLW1lbW9yeS5ncm93LXNpemUAOAkHAQBBAAsBIAqDBzkIAQF/QQAiAAsIAQF+QgAiAAsLAQF9QwAAAAAiAAsPAQF8RAAAAAAAAAAAIgALBgBBCiIACwYAQgsiAAsJAEOamTFBIgALDQBEZmZmZmZmKEAiAAtSAwF9An4BfEIAIgBQGkMAAAAAIgGMGkQAAAAAAAAAACICmhpBACIDRRpBACIERRpDAAAAACIFjBpCACIGUBpCACIHUBpEAAAAAAAAAAAiCJoaC1UDAX0CfgF8Q5qZmb4iARpBKCIDGkF5IgQaQwAAsEAiBRpCBiIGGkQAAAAAAAAgQCIIGiAAuiABuyACIAO4IAS3IAW7IAa6IAe6IAigoKCgoKCgoLALTwMBfQJ+AXxCASIAukMAAABAIgG7RGZmZmZmZgpAIgJBBCIDuEEFIgS3QwAAsEAiBbtCBiIGukIAIge6RAAAAAAAACBAIgigoKCgoKCgoAsCAAsLAAJ/QQEiABALCwsNAAJ/EAtBASIAEAsLCw0AAn8QCxALQQEiAAsLCwADf0EDIgAQCwsLDQADfxALQQQiABALCwsNAAN/EAsQC0EFIgALCwsAAn9BCSIADAALCwsAAkBBASIADQALCxAAAn9BCCIAQQENABpBBwsLEAACf0EGQQkiAA0AGkEHCwsOAAJAQQAiAA4CAAAACwsSAAJ/QQoiAEEBDgIAAABBBwsLEQACf0EGQQsiAA4BAABBBwsLBwBBByIADwsOAEECIgAEf0EABUEBCwsOACAABH9BAyIABSAACwsOACAABH8gAAVBBCIACwsLAEEFIgAgACABGwsLACAAQQYiACABGwsLAEEAQQFBByIAGwsEAEF/CwwAQQwiAEECQQMQIAsMAEEBQQ0iAEEDECALDABBAUECQQ4iABAgCw8AQQEiAEECQQNBABEAAAsPAEEBQQIiAEEDQQARAAALDwBBAUECQQMiAEEAEQAACw8AQQFBAkEDQQAiABEAAAsKAQF/QQEiACEACwgAQQEiACIACwoBAX9BASIAJAALCQBBASIAKAIACwkAQQMiACwAAAsLAEEeIgBBBzYCAAsLAEECQQEiADYCAAsLAEEBIgBBBzoAAAsLAEECQQEiADsBAAsKAEPi8YB/IgCMCwkAQQMiAEEKagsJAEEKQQQiAGsLBwBBACIARQsJAEErIgBBCkwLCQBBCkEqIgBHCwcAQikiAKcLCABBKCIAQAAL", + "AGFzbQEAAAABIwdgAn9/AX9gAABgA39/fwF/YAABf2ABfwBgAX8Bf2ACf38AA1RTAQIDAwMDBAQEBQUFBQUDAwMDAwMDAwQEBAUFBQUFBQUFBQUFBQUFAgICAgIFBQUFBQUFBQUFBQUFBQUFBQADAwMDAwUFBQUFBQMDAwUFBQYGBgYEBQFwAQEBBQMBAAEGBgF/AUEACwfqC1ANYXMtZnVuYy1maXJzdAACC2FzLWZ1bmMtbWlkAAMMYXMtZnVuYy1sYXN0AAQSYXMtZnVuYy1ldmVyeXdoZXJlAAUNYXMtZHJvcC1maXJzdAAGDGFzLWRyb3AtbGFzdAAHEmFzLWRyb3AtZXZlcnl3aGVyZQAID2FzLXNlbGVjdC1maXJzdAAJDmFzLXNlbGVjdC1taWQxAAoOYXMtc2VsZWN0LW1pZDIACw5hcy1zZWxlY3QtbGFzdAAMFGFzLXNlbGVjdC1ldmVyeXdoZXJlAA0OYXMtYmxvY2stZmlyc3QADgxhcy1ibG9jay1taWQADw1hcy1ibG9jay1sYXN0ABATYXMtYmxvY2stZXZlcnl3aGVyZQARDWFzLWxvb3AtZmlyc3QAEgthcy1sb29wLW1pZAATDGFzLWxvb3AtbGFzdAAUEmFzLWxvb3AtZXZlcnl3aGVyZQAVD2FzLWlmLWNvbmRpdGlvbgAWCmFzLWlmLXRoZW4AFwphcy1pZi1lbHNlABgLYXMtYnItZmlyc3QAGQphcy1ici1sYXN0ABoQYXMtYnItZXZlcnl3aGVyZQAbDmFzLWJyX2lmLWZpcnN0ABwMYXMtYnJfaWYtbWlkAB0NYXMtYnJfaWYtbGFzdAAeE2FzLWJyX2lmLWV2ZXJ5d2hlcmUAHxFhcy1icl90YWJsZS1maXJzdAAgD2FzLWJyX3RhYmxlLW1pZAAhEGFzLWJyX3RhYmxlLWxhc3QAIhZhcy1icl90YWJsZS1ldmVyeXdoZXJlACMPYXMtcmV0dXJuLWZpcnN0ACQOYXMtcmV0dXJuLWxhc3QAJRRhcy1yZXR1cm4tZXZlcnl3aGVyZQAmDWFzLWNhbGwtZmlyc3QAJwxhcy1jYWxsLW1pZDEAKAxhcy1jYWxsLW1pZDIAKQxhcy1jYWxsLWxhc3QAKhJhcy1jYWxsLWV2ZXJ5d2hlcmUAKw5hcy11bmFyeS1maXJzdAAsDWFzLXVuYXJ5LWxhc3QALRNhcy11bmFyeS1ldmVyeXdoZXJlAC4PYXMtYmluYXJ5LWZpcnN0AC8NYXMtYmluYXJ5LW1pZAAwDmFzLWJpbmFyeS1sYXN0ADEUYXMtYmluYXJ5LWV2ZXJ5d2hlcmUAMg1hcy10ZXN0LWZpcnN0ADMMYXMtdGVzdC1sYXN0ADQSYXMtdGVzdC1ldmVyeXdoZXJlADUQYXMtY29tcGFyZS1maXJzdAA2DmFzLWNvbXBhcmUtbWlkADcPYXMtY29tcGFyZS1sYXN0ADgVYXMtY29tcGFyZS1ldmVyeXdoZXJlADkUYXMtbWVtb3J5Lmdyb3ctZmlyc3QAOhNhcy1tZW1vcnkuZ3Jvdy1sYXN0ADsZYXMtbWVtb3J5Lmdyb3ctZXZlcnl3aGVyZQA8FmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAPhVhcy1jYWxsX2luZGlyZWN0LW1pZDEAPxVhcy1jYWxsX2luZGlyZWN0LW1pZDIAQBVhcy1jYWxsX2luZGlyZWN0LWxhc3QAQRthcy1jYWxsX2luZGlyZWN0LWV2ZXJ5d2hlcmUAQhJhcy1sb2NhbC5zZXQtZmlyc3QAQxFhcy1sb2NhbC5zZXQtbGFzdABEF2FzLWxvY2FsLnNldC1ldmVyeXdoZXJlAEUSYXMtbG9jYWwudGVlLWZpcnN0AEYRYXMtbG9jYWwudGVlLWxhc3QARxdhcy1sb2NhbC50ZWUtZXZlcnl3aGVyZQBIE2FzLWdsb2JhbC5zZXQtZmlyc3QASRJhcy1nbG9iYWwuc2V0LWxhc3QAShhhcy1nbG9iYWwuc2V0LWV2ZXJ5d2hlcmUASw1hcy1sb2FkLWZpcnN0AEwMYXMtbG9hZC1sYXN0AE0SYXMtbG9hZC1ldmVyeXdoZXJlAE4OYXMtc3RvcmUtZmlyc3QATwxhcy1zdG9yZS1taWQAUA1hcy1zdG9yZS1sYXN0AFETYXMtc3RvcmUtZXZlcnl3aGVyZQBSCQcBAEEACwE9CqIHUwIACwoAIAAgASACa2oLBQABQQELBwAQAAFBAgsHABAAQQMBCwsAAQEQAAFBBAEBCwYAASAAGgsGACAAARoLCQABASAAAQEaCwoAASAAIAAgABsLCgAgAAEgACAAGwsKACAAIAABIAAbCwoAIAAgACAAARsLEAABIAABASAAAQEgAAEBGwsIAAJ/AUECCwsKAAJ/EAABQQILCwsAAn8BEABBAwELCw4AAn8BARAAAUEEAQELCwgAA38BQQILCwoAA38QAAFBAgsLCgADfxAAQQMBCwsOAAN/AQEQAAFBBAEBCwsKACAAAQRAEAALCwsAIAAEQAEFEAALCwsAIAAEQBAABQELCwoAAn8BIAAMAAsLCgACfyAAAQwACwsNAAJ/AQEgAAEBDAALCwwAAn8BIAAgAA0ACwsMAAJ/IAABIAANAAsLDAACfyAAIAABDQALCxEAAn8BASAAAQEgAAEBDQALCw4AAn8BIAAgAA4BAAALCw4AAn8gAAEgAA4BAAALCw4AAn8gACAAAQ4BAAALCxMAAn8BASAAAQEgAAEBDgEAAAsLBgABIAAPCwYAIAABDwsJAAEBIAABAQ8LCwABIAAgASACEAELCwAgAAEgASACEAELCwAgACABASACEAELCwAgACABIAIBEAELEgABASAAAQEgAQEBIAIBARABCwYAASAAaAsGACAAAWgLCQABASAAAQFoCwgAASAAIABqCwgAIAABIABqCwgAIAAgAAFqCwwAASAAAQEgAAEBagsGAAEgAEULBgAgAAFFCwkAAQEgAAEBRQsIAAEgACAARwsIACAAASAARwsIACAAIAABSQsMAAEgAAEBIAABAUwLBwABIABAAAsHACAAAUAACwoAAQEgAAEBQAALBAAgAAsPAAJ/AUEBQQJBABEAAAsLDwACf0EBAUECQQARAAALCw8AAn9BAUECAUEAEQAACwsPAAJ/QQFBAkEAAREAAAsLFgACfwEBQQEBAUECAQFBAAEBEQAACwsJAAFBAiEAIAALCQBBAgEhACAACwwAAQFBAgEBIQAgAAsHAAFBAiIACwcAQQIBIgALCgABAUECAQEiAAsJAAFBAiQAIwALCQBBAgEkACMACwwAAQFBAgEBJAAjAAsIAAEgACgCAAsIACAAASgCAAsLAAEBIAABASgCAAsKAAEgACABNgIACwoAIAABIAE2AgALCgAgACABATYCAAsPAAEBIAABASABAQE2AgAL", + "AGFzbQEAAAABIQdgA39/fwF/YAAAYAABf2AAAX5gAAF9YAABfGACf38BfwM+PQEBAQEBAgMEBQEFAgIBAgEBAQICAgICAQICAwICAwIGBgYGAgACAgICAgICAgICBAMCAgICBAIDAgICAgIEBQFwAQEBBQMBAAEGBgF/AUEACwf4BzsIdHlwZS1pMzIAAQh0eXBlLWk2NAACCHR5cGUtZjMyAAMIdHlwZS1mNjQABA50eXBlLWkzMi12YWx1ZQAFDnR5cGUtaTY0LXZhbHVlAAYOdHlwZS1mMzItdmFsdWUABw50eXBlLWY2NC12YWx1ZQAIB251bGxhcnkACQV1bmFyeQAKDWFzLWZ1bmMtZmlyc3QACwthcy1mdW5jLW1pZAAMDGFzLWZ1bmMtbGFzdAANDWFzLWZ1bmMtdmFsdWUADg5hcy1ibG9jay1maXJzdAAPDGFzLWJsb2NrLW1pZAAQDWFzLWJsb2NrLWxhc3QAEQ5hcy1ibG9jay12YWx1ZQASDWFzLWxvb3AtZmlyc3QAEwthcy1sb29wLW1pZAAUDGFzLWxvb3AtbGFzdAAVC2FzLWJyLXZhbHVlABYNYXMtYnJfaWYtY29uZAAXDmFzLWJyX2lmLXZhbHVlABgTYXMtYnJfaWYtdmFsdWUtY29uZAAZEWFzLWJyX3RhYmxlLWluZGV4ABoRYXMtYnJfdGFibGUtdmFsdWUAGxdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAcD2FzLXJldHVybi12YWx1ZQAdCmFzLWlmLWNvbmQAHgphcy1pZi10aGVuAB8KYXMtaWYtZWxzZQAgD2FzLXNlbGVjdC1maXJzdAAhEGFzLXNlbGVjdC1zZWNvbmQAIg5hcy1zZWxlY3QtY29uZAAjDWFzLWNhbGwtZmlyc3QAJQthcy1jYWxsLW1pZAAmDGFzLWNhbGwtbGFzdAAnFWFzLWNhbGxfaW5kaXJlY3QtZnVuYwAoFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAKRRhcy1jYWxsX2luZGlyZWN0LW1pZAAqFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAArEmFzLWxvY2FsLnNldC12YWx1ZQAsEmFzLWxvY2FsLnRlZS12YWx1ZQAtE2FzLWdsb2JhbC5zZXQtdmFsdWUALg9hcy1sb2FkLWFkZHJlc3MALxBhcy1sb2FkTi1hZGRyZXNzADAQYXMtc3RvcmUtYWRkcmVzcwAxDmFzLXN0b3JlLXZhbHVlADIRYXMtc3RvcmVOLWFkZHJlc3MAMw9hcy1zdG9yZU4tdmFsdWUANBBhcy11bmFyeS1vcGVyYW5kADUOYXMtYmluYXJ5LWxlZnQANg9hcy1iaW5hcnktcmlnaHQANw9hcy10ZXN0LW9wZXJhbmQAOA9hcy1jb21wYXJlLWxlZnQAORBhcy1jb21wYXJlLXJpZ2h0ADoSYXMtY29udmVydC1vcGVyYW5kADsTYXMtbWVtb3J5Lmdyb3ctc2l6ZQA8CQcBAEEACwEkCqkFPQIACwUAD2gaCwUAD3oaCwUAD4waCwUAD5oaCwkAAn9BAQ9oCwsJAAJ+QgIPegsLDAACfUMAAEBAD4wLCxAAAnxEAAAAAAAAEEAPmgsLAwAPCwwARAAAAAAAAAhADwsHAEEBD0ECCwkAEABBAg9BAwsGAAEQAA8LCAABEABBAw8LCAACQA8QAAsLCgACQBAADxAACwsJAAJAARAADwsLCwACfwEQAEECDwsLCgADf0EDD0ECCwsMAAN/EABBBA9BAgsLCwADfwEQAEEFDwsLCgACf0EJDwwACwsIAAJADw0ACwsPAAJ/QQgPQQENABpBBwsLDwACf0EGQQkPDQAaQQcLCw8AAkBCCQ8OAgAAAAtCfwsRAAJ/QQoPQQEOAgAAAEEHCwsQAAJ/QQZBCw8OAQAAQQcLCwYAQgcPDwsNAEECDwR/QQAFQQELCw0AIAAEf0EDDwUgAQsLDQAgAAR/IAEFQQQPCwsKAEEFDyAAIAEbCwoAIABBBg8gARsLCgBBAEEBQQcPGwsEAEF/CwsAQQwPQQJBAxAkCwsAQQFBDQ9BAxAkCwsAQQFBAkEODxAkCw4AQRQPQQFBAkEDEQAACw4AQQBBFQ9BAkEDEQAACw4AQQBBAUEWD0EDEQAACw4AQQBBAUECQRcPEQAACwsBAX1BEQ8hAEF/CwkBAX9BAQ8iAAsHAEEBDyQACwsAQ5qZ2T8PKgIACwgAQh4PMAAACxMAQR4PRAAAAAAAABxAOQMAQX8LDABBAkEfDzcDAEF/CwwAQSAPQQc6AABBfwsMAEECQSEPPQEAQX8LCQBDmplZQA+MCwgAQQMPQQpqCwgAQgpCLQ99CwYAQSwPRQsPAEErD0QAAAAAAAAkQGULCwBDAAAgQUEqD1wLBgBBKQ+nCwcAQSgPQAAL", + "AGFzbQEAAAABPQpgAn9/AX9gAABgA39/fwF/YAN+fn8BfmADfX1/AX1gA3x8fwF8YANwcH8BcGADb29/AW9gAX8Bf2ABfwADLy4BAgMEBQIDBAUGBwgICAgICAkICAgICAgACAgICQkICAgICQgICAgICAgICAgIBAkCcAEBAXABAQEFAwEAAQYGAX8BQQoLB/AFKwpzZWxlY3QtaTMyAAEKc2VsZWN0LWk2NAACCnNlbGVjdC1mMzIAAwpzZWxlY3QtZjY0AAQMc2VsZWN0LWkzMi10AAUMc2VsZWN0LWk2NC10AAYMc2VsZWN0LWYzMi10AAcMc2VsZWN0LWY2NC10AAgOc2VsZWN0LWZ1bmNyZWYACRBzZWxlY3QtZXh0ZXJucmVmAAoPYXMtc2VsZWN0LWZpcnN0AAsNYXMtc2VsZWN0LW1pZAAMDmFzLXNlbGVjdC1sYXN0AA0NYXMtbG9vcC1maXJzdAAOC2FzLWxvb3AtbWlkAA8MYXMtbG9vcC1sYXN0ABAPYXMtaWYtY29uZGl0aW9uABEKYXMtaWYtdGhlbgASCmFzLWlmLWVsc2UAEw5hcy1icl9pZi1maXJzdAAUDWFzLWJyX2lmLWxhc3QAFRFhcy1icl90YWJsZS1maXJzdAAWEGFzLWJyX3RhYmxlLWxhc3QAFxZhcy1jYWxsX2luZGlyZWN0LWZpcnN0ABkUYXMtY2FsbF9pbmRpcmVjdC1taWQAGhVhcy1jYWxsX2luZGlyZWN0LWxhc3QAGw5hcy1zdG9yZS1maXJzdAAcDWFzLXN0b3JlLWxhc3QAHRRhcy1tZW1vcnkuZ3Jvdy12YWx1ZQAeDWFzLWNhbGwtdmFsdWUAIA9hcy1yZXR1cm4tdmFsdWUAIQ9hcy1kcm9wLW9wZXJhbmQAIgthcy1ici12YWx1ZQAjEmFzLWxvY2FsLnNldC12YWx1ZQAkEmFzLWxvY2FsLnRlZS12YWx1ZQAlE2FzLWdsb2JhbC5zZXQtdmFsdWUAJg9hcy1sb2FkLW9wZXJhbmQAJxBhcy11bmFyeS1vcGVyYW5kACgRYXMtYmluYXJ5LW9wZXJhbmQAKQ9hcy10ZXN0LW9wZXJhbmQAKg9hcy1jb21wYXJlLWxlZnQAKxBhcy1jb21wYXJlLXJpZ2h0ACwSYXMtY29udmVydC1vcGVyYW5kAC0JDwIAQQALAQACAUEACwABGAqABS4CAAsJACAAIAEgAhsLCQAgACABIAIbCwkAIAAgASACGwsJACAAIAEgAhsLCwAgACABIAIcAX8LCwAgACABIAIcAX4LCwAgACABIAIcAX0LCwAgACABIAIcAXwLCwAgACABIAIcAXALCwAgACABIAIcAW8LDgBBAEEBIAAbQQJBAxsLDgBBAkEAQQEgABtBAxsLDgBBAkEDQQBBASAAGxsLEAADf0ECQQMgABsQABAACwsQAAN/EABBAkEDIAAbEAALCxAAA38QABAAQQJBAyAAGwsLDgBBAkEDIAAbBEAQAAsLEQBBAQR/QQJBAyAAGwVBBAsLEQBBAAR/QQIFQQJBAyAAGwsLEAACf0ECQQMgABtBBA0ACwsQAAJ/QQJBAkEDIAAbDQALCxIAAn9BAkEDIAAbQQIOAQAACwsSAAJ/QQJBAkEDIAAbDgEAAAsLBAAgAAsTAAJ/QQJBAyAAG0EBQQARAAELCxMAAn9BAUECQQMgABtBABEAAQsLEwACf0EBQQRBAkEDIAAbEQABCwsOAEEAQQQgABtBATYCAAsOAEEIQQFBAiAAGzYCAAsLAEEBQQIgABtAAAsEACAACwsAQQFBAiAAGxAfCwoAQQFBAiAAGw8LCgBBAUECIAAbGgsOAAJ/QQFBAiAAGwwACwsPAQF/QQFBAiAAGyEAIAALCwBBAUECIAAbIgALDQBBAUECIAAbJAAjAAsMAEEAQQQgABsoAgALCgBBAEEBIAAbRQsRAEEBQQIgABtBAUECIAAbbAsNAAJ/QQBBASAAG0ULCw8AAn9BAUECIAAbQQFMCwsPAAJ/QQFBAEEBIAAbRwsLDQACf0IBQgAgABunCws=", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAAEBAFwAAEKUAsEAAAbCwUAABsBCwUAABsbCwUAABsbCwYAABsbGwsGAAAcAX8LBgAAHAF/CwcAABwBfxsLCQAAHAF/HAF/CwcAABsRAQALCAAAGxEBABsL", + "AGFzbQEAAAABDQNgAX4BfmAAAX9gAAADCQgAAAAAAAECAQYGAX8BQQALB1cGCGZhYy1leHByAAAJZmFjLXN0YWNrAAENZmFjLXN0YWNrLXJhdwACCWZhYy1taXhlZAADDWZhYy1taXhlZC1yYXcABBBub3QtcXVpdGUtYS10cmVlAAcKjwIILwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILLwECfiAAIQFCASECAkADQCABQgBRBEAMAgUgASACfiECIAFCAX0hAQsMAAsLIAILDQEBf0EBIwBqJAAjAAsFABAFGgsJABAFEAUQBmoL", + "AGFzbQEAAAABDANgAABgAX8AYAABfwMCAQAEBAFwAAEKtAMBsQMAAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBAEEAEQEACwNAQQBBABEBAAtBAARAQQBBABEBAAtBAARAQQBBABEBAAVBAEEAEQEACwJ/QQARAgALGgN/QQARAgALGkEABH9BABECAAVBABECAAsaAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALAkBBAEEAEQEACwNAQQBBABEBAAtBAARAQQBBABEBAAtBAARAQQBBABEBAAVBAEEAEQEACwJ/QQARAgALGgN/QQARAgALGkEABH9BABECAAVBABECAAsaAkBBABEAAAsDQEEAEQAAC0EABEBBABEAAAtBAARAQQARAAAFQQARAAALQQARAAAL", + "AGFzbQEAAAABDwNgAX8Bf2ABfgF+YAABfwMFBAABAAIHHgQEc3RtdAAABGV4cHIAAQNhcmcAAgZjb3JuZXIAAwreAQRXAQF/QeQAIQECQAJAAkACQAJAAkACQAJAAkACQCAADggAAQIDBAUGCAcLIAAPCwELC0EAIABrIQEMBQsMBAtB5QAhAQwDC0HlACEBC0HmACEBCwsgAQ8LTAEBfkLkACEBAn4CQAJAAkACQAJAAkACQAJAAkAgAKcOCAABAgMGBQQIBwsgAA8LAQsLQgAgAH0MBQtC5QAhAQsLCyABDAELQnsLDwsqAAJ/QQoCf0HkAAJ/QegHAn9BAiAAbEEDIABxDgMBAgMAC2oLagtqCw8LDAACQEEADgAAC0EBCw==", + "AGFzbQEAAAABIAdgA39/fwBgAABgAAF/YAABfmAAAX1gAAF8YAJ/fwF/Az08AQACAwQFAgIBAgICAQICAgIBAgIBAgIBAgICAgMCBgYGBgYCAQEBAQEBAQEEBAQDAQEBAQQCAwICAgICBAUBcAEBAQUDAQABBgkBfQFDAAAAAAsHjAg6CHR5cGUtaTMyAAIIdHlwZS1pNjQAAwh0eXBlLWYzMgAECHR5cGUtZjY0AAUNYXMtZnVuYy1maXJzdAAGC2FzLWZ1bmMtbWlkAAcMYXMtZnVuYy1sYXN0AAgNYXMtZnVuYy12YWx1ZQAJDmFzLWJsb2NrLWZpcnN0AAoMYXMtYmxvY2stbWlkAAsNYXMtYmxvY2stbGFzdAAMDmFzLWJsb2NrLXZhbHVlAA0OYXMtYmxvY2stYnJva2UADg1hcy1sb29wLWZpcnN0AA8LYXMtbG9vcC1taWQAEAxhcy1sb29wLWxhc3QAEQ1hcy1sb29wLWJyb2tlABILYXMtYnItdmFsdWUAEw1hcy1icl9pZi1jb25kABQOYXMtYnJfaWYtdmFsdWUAFRNhcy1icl9pZi12YWx1ZS1jb25kABYRYXMtYnJfdGFibGUtaW5kZXgAFxFhcy1icl90YWJsZS12YWx1ZQAYE2FzLWJyX3RhYmxlLXZhbHVlLTIAGRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAaG2FzLWJyX3RhYmxlLXZhbHVlLWFuZC1pbmRleAAbD2FzLXJldHVybi12YWx1ZQAcCmFzLWlmLWNvbmQAHQphcy1pZi10aGVuAB4KYXMtaWYtZWxzZQAfEmFzLWlmLXRoZW4tbm8tZWxzZQAgD2FzLXNlbGVjdC1maXJzdAAhEGFzLXNlbGVjdC1zZWNvbmQAIg5hcy1zZWxlY3QtY29uZAAjDWFzLWNhbGwtZmlyc3QAJAthcy1jYWxsLW1pZAAlDGFzLWNhbGwtbGFzdAAmFWFzLWNhbGxfaW5kaXJlY3QtZnVuYwAnFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAKBRhcy1jYWxsX2luZGlyZWN0LW1pZAApFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAqEmFzLWxvY2FsLnNldC12YWx1ZQArEmFzLWxvY2FsLnRlZS12YWx1ZQAsE2FzLWdsb2JhbC5zZXQtdmFsdWUALQ9hcy1sb2FkLWFkZHJlc3MALhBhcy1sb2FkTi1hZGRyZXNzAC8QYXMtc3RvcmUtYWRkcmVzcwAwDmFzLXN0b3JlLXZhbHVlADERYXMtc3RvcmVOLWFkZHJlc3MAMg9hcy1zdG9yZU4tdmFsdWUAMxBhcy11bmFyeS1vcGVyYW5kADQOYXMtYmluYXJ5LWxlZnQANQ9hcy1iaW5hcnktcmlnaHQANg9hcy10ZXN0LW9wZXJhbmQANw9hcy1jb21wYXJlLWxlZnQAOBBhcy1jb21wYXJlLXJpZ2h0ADkSYXMtY29udmVydC1vcGVyYW5kADoTYXMtbWVtb3J5Lmdyb3ctc2l6ZQA7CQcBAEEACwEBCrUEPAIACwIACwMAAAsDAAALAwAACwMAAAsFAABBfwsHABAAAEF/CwUAEAAACwUAEAAACwgAAn8AQQILCwoAAn8QAABBAgsLCQACQAEQAAALCwkAAn8BEAAACwsMAAJ/EABBAQwAAAsLCAADfwBBAgsLCgADfxAAAEECCwsJAANAARAAAAsLDwACfwN/EABBAQwBAAsLCwgAAn8ADAALCwgAAkAADQALCw0AAn8AQQENABpBBwsLDQACf0EGAA0AGkEHCwsLAAJAAA4CAAAACwsPAAJ/AEEBDgIAAABBBwsLDwACfwJ/AEEBDgEAAQsLCw4AAn9BBgAOAQAAQQcLCwwAAn8ADgEAAEEICwsEAAAPCwsAAAR/QQAFQQELCwsAIAAEfwAFIAELCwsAIAAEfyABBQALCwoAIAAEQAALIAELCAAAIAAgARsLCAAgAAAgARsLCABBAEEBABsLCQAAQQJBAxABCwkAQQEAQQMQAQsJAEEBQQIAEAELDAAAQQFBAkEDEQAACwwAQQAAQQJBAxEAAAsMAEEAQQEAQQMRAAALDABBAEEBQQIAEQAACwcBAX0AIQALBwEBfQAiAAsFAAAkAAsGAAAqAgALBgAAMAAACw8AAEQAAAAAAAAcQDkDAAsIAEECADcDAAsIAABBBzoAAAsIAEECAD0BAAsEAACMCwYAAEEKagsGAEIKAH0LBAAARQsNAABEAAAAAAAAJEBlCwkAQwAAIEEAXAsEAACnCwUAAEAACw==", + "AGFzbQEAAAABCAJgAABgAAF/AzIxAAABAAEAAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQeUCjEaZnVuYy11bndpbmQtYnktdW5yZWFjaGFibGUAABFmdW5jLXVud2luZC1ieS1icgABF2Z1bmMtdW53aW5kLWJ5LWJyLXZhbHVlAAIUZnVuYy11bndpbmQtYnktYnJfaWYAAxpmdW5jLXVud2luZC1ieS1icl9pZi12YWx1ZQAEF2Z1bmMtdW53aW5kLWJ5LWJyX3RhYmxlAAUdZnVuYy11bndpbmQtYnktYnJfdGFibGUtdmFsdWUABhVmdW5jLXVud2luZC1ieS1yZXR1cm4ABxtibG9jay11bndpbmQtYnktdW5yZWFjaGFibGUACBJibG9jay11bndpbmQtYnktYnIACRhibG9jay11bndpbmQtYnktYnItdmFsdWUAChVibG9jay11bndpbmQtYnktYnJfaWYACxtibG9jay11bndpbmQtYnktYnJfaWYtdmFsdWUADBhibG9jay11bndpbmQtYnktYnJfdGFibGUADR5ibG9jay11bndpbmQtYnktYnJfdGFibGUtdmFsdWUADhZibG9jay11bndpbmQtYnktcmV0dXJuAA8iYmxvY2stbmVzdGVkLXVud2luZC1ieS11bnJlYWNoYWJsZQAQGWJsb2NrLW5lc3RlZC11bndpbmQtYnktYnIAER9ibG9jay1uZXN0ZWQtdW53aW5kLWJ5LWJyLXZhbHVlABIcYmxvY2stbmVzdGVkLXVud2luZC1ieS1icl9pZgATImJsb2NrLW5lc3RlZC11bndpbmQtYnktYnJfaWYtdmFsdWUAFB9ibG9jay1uZXN0ZWQtdW53aW5kLWJ5LWJyX3RhYmxlABUlYmxvY2stbmVzdGVkLXVud2luZC1ieS1icl90YWJsZS12YWx1ZQAWHWJsb2NrLW5lc3RlZC11bndpbmQtYnktcmV0dXJuABcXdW5hcnktYWZ0ZXItdW5yZWFjaGFibGUAGA51bmFyeS1hZnRlci1icgAZEXVuYXJ5LWFmdGVyLWJyX2lmABoUdW5hcnktYWZ0ZXItYnJfdGFibGUAGxJ1bmFyeS1hZnRlci1yZXR1cm4AHBhiaW5hcnktYWZ0ZXItdW5yZWFjaGFibGUAHQ9iaW5hcnktYWZ0ZXItYnIAHhJiaW5hcnktYWZ0ZXItYnJfaWYAHxViaW5hcnktYWZ0ZXItYnJfdGFibGUAIBNiaW5hcnktYWZ0ZXItcmV0dXJuACEYc2VsZWN0LWFmdGVyLXVucmVhY2hhYmxlACIPc2VsZWN0LWFmdGVyLWJyACMSc2VsZWN0LWFmdGVyLWJyX2lmACQVc2VsZWN0LWFmdGVyLWJyX3RhYmxlACUTc2VsZWN0LWFmdGVyLXJldHVybgAmHWJsb2NrLXZhbHVlLWFmdGVyLXVucmVhY2hhYmxlACcUYmxvY2stdmFsdWUtYWZ0ZXItYnIAKBdibG9jay12YWx1ZS1hZnRlci1icl9pZgApGmJsb2NrLXZhbHVlLWFmdGVyLWJyX3RhYmxlACoYYmxvY2stdmFsdWUtYWZ0ZXItcmV0dXJuACscbG9vcC12YWx1ZS1hZnRlci11bnJlYWNoYWJsZQAsE2xvb3AtdmFsdWUtYWZ0ZXItYnIALRZsb29wLXZhbHVlLWFmdGVyLWJyX2lmAC4ZbG9vcC12YWx1ZS1hZnRlci1icl90YWJsZQAvF2xvb3AtdmFsdWUtYWZ0ZXItcmV0dXJuADAKvAYxBwBBA0IBAAsIAEEDQgEMAAsKAEEDQgFBCQwACwwAQQNCAUEBDQAaGgsOAEEDQgFBCUEBDQAaGgsLAEEDQgFBAA4AAAsNAEEDQgFBCUEADgAACwkAQQNCAUEJDwsKAAJAQQNCAQALCw0AAkBBA0IBDAALQQkLDQACf0EDQgFBCQwACwsRAAJAQQNCAUEBDQAaGgtBCQsRAAJ/QQNCAUEJQQENABoaCwsQAAJAQQNCAUEADgAAC0EJCxAAAn9BA0IBQQlBAA4AAAsLDAACf0EDQgFBCQ8LCw0AAn9BAwJAQgEACwsLEQACQEEDAkBCAQwBCxoLQQkLEAACf0EDAkBCAUEJDAELCwsUAAJAQQMCQEIBQQENARoLGgtBCQsUAAJ/QQMCQEIBQQlBAQ0BGhoLCwsUAAJAQQMCQEIBQQEOAAELGgtBCQsTAAJ/QQMCQEIBQQlBAQ4AAQsLCw8AAn9BAwJAQgFBCQ8LCwsJAEMAAAAAAFALDwACf0MAAAAAQQkMAFALCw8AAn9CAEEJQQENABpQCwsTAAJ/QwAAAABBCUEADgEAAFALCwsAQwAAAABBCQ9QCxIAQwAAAABEAAAAAAAA8D8AUQsYAAJ/QwAAAABEAAAAAAAA8D9BCQwAUQsLEQACf0IAQgFBCUEBDQAaUQsLGwACf0MAAAAARAAAAAAAAPA/QQlBAA4AAFELCxQAQwAAAABEAAAAAAAA8D9BCQ9RCxQAQwAAAABEAAAAAAAA8D9CAAAbCxoAAn9DAAAAAEQAAAAAAADwP0IAQQkMABsLCxMAAn9BAEEBQQBBCUEBDQAaGwsLHQACf0MAAAAARAAAAAAAAPA/QgBBCUEADgAAGwsLFgBDAAAAAEQAAAAAAADwP0IBQQkPGwsLAAJ/QwAAAAAACwsOAAJ/QwAAAABBCQwACwsOAAJ/QQBBCUEBDQAaCwsSAAJ/QwAAAABBCUEADgEAAAsLDQACf0MAAAAAQQkPCwsLAAN/QwAAAAAACwsRAAJ/A39DAAAAAEEJDAELCwsRAAJ/A39BAEEJQQENARoLCwsVAAJ/A39DAAAAAEEJQQAOAQEBCwsLDQADf0MAAAAAQQkPCws=", + "AGFzbQEAAAABQA1gAn9/AX9gAn5+AX9gAn19AX9gAnx8AX9gAABgAAF/YAABfmAAAX1gAAF8YAJ/fwBgAn5+AGACfX0AYAJ8fAADhAGCAQAAAQECAgMDBAQFBQUFBQUGBgYFBQcHBwUFCAgIBQUJCgsMBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEBQFwAQgIBQMBAAEHqghfB2kzMl9hZGQAIwdpMzJfc3ViACQHaTMyX211bAAlCWkzMl9kaXZfcwAmCWkzMl9kaXZfdQAnCWkzMl9yZW1fcwAoCWkzMl9yZW1fdQApB2kzMl9hbmQAKgZpMzJfb3IAKwdpMzJfeG9yACwHaTMyX3NobAAtCWkzMl9zaHJfdQAuCWkzMl9zaHJfcwAvBmkzMl9lcQAwBmkzMl9uZQAxCGkzMl9sdF9zADIIaTMyX2xlX3MAMwhpMzJfbHRfdQA0CGkzMl9sZV91ADUIaTMyX2d0X3MANghpMzJfZ2VfcwA3CGkzMl9ndF91ADgIaTMyX2dlX3UAOQlpMzJfc3RvcmUAOgppMzJfc3RvcmU4ADsLaTMyX3N0b3JlMTYAPAhpMzJfY2FsbAA9EWkzMl9jYWxsX2luZGlyZWN0AD4KaTMyX3NlbGVjdAA/B2k2NF9hZGQAQAdpNjRfc3ViAEEHaTY0X211bABCCWk2NF9kaXZfcwBDCWk2NF9kaXZfdQBECWk2NF9yZW1fcwBFCWk2NF9yZW1fdQBGB2k2NF9hbmQARwZpNjRfb3IASAdpNjRfeG9yAEkHaTY0X3NobABKCWk2NF9zaHJfdQBLCWk2NF9zaHJfcwBMBmk2NF9lcQBNBmk2NF9uZQBOCGk2NF9sdF9zAE8IaTY0X2xlX3MAUAhpNjRfbHRfdQBRCGk2NF9sZV91AFIIaTY0X2d0X3MAUwhpNjRfZ2VfcwBUCGk2NF9ndF91AFUIaTY0X2dlX3UAVglpNjRfc3RvcmUAVwppNjRfc3RvcmU4AFgLaTY0X3N0b3JlMTYAWQtpNjRfc3RvcmUzMgBaCGk2NF9jYWxsAFsRaTY0X2NhbGxfaW5kaXJlY3QAXAppNjRfc2VsZWN0AF0HZjMyX2FkZABeB2YzMl9zdWIAXwdmMzJfbXVsAGAHZjMyX2RpdgBhDGYzMl9jb3B5c2lnbgBiBmYzMl9lcQBjBmYzMl9uZQBkBmYzMl9sdABlBmYzMl9sZQBmBmYzMl9ndABnBmYzMl9nZQBoB2YzMl9taW4AaQdmMzJfbWF4AGoJZjMyX3N0b3JlAGsIZjMyX2NhbGwAbBFmMzJfY2FsbF9pbmRpcmVjdABtCmYzMl9zZWxlY3QAbgdmNjRfYWRkAG8HZjY0X3N1YgBwB2Y2NF9tdWwAcQdmNjRfZGl2AHIMZjY0X2NvcHlzaWduAHMGZjY0X2VxAHQGZjY0X25lAHUGZjY0X2x0AHYGZjY0X2xlAHcGZjY0X2d0AHgGZjY0X2dlAHkHZjY0X21pbgB6B2Y2NF9tYXgAewlmNjRfc3RvcmUAfAhmNjRfY2FsbAB9EWY2NF9jYWxsX2luZGlyZWN0AH4KZjY0X3NlbGVjdAB/BWJyX2lmAIABCGJyX3RhYmxlAIEBCQ4BAEEACwgAAQIDBAUGBwqqDYIBBABBfwsEAEF+CwQAQX8LBABBfgsEAEF/CwQAQX4LBABBfwsEAEF+CwkAQQhBADYCAAsnAEELQQotAAA6AABBCkEJLQAAOgAAQQlBCC0AADoAAEEIQX06AAALBwBBCCgCAAsNABAJQQhBAToAAEEACw0AEAlBCEECOgAAQQELDQAQCUEIQQM6AABBAQsNABAJQQhBBDoAAEEACw0AEAlBCEEFOgAAQQALDQAQCUEIQQE6AABCAAsNABAJQQhBAjoAAEIBCw0AEAlBCEEDOgAAQgELDQAQCUEIQQQ6AABBAgsNABAJQQhBBToAAEEACxAAEAlBCEEBOgAAQwAAAAALEAAQCUEIQQI6AABDAACAPwsQABAJQQhBAzoAAEMAAIA/Cw0AEAlBCEEEOgAAQQQLDQAQCUEIQQU6AABBAAsUABAJQQhBAToAAEQAAAAAAAAAAAsUABAJQQhBAjoAAEQAAAAAAADwPwsUABAJQQhBAzoAAEQAAAAAAADwPwsNABAJQQhBBDoAAEEGCw0AEAlBCEEFOgAAQQALAgALAgALAgALAgALDAAQCBALEAxqGhAKCwwAEAgQCxAMaxoQCgsMABAIEAsQDGwaEAoLDAAQCBALEAxtGhAKCwwAEAgQCxAMbhoQCgsMABAIEAsQDG8aEAoLDAAQCBALEAxwGhAKCwwAEAgQCxAMcRoQCgsMABAIEAsQDHIaEAoLDAAQCBALEAxzGhAKCwwAEAgQCxAMdBoQCgsMABAIEAsQDHYaEAoLDAAQCBALEAx1GhAKCwwAEAgQCxAMRhoQCgsMABAIEAsQDEcaEAoLDAAQCBALEAxIGhAKCwwAEAgQCxAMTBoQCgsMABAIEAsQDEkaEAoLDAAQCBALEAxNGhAKCwwAEAgQCxAMShoQCgsMABAIEAsQDE4aEAoLDAAQCBALEAxLGhAKCwwAEAgQCxAMTxoQCgsNABAIEAsQDDYCABAKCw0AEAgQCxAMOgAAEAoLDQAQCBALEAw7AQAQCgsMABAIEAsQDBAfEAoLEAAQCBALEAwQDhEAABoQCgsOABAIEAsQDBAPGxoQCgsMABAIEBAQEXwaEAoLDAAQCBAQEBF9GhAKCwwAEAgQEBARfhoQCgsMABAIEBAQEX8aEAoLDAAQCBAQEBGAGhAKCwwAEAgQEBARgRoQCgsMABAIEBAQEYIaEAoLDAAQCBAQEBGDGhAKCwwAEAgQEBARhBoQCgsMABAIEBAQEYUaEAoLDAAQCBAQEBGGGhAKCwwAEAgQEBARiBoQCgsMABAIEBAQEYcaEAoLDAAQCBAQEBFRGhAKCwwAEAgQEBARUhoQCgsMABAIEBAQEVMaEAoLDAAQCBAQEBFXGhAKCwwAEAgQEBARVBoQCgsMABAIEBAQEVgaEAoLDAAQCBAQEBFVGhAKCwwAEAgQEBARWRoQCgsMABAIEBAQEVYaEAoLDAAQCBAQEBFaGhAKCw0AEAgQCxARNwMAEAoLDQAQCBALEBE8AAAQCgsNABAIEAsQET0BABAKCw0AEAgQCxARPgIAEAoLDAAQCBAQEBEQIBAKCxAAEAgQEBAREBMRAQAaEAoLDgAQCBAQEBEQFBsaEAoLDAAQCBAVEBaSGhAKCwwAEAgQFRAWkxoQCgsMABAIEBUQFpQaEAoLDAAQCBAVEBaVGhAKCwwAEAgQFRAWmBoQCgsMABAIEBUQFlsaEAoLDAAQCBAVEBZcGhAKCwwAEAgQFRAWXRoQCgsMABAIEBUQFl8aEAoLDAAQCBAVEBZeGhAKCwwAEAgQFRAWYBoQCgsMABAIEBUQFpYaEAoLDAAQCBAVEBaXGhAKCw0AEAgQCxAWOAIAEAoLDAAQCBAVEBYQIRAKCxAAEAgQFRAWEBgRAgAaEAoLDgAQCBAVEBYQGRsaEAoLDAAQCBAaEBugGhAKCwwAEAgQGhAboRoQCgsMABAIEBoQG6IaEAoLDAAQCBAaEBujGhAKCwwAEAgQGhAbphoQCgsMABAIEBoQG2EaEAoLDAAQCBAaEBtiGhAKCwwAEAgQGhAbYxoQCgsMABAIEBoQG2UaEAoLDAAQCBAaEBtkGhAKCwwAEAgQGhAbZhoQCgsMABAIEBoQG6QaEAoLDAAQCBAaEBulGhAKCw0AEAgQCxAbOQMAEAoLDAAQCBAaEBsQIhAKCxAAEAgQGhAbEB0RAwAaEAoLDgAQCBAaEBsQHhsaEAoLEwACfxAIEAsQDEEAcQ0AGhAKCwsVAAJ/EAgCfxALEAwOAQEACxoQCgsL", + "AGFzbQEAAAABNw1gAn9/AX9gAAF/YAABfmAAAW9gAX8AYAF+AGABbwBgAAF9YAABfGABfQBgAXwAYAAAYAF/AX8CLwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdApnbG9iYWxfaTY0A34AAzMyAQIDAwECAQIEBQYHCAcICQoLAQEBAQEBAQEBAQEBAQABAQELCwEBDAEBCwEMDAEBAQEEBQFwAQEBBQMBAAEGVg1/AEF+C30AQwAAQMALfABEAAAAAAAAEMALfgBCewt/AUF0C30BQwAAUMELfAFEAAAAAAAALMALfgFCcQt/ACMAC34AIwELbwDQbwtvAdBvC3AA0HALB7EFLwVnZXQtYQAABWdldC1iAAEFZ2V0LXIAAgZnZXQtbXIAAwVnZXQteAAEBWdldC15AAUGZ2V0LXoxAAYGZ2V0LXoyAAcFc2V0LXgACAVzZXQteQAJBnNldC1tcgAKBWdldC0zAAsFZ2V0LTQADAVnZXQtNwANBWdldC04AA4Fc2V0LTcADwVzZXQtOAAQD2FzLXNlbGVjdC1maXJzdAASDWFzLXNlbGVjdC1taWQAEw5hcy1zZWxlY3QtbGFzdAAUDWFzLWxvb3AtZmlyc3QAFQthcy1sb29wLW1pZAAWDGFzLWxvb3AtbGFzdAAXD2FzLWlmLWNvbmRpdGlvbgAYCmFzLWlmLXRoZW4AGQphcy1pZi1lbHNlABoOYXMtYnJfaWYtZmlyc3QAGw1hcy1icl9pZi1sYXN0ABwRYXMtYnJfdGFibGUtZmlyc3QAHRBhcy1icl90YWJsZS1sYXN0AB4WYXMtY2FsbF9pbmRpcmVjdC1maXJzdAAgFGFzLWNhbGxfaW5kaXJlY3QtbWlkACEVYXMtY2FsbF9pbmRpcmVjdC1sYXN0ACIOYXMtc3RvcmUtZmlyc3QAIw1hcy1zdG9yZS1sYXN0ACQPYXMtbG9hZC1vcGVyYW5kACUUYXMtbWVtb3J5Lmdyb3ctdmFsdWUAJg1hcy1jYWxsLXZhbHVlACgPYXMtcmV0dXJuLXZhbHVlACkPYXMtZHJvcC1vcGVyYW5kACoLYXMtYnItdmFsdWUAKxJhcy1sb2NhbC5zZXQtdmFsdWUALBJhcy1sb2NhbC50ZWUtdmFsdWUALRNhcy1nbG9iYWwuc2V0LXZhbHVlAC4QYXMtdW5hcnktb3BlcmFuZAAvEWFzLWJpbmFyeS1vcGVyYW5kADASYXMtY29tcGFyZS1vcGVyYW5kADEJBwEAQQALAR8KsAMyBAAjAgsEACMFCwQAIwwLBAAjDQsEACMGCwQAIwkLBAAjCgsEACMLCwYAIAAkBgsGACAAJAkLBgAgACQNCwQAIwMLBAAjBAsEACMHCwQAIwgLBgAgACQHCwYAIAAkCAsCAAsJACMGQQJBAxsLCQBBAiMGQQMbCwkAQQJBAyMGGwsLAAN/IwYQERARCwsLAAN/EBEjBhARCwsLAAN/EBEQESMGCwsQACMGBH8QEUECBRARQQMLCwwAQQEEfyMGBUECCwsMAEEABH9BAgUjBgsLDgACfyMGQQINAEEDDwsLDgACf0ECIwYNAEEDDwsLDQACfyMGQQIOAQAACwsNAAJ/QQIjBg4BAAALCwQAIAALDgACfyMGQQJBABEAAAsLDgACf0ECIwZBABEAAAsLDgACf0ECQQAjBhEAAAsLCQAjBkEBNgIACwkAQQAjBjYCAAsHACMGKAIACwYAIwZAAAsEACAACwYAIwYQJwsFACMGDwsFACMGGgsJAAJ/IwYMAAsLCAAjBiEAIAALBgAjBiIACwgAIwYkBiMGCwUAIwZFCwcAIwYjBmwLBwAjAEEBSws=", + "AGFzbQEAAAAGCQF9AUMAAAAACwcFAQFhAwA=", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AA==", + "AGFzbQEAAAAGBgF/AEEACw==", + "AGFzbQEAAAABCAJgAABgAAF/AwQDAAEABQQBAQEBBw0CA2luYwAAA2dldAABCAECCiMDDwBBAEEALQAAQQFqOgAACwgAQQAtAAAPCwgAEAAQABAACwsHAQBBAAsBQQ==", + "AGFzbQEAAAABCAJgAX8AYAAAAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQgBAQoIAQYAQQEQAAs=", + "AGFzbQEAAAABCAJgAX8AYAAAAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQgBAQoIAQYAQQIQAAs=", + "AGFzbQEAAAABBAFgAAACEgEIc3BlY3Rlc3QFcHJpbnQAAAgBAA==", + "AGFzbQEAAAABBAFgAAADAgEACAEACgUBAwAACw==", +]; +export const script = "[[\"file\",\"block.wast\"],[\"module\",3,0],[\"invoke\",353,0,\"empty\",[]],[\"invoke\",354,0,\"singular\",[]],[\"invoke\",355,0,\"multi\",[]],[\"invoke\",356,0,\"nested\",[]],[\"invoke\",357,0,\"deep\",[]],[\"invoke\",359,0,\"as-select-first\",[]],[\"invoke\",360,0,\"as-select-mid\",[]],[\"invoke\",361,0,\"as-select-last\",[]],[\"invoke\",363,0,\"as-loop-first\",[]],[\"invoke\",364,0,\"as-loop-mid\",[]],[\"invoke\",365,0,\"as-loop-last\",[]],[\"invoke\",367,0,\"as-if-condition\",[]],[\"invoke\",368,0,\"as-if-then\",[]],[\"invoke\",369,0,\"as-if-else\",[]],[\"invoke\",371,0,\"as-br_if-first\",[]],[\"invoke\",372,0,\"as-br_if-last\",[]],[\"invoke\",374,0,\"as-br_table-first\",[]],[\"invoke\",375,0,\"as-br_table-last\",[]],[\"invoke\",377,0,\"as-call_indirect-first\",[]],[\"invoke\",378,0,\"as-call_indirect-mid\",[]],[\"invoke\",379,0,\"as-call_indirect-last\",[]],[\"invoke\",381,0,\"as-store-first\",[]],[\"invoke\",382,0,\"as-store-last\",[]],[\"invoke\",384,0,\"as-memory.grow-value\",[]],[\"invoke\",385,0,\"as-call-value\",[]],[\"invoke\",386,0,\"as-return-value\",[]],[\"invoke\",387,0,\"as-drop-operand\",[]],[\"invoke\",388,0,\"as-br-value\",[]],[\"invoke\",389,0,\"as-local.set-value\",[]],[\"invoke\",390,0,\"as-local.tee-value\",[]],[\"invoke\",391,0,\"as-global.set-value\",[]],[\"invoke\",392,0,\"as-load-operand\",[]],[\"invoke\",394,0,\"as-unary-operand\",[]],[\"invoke\",395,0,\"as-binary-operand\",[]],[\"invoke\",396,0,\"as-test-operand\",[]],[\"invoke\",397,0,\"as-compare-operand\",[]],[\"invoke\",398,0,\"as-binary-operands\",[]],[\"invoke\",399,0,\"as-compare-operands\",[]],[\"invoke\",400,0,\"as-mixed-operands\",[]],[\"invoke\",402,0,\"break-bare\",[]],[\"invoke\",403,0,\"break-value\",[]],[\"invoke\",404,0,\"break-multi-value\",[]],[\"invoke\",407,0,\"break-repeated\",[]],[\"invoke\",408,0,\"break-inner\",[]],[\"invoke\",410,0,\"param\",[]],[\"invoke\",411,0,\"params\",[]],[\"invoke\",412,0,\"params-id\",[]],[\"invoke\",413,0,\"param-break\",[]],[\"invoke\",414,0,\"params-break\",[]],[\"invoke\",415,0,\"params-id-break\",[]],[\"invoke\",417,0,\"effects\",[]],[\"invoke\",419,0,\"type-use\",[]],[\"file\",\"br.wast\"],[\"module\",3,1],[\"invoke\",372,0,\"type-i32\",[]],[\"invoke\",373,0,\"type-i64\",[]],[\"invoke\",374,0,\"type-f32\",[]],[\"invoke\",375,0,\"type-f64\",[]],[\"invoke\",376,0,\"type-i32-i32\",[]],[\"invoke\",377,0,\"type-i64-i64\",[]],[\"invoke\",378,0,\"type-f32-f32\",[]],[\"invoke\",379,0,\"type-f64-f64\",[]],[\"invoke\",381,0,\"type-i32-value\",[]],[\"invoke\",382,0,\"type-i64-value\",[]],[\"invoke\",383,0,\"type-f32-value\",[]],[\"invoke\",384,0,\"type-f64-value\",[]],[\"invoke\",385,0,\"type-f64-f64-value\",[]],[\"invoke\",387,0,\"as-block-first\",[]],[\"invoke\",388,0,\"as-block-mid\",[]],[\"invoke\",389,0,\"as-block-last\",[]],[\"invoke\",390,0,\"as-block-value\",[]],[\"invoke\",392,0,\"as-loop-first\",[]],[\"invoke\",393,0,\"as-loop-mid\",[]],[\"invoke\",394,0,\"as-loop-last\",[]],[\"invoke\",396,0,\"as-br-value\",[]],[\"invoke\",398,0,\"as-br_if-cond\",[]],[\"invoke\",399,0,\"as-br_if-value\",[]],[\"invoke\",400,0,\"as-br_if-value-cond\",[]],[\"invoke\",402,0,\"as-br_table-index\",[]],[\"invoke\",403,0,\"as-br_table-value\",[]],[\"invoke\",404,0,\"as-br_table-value-index\",[]],[\"invoke\",406,0,\"as-return-value\",[]],[\"invoke\",407,0,\"as-return-values\",[]],[\"invoke\",409,0,\"as-if-cond\",[]],[\"invoke\",410,0,\"as-if-then\",[\"i32:1\",\"i32:6\"]],[\"invoke\",411,0,\"as-if-then\",[\"i32:0\",\"i32:6\"]],[\"invoke\",412,0,\"as-if-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",413,0,\"as-if-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",415,0,\"as-select-first\",[\"i32:0\",\"i32:6\"]],[\"invoke\",416,0,\"as-select-first\",[\"i32:1\",\"i32:6\"]],[\"invoke\",417,0,\"as-select-second\",[\"i32:0\",\"i32:6\"]],[\"invoke\",418,0,\"as-select-second\",[\"i32:1\",\"i32:6\"]],[\"invoke\",419,0,\"as-select-cond\",[]],[\"invoke\",420,0,\"as-select-all\",[]],[\"invoke\",422,0,\"as-call-first\",[]],[\"invoke\",423,0,\"as-call-mid\",[]],[\"invoke\",424,0,\"as-call-last\",[]],[\"invoke\",425,0,\"as-call-all\",[]],[\"invoke\",427,0,\"as-call_indirect-func\",[]],[\"invoke\",428,0,\"as-call_indirect-first\",[]],[\"invoke\",429,0,\"as-call_indirect-mid\",[]],[\"invoke\",430,0,\"as-call_indirect-last\",[]],[\"invoke\",431,0,\"as-call_indirect-all\",[]],[\"invoke\",433,0,\"as-local.set-value\",[]],[\"invoke\",434,0,\"as-local.tee-value\",[]],[\"invoke\",435,0,\"as-global.set-value\",[]],[\"invoke\",437,0,\"as-load-address\",[]],[\"invoke\",438,0,\"as-loadN-address\",[]],[\"invoke\",440,0,\"as-store-address\",[]],[\"invoke\",441,0,\"as-store-value\",[]],[\"invoke\",442,0,\"as-store-both\",[]],[\"invoke\",443,0,\"as-storeN-address\",[]],[\"invoke\",444,0,\"as-storeN-value\",[]],[\"invoke\",445,0,\"as-storeN-both\",[]],[\"invoke\",447,0,\"as-unary-operand\",[]],[\"invoke\",449,0,\"as-binary-left\",[]],[\"invoke\",450,0,\"as-binary-right\",[]],[\"invoke\",451,0,\"as-binary-both\",[]],[\"invoke\",453,0,\"as-test-operand\",[]],[\"invoke\",455,0,\"as-compare-left\",[]],[\"invoke\",456,0,\"as-compare-right\",[]],[\"invoke\",457,0,\"as-compare-both\",[]],[\"invoke\",459,0,\"as-convert-operand\",[]],[\"invoke\",461,0,\"as-memory.grow-size\",[]],[\"invoke\",463,0,\"nested-block-value\",[]],[\"invoke\",464,0,\"nested-br-value\",[]],[\"invoke\",465,0,\"nested-br_if-value\",[]],[\"invoke\",466,0,\"nested-br_if-value-cond\",[]],[\"invoke\",467,0,\"nested-br_table-value\",[]],[\"invoke\",468,0,\"nested-br_table-value-index\",[]],[\"file\",\"br_if.wast\"],[\"module\",3,2],[\"invoke\",372,0,\"type-i32\",[]],[\"invoke\",373,0,\"type-i64\",[]],[\"invoke\",374,0,\"type-f32\",[]],[\"invoke\",375,0,\"type-f64\",[]],[\"invoke\",377,0,\"type-i32-value\",[]],[\"invoke\",378,0,\"type-i64-value\",[]],[\"invoke\",379,0,\"type-f32-value\",[]],[\"invoke\",380,0,\"type-f64-value\",[]],[\"invoke\",382,0,\"as-block-first\",[\"i32:0\"]],[\"invoke\",383,0,\"as-block-first\",[\"i32:1\"]],[\"invoke\",384,0,\"as-block-mid\",[\"i32:0\"]],[\"invoke\",385,0,\"as-block-mid\",[\"i32:1\"]],[\"invoke\",386,0,\"as-block-last\",[\"i32:0\"]],[\"invoke\",387,0,\"as-block-last\",[\"i32:1\"]],[\"invoke\",389,0,\"as-block-first-value\",[\"i32:0\"]],[\"invoke\",390,0,\"as-block-first-value\",[\"i32:1\"]],[\"invoke\",391,0,\"as-block-mid-value\",[\"i32:0\"]],[\"invoke\",392,0,\"as-block-mid-value\",[\"i32:1\"]],[\"invoke\",393,0,\"as-block-last-value\",[\"i32:0\"]],[\"invoke\",394,0,\"as-block-last-value\",[\"i32:1\"]],[\"invoke\",396,0,\"as-loop-first\",[\"i32:0\"]],[\"invoke\",397,0,\"as-loop-first\",[\"i32:1\"]],[\"invoke\",398,0,\"as-loop-mid\",[\"i32:0\"]],[\"invoke\",399,0,\"as-loop-mid\",[\"i32:1\"]],[\"invoke\",400,0,\"as-loop-last\",[\"i32:0\"]],[\"invoke\",401,0,\"as-loop-last\",[\"i32:1\"]],[\"invoke\",403,0,\"as-br-value\",[]],[\"invoke\",405,0,\"as-br_if-cond\",[]],[\"invoke\",406,0,\"as-br_if-value\",[]],[\"invoke\",407,0,\"as-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",408,0,\"as-br_if-value-cond\",[\"i32:1\"]],[\"invoke\",410,0,\"as-br_table-index\",[]],[\"invoke\",411,0,\"as-br_table-value\",[]],[\"invoke\",412,0,\"as-br_table-value-index\",[]],[\"invoke\",414,0,\"as-return-value\",[]],[\"invoke\",416,0,\"as-if-cond\",[\"i32:0\"]],[\"invoke\",417,0,\"as-if-cond\",[\"i32:1\"]],[\"invoke\",418,0,\"as-if-then\",[\"i32:0\",\"i32:0\"]],[\"invoke\",419,0,\"as-if-then\",[\"i32:4\",\"i32:0\"]],[\"invoke\",420,0,\"as-if-then\",[\"i32:0\",\"i32:1\"]],[\"invoke\",421,0,\"as-if-then\",[\"i32:4\",\"i32:1\"]],[\"invoke\",422,0,\"as-if-else\",[\"i32:0\",\"i32:0\"]],[\"invoke\",423,0,\"as-if-else\",[\"i32:3\",\"i32:0\"]],[\"invoke\",424,0,\"as-if-else\",[\"i32:0\",\"i32:1\"]],[\"invoke\",425,0,\"as-if-else\",[\"i32:3\",\"i32:1\"]],[\"invoke\",427,0,\"as-select-first\",[\"i32:0\"]],[\"invoke\",428,0,\"as-select-first\",[\"i32:1\"]],[\"invoke\",429,0,\"as-select-second\",[\"i32:0\"]],[\"invoke\",430,0,\"as-select-second\",[\"i32:1\"]],[\"invoke\",431,0,\"as-select-cond\",[]],[\"invoke\",433,0,\"as-call-first\",[]],[\"invoke\",434,0,\"as-call-mid\",[]],[\"invoke\",435,0,\"as-call-last\",[]],[\"invoke\",437,0,\"as-call_indirect-func\",[]],[\"invoke\",438,0,\"as-call_indirect-first\",[]],[\"invoke\",439,0,\"as-call_indirect-mid\",[]],[\"invoke\",440,0,\"as-call_indirect-last\",[]],[\"invoke\",442,0,\"as-local.set-value\",[\"i32:0\"]],[\"invoke\",443,0,\"as-local.set-value\",[\"i32:1\"]],[\"invoke\",445,0,\"as-local.tee-value\",[\"i32:0\"]],[\"invoke\",446,0,\"as-local.tee-value\",[\"i32:1\"]],[\"invoke\",448,0,\"as-global.set-value\",[\"i32:0\"]],[\"invoke\",449,0,\"as-global.set-value\",[\"i32:1\"]],[\"invoke\",451,0,\"as-load-address\",[]],[\"invoke\",452,0,\"as-loadN-address\",[]],[\"invoke\",454,0,\"as-store-address\",[]],[\"invoke\",455,0,\"as-store-value\",[]],[\"invoke\",456,0,\"as-storeN-address\",[]],[\"invoke\",457,0,\"as-storeN-value\",[]],[\"invoke\",459,0,\"as-unary-operand\",[]],[\"invoke\",460,0,\"as-binary-left\",[]],[\"invoke\",461,0,\"as-binary-right\",[]],[\"invoke\",462,0,\"as-test-operand\",[]],[\"invoke\",463,0,\"as-compare-left\",[]],[\"invoke\",464,0,\"as-compare-right\",[]],[\"invoke\",465,0,\"as-memory.grow-size\",[]],[\"invoke\",467,0,\"nested-block-value\",[\"i32:0\"]],[\"invoke\",468,0,\"nested-block-value\",[\"i32:1\"]],[\"invoke\",469,0,\"nested-br-value\",[\"i32:0\"]],[\"invoke\",470,0,\"nested-br-value\",[\"i32:1\"]],[\"invoke\",471,0,\"nested-br_if-value\",[\"i32:0\"]],[\"invoke\",472,0,\"nested-br_if-value\",[\"i32:1\"]],[\"invoke\",473,0,\"nested-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",474,0,\"nested-br_if-value-cond\",[\"i32:1\"]],[\"invoke\",475,0,\"nested-br_table-value\",[\"i32:0\"]],[\"invoke\",476,0,\"nested-br_table-value\",[\"i32:1\"]],[\"invoke\",477,0,\"nested-br_table-value-index\",[\"i32:0\"]],[\"invoke\",478,0,\"nested-br_table-value-index\",[\"i32:1\"]],[\"file\",\"br_table.wast\"],[\"module\",3,3],[\"invoke\",1007,0,\"type-i32\",[]],[\"invoke\",1008,0,\"type-i64\",[]],[\"invoke\",1009,0,\"type-f32\",[]],[\"invoke\",1010,0,\"type-f64\",[]],[\"invoke\",1012,0,\"type-i32-value\",[]],[\"invoke\",1013,0,\"type-i64-value\",[]],[\"invoke\",1014,0,\"type-f32-value\",[]],[\"invoke\",1015,0,\"type-f64-value\",[]],[\"invoke\",1017,0,\"empty\",[\"i32:0\"]],[\"invoke\",1018,0,\"empty\",[\"i32:1\"]],[\"invoke\",1019,0,\"empty\",[\"i32:11\"]],[\"invoke\",1020,0,\"empty\",[\"i32:4294967295\"]],[\"invoke\",1021,0,\"empty\",[\"i32:4294967196\"]],[\"invoke\",1022,0,\"empty\",[\"i32:4294967295\"]],[\"invoke\",1024,0,\"empty-value\",[\"i32:0\"]],[\"invoke\",1025,0,\"empty-value\",[\"i32:1\"]],[\"invoke\",1026,0,\"empty-value\",[\"i32:11\"]],[\"invoke\",1027,0,\"empty-value\",[\"i32:4294967295\"]],[\"invoke\",1028,0,\"empty-value\",[\"i32:4294967196\"]],[\"invoke\",1029,0,\"empty-value\",[\"i32:4294967295\"]],[\"invoke\",1031,0,\"singleton\",[\"i32:0\"]],[\"invoke\",1032,0,\"singleton\",[\"i32:1\"]],[\"invoke\",1033,0,\"singleton\",[\"i32:11\"]],[\"invoke\",1034,0,\"singleton\",[\"i32:4294967295\"]],[\"invoke\",1035,0,\"singleton\",[\"i32:4294967196\"]],[\"invoke\",1036,0,\"singleton\",[\"i32:4294967295\"]],[\"invoke\",1038,0,\"singleton-value\",[\"i32:0\"]],[\"invoke\",1039,0,\"singleton-value\",[\"i32:1\"]],[\"invoke\",1040,0,\"singleton-value\",[\"i32:11\"]],[\"invoke\",1041,0,\"singleton-value\",[\"i32:4294967295\"]],[\"invoke\",1042,0,\"singleton-value\",[\"i32:4294967196\"]],[\"invoke\",1043,0,\"singleton-value\",[\"i32:4294967295\"]],[\"invoke\",1045,0,\"multiple\",[\"i32:0\"]],[\"invoke\",1046,0,\"multiple\",[\"i32:1\"]],[\"invoke\",1047,0,\"multiple\",[\"i32:2\"]],[\"invoke\",1048,0,\"multiple\",[\"i32:3\"]],[\"invoke\",1049,0,\"multiple\",[\"i32:4\"]],[\"invoke\",1050,0,\"multiple\",[\"i32:5\"]],[\"invoke\",1051,0,\"multiple\",[\"i32:6\"]],[\"invoke\",1052,0,\"multiple\",[\"i32:10\"]],[\"invoke\",1053,0,\"multiple\",[\"i32:4294967295\"]],[\"invoke\",1054,0,\"multiple\",[\"i32:4294967295\"]],[\"invoke\",1056,0,\"multiple-value\",[\"i32:0\"]],[\"invoke\",1057,0,\"multiple-value\",[\"i32:1\"]],[\"invoke\",1058,0,\"multiple-value\",[\"i32:2\"]],[\"invoke\",1059,0,\"multiple-value\",[\"i32:3\"]],[\"invoke\",1060,0,\"multiple-value\",[\"i32:4\"]],[\"invoke\",1061,0,\"multiple-value\",[\"i32:5\"]],[\"invoke\",1062,0,\"multiple-value\",[\"i32:6\"]],[\"invoke\",1063,0,\"multiple-value\",[\"i32:10\"]],[\"invoke\",1064,0,\"multiple-value\",[\"i32:4294967295\"]],[\"invoke\",1065,0,\"multiple-value\",[\"i32:4294967295\"]],[\"invoke\",1067,0,\"large\",[\"i32:0\"]],[\"invoke\",1068,0,\"large\",[\"i32:1\"]],[\"invoke\",1069,0,\"large\",[\"i32:100\"]],[\"invoke\",1070,0,\"large\",[\"i32:101\"]],[\"invoke\",1071,0,\"large\",[\"i32:10000\"]],[\"invoke\",1072,0,\"large\",[\"i32:10001\"]],[\"invoke\",1073,0,\"large\",[\"i32:1000000\"]],[\"invoke\",1074,0,\"large\",[\"i32:1000001\"]],[\"invoke\",1076,0,\"as-block-first\",[]],[\"invoke\",1077,0,\"as-block-mid\",[]],[\"invoke\",1078,0,\"as-block-last\",[]],[\"invoke\",1079,0,\"as-block-value\",[]],[\"invoke\",1081,0,\"as-loop-first\",[]],[\"invoke\",1082,0,\"as-loop-mid\",[]],[\"invoke\",1083,0,\"as-loop-last\",[]],[\"invoke\",1085,0,\"as-br-value\",[]],[\"invoke\",1087,0,\"as-br_if-cond\",[]],[\"invoke\",1088,0,\"as-br_if-value\",[]],[\"invoke\",1089,0,\"as-br_if-value-cond\",[]],[\"invoke\",1091,0,\"as-br_table-index\",[]],[\"invoke\",1092,0,\"as-br_table-value\",[]],[\"invoke\",1093,0,\"as-br_table-value-index\",[]],[\"invoke\",1095,0,\"as-return-value\",[]],[\"invoke\",1097,0,\"as-if-cond\",[]],[\"invoke\",1098,0,\"as-if-then\",[\"i32:1\",\"i32:6\"]],[\"invoke\",1099,0,\"as-if-then\",[\"i32:0\",\"i32:6\"]],[\"invoke\",1100,0,\"as-if-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",1101,0,\"as-if-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",1103,0,\"as-select-first\",[\"i32:0\",\"i32:6\"]],[\"invoke\",1104,0,\"as-select-first\",[\"i32:1\",\"i32:6\"]],[\"invoke\",1105,0,\"as-select-second\",[\"i32:0\",\"i32:6\"]],[\"invoke\",1106,0,\"as-select-second\",[\"i32:1\",\"i32:6\"]],[\"invoke\",1107,0,\"as-select-cond\",[]],[\"invoke\",1109,0,\"as-call-first\",[]],[\"invoke\",1110,0,\"as-call-mid\",[]],[\"invoke\",1111,0,\"as-call-last\",[]],[\"invoke\",1113,0,\"as-call_indirect-first\",[]],[\"invoke\",1114,0,\"as-call_indirect-mid\",[]],[\"invoke\",1115,0,\"as-call_indirect-last\",[]],[\"invoke\",1116,0,\"as-call_indirect-func\",[]],[\"invoke\",1118,0,\"as-local.set-value\",[]],[\"invoke\",1119,0,\"as-local.tee-value\",[]],[\"invoke\",1120,0,\"as-global.set-value\",[]],[\"invoke\",1122,0,\"as-load-address\",[]],[\"invoke\",1123,0,\"as-loadN-address\",[]],[\"invoke\",1125,0,\"as-store-address\",[]],[\"invoke\",1126,0,\"as-store-value\",[]],[\"invoke\",1127,0,\"as-storeN-address\",[]],[\"invoke\",1128,0,\"as-storeN-value\",[]],[\"invoke\",1130,0,\"as-unary-operand\",[]],[\"invoke\",1132,0,\"as-binary-left\",[]],[\"invoke\",1133,0,\"as-binary-right\",[]],[\"invoke\",1135,0,\"as-test-operand\",[]],[\"invoke\",1137,0,\"as-compare-left\",[]],[\"invoke\",1138,0,\"as-compare-right\",[]],[\"invoke\",1140,0,\"as-convert-operand\",[]],[\"invoke\",1142,0,\"as-memory.grow-size\",[]],[\"invoke\",1144,0,\"nested-block-value\",[\"i32:0\"]],[\"invoke\",1145,0,\"nested-block-value\",[\"i32:1\"]],[\"invoke\",1146,0,\"nested-block-value\",[\"i32:2\"]],[\"invoke\",1147,0,\"nested-block-value\",[\"i32:10\"]],[\"invoke\",1148,0,\"nested-block-value\",[\"i32:4294967295\"]],[\"invoke\",1149,0,\"nested-block-value\",[\"i32:100000\"]],[\"invoke\",1151,0,\"nested-br-value\",[\"i32:0\"]],[\"invoke\",1152,0,\"nested-br-value\",[\"i32:1\"]],[\"invoke\",1153,0,\"nested-br-value\",[\"i32:2\"]],[\"invoke\",1154,0,\"nested-br-value\",[\"i32:11\"]],[\"invoke\",1155,0,\"nested-br-value\",[\"i32:4294967292\"]],[\"invoke\",1156,0,\"nested-br-value\",[\"i32:10213210\"]],[\"invoke\",1158,0,\"nested-br_if-value\",[\"i32:0\"]],[\"invoke\",1159,0,\"nested-br_if-value\",[\"i32:1\"]],[\"invoke\",1160,0,\"nested-br_if-value\",[\"i32:2\"]],[\"invoke\",1161,0,\"nested-br_if-value\",[\"i32:9\"]],[\"invoke\",1162,0,\"nested-br_if-value\",[\"i32:4294967287\"]],[\"invoke\",1163,0,\"nested-br_if-value\",[\"i32:999999\"]],[\"invoke\",1165,0,\"nested-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",1166,0,\"nested-br_if-value-cond\",[\"i32:1\"]],[\"invoke\",1167,0,\"nested-br_if-value-cond\",[\"i32:2\"]],[\"invoke\",1168,0,\"nested-br_if-value-cond\",[\"i32:3\"]],[\"invoke\",1169,0,\"nested-br_if-value-cond\",[\"i32:4293967296\"]],[\"invoke\",1170,0,\"nested-br_if-value-cond\",[\"i32:9423975\"]],[\"invoke\",1172,0,\"nested-br_table-value\",[\"i32:0\"]],[\"invoke\",1173,0,\"nested-br_table-value\",[\"i32:1\"]],[\"invoke\",1174,0,\"nested-br_table-value\",[\"i32:2\"]],[\"invoke\",1175,0,\"nested-br_table-value\",[\"i32:9\"]],[\"invoke\",1176,0,\"nested-br_table-value\",[\"i32:4294967287\"]],[\"invoke\",1177,0,\"nested-br_table-value\",[\"i32:999999\"]],[\"invoke\",1179,0,\"nested-br_table-value-index\",[\"i32:0\"]],[\"invoke\",1180,0,\"nested-br_table-value-index\",[\"i32:1\"]],[\"invoke\",1181,0,\"nested-br_table-value-index\",[\"i32:2\"]],[\"invoke\",1182,0,\"nested-br_table-value-index\",[\"i32:3\"]],[\"invoke\",1183,0,\"nested-br_table-value-index\",[\"i32:4293967296\"]],[\"invoke\",1184,0,\"nested-br_table-value-index\",[\"i32:9423975\"]],[\"invoke\",1186,0,\"nested-br_table-loop-block\",[\"i32:1\"]],[\"invoke\",1188,0,\"meet-externref\",[\"i32:0\",\"externref:1\"]],[\"invoke\",1189,0,\"meet-externref\",[\"i32:1\",\"externref:1\"]],[\"invoke\",1190,0,\"meet-externref\",[\"i32:2\",\"externref:1\"]],[\"file\",\"call.wast\"],[\"module\",3,4],[\"invoke\",285,0,\"type-i32\",[]],[\"invoke\",286,0,\"type-i64\",[]],[\"invoke\",287,0,\"type-f32\",[]],[\"invoke\",288,0,\"type-f64\",[]],[\"invoke\",289,0,\"type-i32-i64\",[]],[\"invoke\",291,0,\"type-first-i32\",[]],[\"invoke\",292,0,\"type-first-i64\",[]],[\"invoke\",293,0,\"type-first-f32\",[]],[\"invoke\",294,0,\"type-first-f64\",[]],[\"invoke\",296,0,\"type-second-i32\",[]],[\"invoke\",297,0,\"type-second-i64\",[]],[\"invoke\",298,0,\"type-second-f32\",[]],[\"invoke\",299,0,\"type-second-f64\",[]],[\"invoke\",301,0,\"type-all-i32-f64\",[]],[\"invoke\",302,0,\"type-all-i32-i32\",[]],[\"invoke\",303,0,\"type-all-f32-f64\",[]],[\"invoke\",304,0,\"type-all-f64-i32\",[]],[\"invoke\",306,0,\"as-binary-all-operands\",[]],[\"invoke\",307,0,\"as-mixed-operands\",[]],[\"invoke\",308,0,\"as-call-all-operands\",[]],[\"invoke\",310,0,\"fac\",[\"i64:0\"]],[\"invoke\",311,0,\"fac\",[\"i64:1\"]],[\"invoke\",312,0,\"fac\",[\"i64:5\"]],[\"invoke\",313,0,\"fac\",[\"i64:25\"]],[\"invoke\",314,0,\"fac-acc\",[\"i64:0\",\"i64:1\"]],[\"invoke\",315,0,\"fac-acc\",[\"i64:1\",\"i64:1\"]],[\"invoke\",316,0,\"fac-acc\",[\"i64:5\",\"i64:1\"]],[\"invoke\",318,0,\"fac-acc\",[\"i64:25\",\"i64:1\"]],[\"invoke\",322,0,\"fib\",[\"i64:0\"]],[\"invoke\",323,0,\"fib\",[\"i64:1\"]],[\"invoke\",324,0,\"fib\",[\"i64:2\"]],[\"invoke\",325,0,\"fib\",[\"i64:5\"]],[\"invoke\",326,0,\"fib\",[\"i64:20\"]],[\"invoke\",328,0,\"even\",[\"i64:0\"]],[\"invoke\",329,0,\"even\",[\"i64:1\"]],[\"invoke\",330,0,\"even\",[\"i64:100\"]],[\"invoke\",331,0,\"even\",[\"i64:77\"]],[\"invoke\",332,0,\"odd\",[\"i64:0\"]],[\"invoke\",333,0,\"odd\",[\"i64:1\"]],[\"invoke\",334,0,\"odd\",[\"i64:200\"]],[\"invoke\",335,0,\"odd\",[\"i64:77\"]],[\"invoke\",337,0,\"runaway\",[]],[\"invoke\",338,0,\"mutual-runaway\",[]],[\"invoke\",340,0,\"as-select-first\",[]],[\"invoke\",341,0,\"as-select-mid\",[]],[\"invoke\",342,0,\"as-select-last\",[]],[\"invoke\",344,0,\"as-if-condition\",[]],[\"invoke\",346,0,\"as-br_if-first\",[]],[\"invoke\",347,0,\"as-br_if-last\",[]],[\"invoke\",349,0,\"as-br_table-first\",[]],[\"invoke\",350,0,\"as-br_table-last\",[]],[\"invoke\",352,0,\"as-call_indirect-first\",[]],[\"invoke\",353,0,\"as-call_indirect-mid\",[]],[\"invoke\",354,0,\"as-call_indirect-last\",[]],[\"invoke\",356,0,\"as-store-first\",[]],[\"invoke\",357,0,\"as-store-last\",[]],[\"invoke\",359,0,\"as-memory.grow-value\",[]],[\"invoke\",360,0,\"as-return-value\",[]],[\"invoke\",361,0,\"as-drop-operand\",[]],[\"invoke\",362,0,\"as-br-value\",[]],[\"invoke\",363,0,\"as-local.set-value\",[]],[\"invoke\",364,0,\"as-local.tee-value\",[]],[\"invoke\",365,0,\"as-global.set-value\",[]],[\"invoke\",366,0,\"as-load-operand\",[]],[\"invoke\",368,0,\"as-unary-operand\",[]],[\"invoke\",369,0,\"as-binary-left\",[]],[\"invoke\",370,0,\"as-binary-right\",[]],[\"invoke\",371,0,\"as-test-operand\",[]],[\"invoke\",372,0,\"as-compare-left\",[]],[\"invoke\",373,0,\"as-compare-right\",[]],[\"invoke\",374,0,\"as-convert-operand\",[]],[\"invoke\",376,0,\"return-from-long-argument-list\",[\"i32:42\"]],[\"file\",\"call_indirect.wast\"],[\"module\",3,5],[\"invoke\",471,0,\"type-i32\",[]],[\"invoke\",472,0,\"type-i64\",[]],[\"invoke\",473,0,\"type-f32\",[]],[\"invoke\",474,0,\"type-f64\",[]],[\"invoke\",475,0,\"type-f64-i32\",[]],[\"invoke\",477,0,\"type-index\",[]],[\"invoke\",479,0,\"type-first-i32\",[]],[\"invoke\",480,0,\"type-first-i64\",[]],[\"invoke\",481,0,\"type-first-f32\",[]],[\"invoke\",482,0,\"type-first-f64\",[]],[\"invoke\",484,0,\"type-second-i32\",[]],[\"invoke\",485,0,\"type-second-i64\",[]],[\"invoke\",486,0,\"type-second-f32\",[]],[\"invoke\",487,0,\"type-second-f64\",[]],[\"invoke\",489,0,\"type-all-f64-i32\",[]],[\"invoke\",490,0,\"type-all-i32-f64\",[]],[\"invoke\",491,0,\"type-all-i32-i64\",[]],[\"invoke\",493,0,\"dispatch\",[\"i32:5\",\"i64:2\"]],[\"invoke\",494,0,\"dispatch\",[\"i32:5\",\"i64:5\"]],[\"invoke\",495,0,\"dispatch\",[\"i32:12\",\"i64:5\"]],[\"invoke\",496,0,\"dispatch\",[\"i32:13\",\"i64:5\"]],[\"invoke\",497,0,\"dispatch\",[\"i32:20\",\"i64:2\"]],[\"invoke\",498,0,\"dispatch\",[\"i32:0\",\"i64:2\"]],[\"invoke\",499,0,\"dispatch\",[\"i32:15\",\"i64:2\"]],[\"invoke\",500,0,\"dispatch\",[\"i32:32\",\"i64:2\"]],[\"invoke\",501,0,\"dispatch\",[\"i32:4294967295\",\"i64:2\"]],[\"invoke\",502,0,\"dispatch\",[\"i32:1213432423\",\"i64:2\"]],[\"invoke\",504,0,\"dispatch-structural-i64\",[\"i32:5\"]],[\"invoke\",505,0,\"dispatch-structural-i64\",[\"i32:12\"]],[\"invoke\",506,0,\"dispatch-structural-i64\",[\"i32:13\"]],[\"invoke\",507,0,\"dispatch-structural-i64\",[\"i32:20\"]],[\"invoke\",508,0,\"dispatch-structural-i64\",[\"i32:11\"]],[\"invoke\",509,0,\"dispatch-structural-i64\",[\"i32:22\"]],[\"invoke\",511,0,\"dispatch-structural-i32\",[\"i32:4\"]],[\"invoke\",512,0,\"dispatch-structural-i32\",[\"i32:23\"]],[\"invoke\",513,0,\"dispatch-structural-i32\",[\"i32:26\"]],[\"invoke\",514,0,\"dispatch-structural-i32\",[\"i32:19\"]],[\"invoke\",515,0,\"dispatch-structural-i32\",[\"i32:9\"]],[\"invoke\",516,0,\"dispatch-structural-i32\",[\"i32:21\"]],[\"invoke\",518,0,\"dispatch-structural-f32\",[\"i32:6\"]],[\"invoke\",519,0,\"dispatch-structural-f32\",[\"i32:24\"]],[\"invoke\",520,0,\"dispatch-structural-f32\",[\"i32:27\"]],[\"invoke\",521,0,\"dispatch-structural-f32\",[\"i32:21\"]],[\"invoke\",522,0,\"dispatch-structural-f32\",[\"i32:8\"]],[\"invoke\",523,0,\"dispatch-structural-f32\",[\"i32:19\"]],[\"invoke\",525,0,\"dispatch-structural-f64\",[\"i32:7\"]],[\"invoke\",526,0,\"dispatch-structural-f64\",[\"i32:25\"]],[\"invoke\",527,0,\"dispatch-structural-f64\",[\"i32:28\"]],[\"invoke\",528,0,\"dispatch-structural-f64\",[\"i32:22\"]],[\"invoke\",529,0,\"dispatch-structural-f64\",[\"i32:10\"]],[\"invoke\",530,0,\"dispatch-structural-f64\",[\"i32:18\"]],[\"invoke\",532,0,\"fac-i64\",[\"i64:0\"]],[\"invoke\",533,0,\"fac-i64\",[\"i64:1\"]],[\"invoke\",534,0,\"fac-i64\",[\"i64:5\"]],[\"invoke\",535,0,\"fac-i64\",[\"i64:25\"]],[\"invoke\",537,0,\"fac-i32\",[\"i32:0\"]],[\"invoke\",538,0,\"fac-i32\",[\"i32:1\"]],[\"invoke\",539,0,\"fac-i32\",[\"i32:5\"]],[\"invoke\",540,0,\"fac-i32\",[\"i32:10\"]],[\"invoke\",542,0,\"fac-f32\",[\"f32:0\"]],[\"invoke\",543,0,\"fac-f32\",[\"f32:1065353216\"]],[\"invoke\",544,0,\"fac-f32\",[\"f32:1084227584\"]],[\"invoke\",545,0,\"fac-f32\",[\"f32:1092616192\"]],[\"invoke\",547,0,\"fac-f64\",[\"f64:0\"]],[\"invoke\",548,0,\"fac-f64\",[\"f64:4607182418800017408\"]],[\"invoke\",549,0,\"fac-f64\",[\"f64:4617315517961601024\"]],[\"invoke\",550,0,\"fac-f64\",[\"f64:4621819117588971520\"]],[\"invoke\",552,0,\"fib-i64\",[\"i64:0\"]],[\"invoke\",553,0,\"fib-i64\",[\"i64:1\"]],[\"invoke\",554,0,\"fib-i64\",[\"i64:2\"]],[\"invoke\",555,0,\"fib-i64\",[\"i64:5\"]],[\"invoke\",556,0,\"fib-i64\",[\"i64:20\"]],[\"invoke\",558,0,\"fib-i32\",[\"i32:0\"]],[\"invoke\",559,0,\"fib-i32\",[\"i32:1\"]],[\"invoke\",560,0,\"fib-i32\",[\"i32:2\"]],[\"invoke\",561,0,\"fib-i32\",[\"i32:5\"]],[\"invoke\",562,0,\"fib-i32\",[\"i32:20\"]],[\"invoke\",564,0,\"fib-f32\",[\"f32:0\"]],[\"invoke\",565,0,\"fib-f32\",[\"f32:1065353216\"]],[\"invoke\",566,0,\"fib-f32\",[\"f32:1073741824\"]],[\"invoke\",567,0,\"fib-f32\",[\"f32:1084227584\"]],[\"invoke\",568,0,\"fib-f32\",[\"f32:1101004800\"]],[\"invoke\",570,0,\"fib-f64\",[\"f64:0\"]],[\"invoke\",571,0,\"fib-f64\",[\"f64:4607182418800017408\"]],[\"invoke\",572,0,\"fib-f64\",[\"f64:4611686018427387904\"]],[\"invoke\",573,0,\"fib-f64\",[\"f64:4617315517961601024\"]],[\"invoke\",574,0,\"fib-f64\",[\"f64:4626322717216342016\"]],[\"invoke\",576,0,\"even\",[\"i32:0\"]],[\"invoke\",577,0,\"even\",[\"i32:1\"]],[\"invoke\",578,0,\"even\",[\"i32:100\"]],[\"invoke\",579,0,\"even\",[\"i32:77\"]],[\"invoke\",580,0,\"odd\",[\"i32:0\"]],[\"invoke\",581,0,\"odd\",[\"i32:1\"]],[\"invoke\",582,0,\"odd\",[\"i32:200\"]],[\"invoke\",583,0,\"odd\",[\"i32:77\"]],[\"invoke\",585,0,\"runaway\",[]],[\"invoke\",586,0,\"mutual-runaway\",[]],[\"invoke\",588,0,\"as-select-first\",[]],[\"invoke\",589,0,\"as-select-mid\",[]],[\"invoke\",590,0,\"as-select-last\",[]],[\"invoke\",592,0,\"as-if-condition\",[]],[\"invoke\",594,0,\"as-br_if-first\",[]],[\"invoke\",595,0,\"as-br_if-last\",[]],[\"invoke\",597,0,\"as-br_table-first\",[]],[\"invoke\",598,0,\"as-br_table-last\",[]],[\"invoke\",600,0,\"as-store-first\",[]],[\"invoke\",601,0,\"as-store-last\",[]],[\"invoke\",603,0,\"as-memory.grow-value\",[]],[\"invoke\",604,0,\"as-return-value\",[]],[\"invoke\",605,0,\"as-drop-operand\",[]],[\"invoke\",606,0,\"as-br-value\",[]],[\"invoke\",607,0,\"as-local.set-value\",[]],[\"invoke\",608,0,\"as-local.tee-value\",[]],[\"invoke\",609,0,\"as-global.set-value\",[]],[\"invoke\",610,0,\"as-load-operand\",[]],[\"invoke\",612,0,\"as-unary-operand\",[]],[\"invoke\",613,0,\"as-binary-left\",[]],[\"invoke\",614,0,\"as-binary-right\",[]],[\"invoke\",615,0,\"as-test-operand\",[]],[\"invoke\",616,0,\"as-compare-left\",[]],[\"invoke\",617,0,\"as-compare-right\",[]],[\"invoke\",618,0,\"as-convert-operand\",[]],[\"module\",623,6],[\"invoke\",650,0,\"call-1\",[\"i32:2\",\"i32:3\",\"i32:0\"]],[\"invoke\",651,0,\"call-1\",[\"i32:2\",\"i32:3\",\"i32:1\"]],[\"invoke\",652,0,\"call-1\",[\"i32:2\",\"i32:3\",\"i32:2\"]],[\"invoke\",654,0,\"call-2\",[\"i32:2\",\"i32:3\",\"i32:0\"]],[\"invoke\",655,0,\"call-2\",[\"i32:2\",\"i32:3\",\"i32:1\"]],[\"invoke\",656,0,\"call-2\",[\"i32:2\",\"i32:3\",\"i32:2\"]],[\"invoke\",657,0,\"call-2\",[\"i32:2\",\"i32:3\",\"i32:3\"]],[\"invoke\",659,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:0\"]],[\"invoke\",660,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:1\"]],[\"invoke\",661,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:2\"]],[\"invoke\",662,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:3\"]],[\"invoke\",663,0,\"call-3\",[\"i32:2\",\"i32:3\",\"i32:4\"]],[\"module\",1047,7],[\"file\",\"fac.wast\"],[\"module\",1,8],[\"invoke\",102,0,\"fac-rec\",[\"i64:25\"]],[\"invoke\",103,0,\"fac-iter\",[\"i64:25\"]],[\"invoke\",104,0,\"fac-rec-named\",[\"i64:25\"]],[\"invoke\",105,0,\"fac-iter-named\",[\"i64:25\"]],[\"invoke\",106,0,\"fac-opt\",[\"i64:25\"]],[\"invoke\",107,0,\"fac-ssa\",[\"i64:25\"]],[\"invoke\",109,0,\"fac-rec\",[\"i64:1073741824\"]],[\"file\",\"forward.wast\"],[\"module\",1,9],[\"invoke\",17,0,\"even\",[\"i32:13\"]],[\"invoke\",18,0,\"even\",[\"i32:20\"]],[\"invoke\",19,0,\"odd\",[\"i32:13\"]],[\"invoke\",20,0,\"odd\",[\"i32:20\"]],[\"file\",\"func.wast\"],[\"module\",3,10],[\"invoke\",241,0,\"type-use-1\",[]],[\"invoke\",242,0,\"type-use-2\",[]],[\"invoke\",243,0,\"type-use-3\",[\"i32:1\"]],[\"invoke\",245,0,\"type-use-4\",[\"i32:1\",\"f64:4607182418800017408\",\"i32:1\"]],[\"invoke\",248,0,\"type-use-5\",[]],[\"invoke\",249,0,\"type-use-6\",[\"i32:1\"]],[\"invoke\",251,0,\"type-use-7\",[\"i32:1\",\"f64:4607182418800017408\",\"i32:1\"]],[\"invoke\",255,0,\"local-first-i32\",[]],[\"invoke\",256,0,\"local-first-i64\",[]],[\"invoke\",257,0,\"local-first-f32\",[]],[\"invoke\",258,0,\"local-first-f64\",[]],[\"invoke\",259,0,\"local-second-i32\",[]],[\"invoke\",260,0,\"local-second-i64\",[]],[\"invoke\",261,0,\"local-second-f32\",[]],[\"invoke\",262,0,\"local-second-f64\",[]],[\"invoke\",263,0,\"local-mixed\",[]],[\"invoke\",266,0,\"param-first-i32\",[\"i32:2\",\"i32:3\"]],[\"invoke\",269,0,\"param-first-i64\",[\"i64:2\",\"i64:3\"]],[\"invoke\",272,0,\"param-first-f32\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",275,0,\"param-first-f64\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",278,0,\"param-second-i32\",[\"i32:2\",\"i32:3\"]],[\"invoke\",281,0,\"param-second-i64\",[\"i64:2\",\"i64:3\"]],[\"invoke\",284,0,\"param-second-f32\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",287,0,\"param-second-f64\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",291,0,\"param-mixed\",[\"f32:1065353216\",\"i32:2\",\"i64:3\",\"i32:4\",\"f64:4617878467915022336\",\"i32:6\"]],[\"invoke\",298,0,\"empty\",[]],[\"invoke\",299,0,\"value-void\",[]],[\"invoke\",300,0,\"value-i32\",[]],[\"invoke\",301,0,\"value-i64\",[]],[\"invoke\",302,0,\"value-f32\",[]],[\"invoke\",303,0,\"value-f64\",[]],[\"invoke\",304,0,\"value-i32-f64\",[]],[\"invoke\",305,0,\"value-i32-i32-i32\",[]],[\"invoke\",308,0,\"value-block-void\",[]],[\"invoke\",309,0,\"value-block-i32\",[]],[\"invoke\",310,0,\"value-block-i32-i64\",[]],[\"invoke\",312,0,\"return-empty\",[]],[\"invoke\",313,0,\"return-i32\",[]],[\"invoke\",314,0,\"return-i64\",[]],[\"invoke\",315,0,\"return-f32\",[]],[\"invoke\",316,0,\"return-f64\",[]],[\"invoke\",317,0,\"return-i32-f64\",[]],[\"invoke\",318,0,\"return-i32-i32-i32\",[]],[\"invoke\",321,0,\"return-block-i32\",[]],[\"invoke\",322,0,\"return-block-i32-i64\",[]],[\"invoke\",324,0,\"break-empty\",[]],[\"invoke\",325,0,\"break-i32\",[]],[\"invoke\",326,0,\"break-i64\",[]],[\"invoke\",327,0,\"break-f32\",[]],[\"invoke\",328,0,\"break-f64\",[]],[\"invoke\",329,0,\"break-i32-f64\",[]],[\"invoke\",330,0,\"break-i32-i32-i32\",[]],[\"invoke\",333,0,\"break-block-i32\",[]],[\"invoke\",334,0,\"break-block-i32-i64\",[]],[\"invoke\",336,0,\"break-br_if-empty\",[\"i32:0\"]],[\"invoke\",337,0,\"break-br_if-empty\",[\"i32:2\"]],[\"invoke\",338,0,\"break-br_if-num\",[\"i32:0\"]],[\"invoke\",339,0,\"break-br_if-num\",[\"i32:1\"]],[\"invoke\",340,0,\"break-br_if-num-num\",[\"i32:0\"]],[\"invoke\",343,0,\"break-br_if-num-num\",[\"i32:1\"]],[\"invoke\",347,0,\"break-br_table-empty\",[\"i32:0\"]],[\"invoke\",348,0,\"break-br_table-empty\",[\"i32:1\"]],[\"invoke\",349,0,\"break-br_table-empty\",[\"i32:5\"]],[\"invoke\",350,0,\"break-br_table-empty\",[\"i32:4294967295\"]],[\"invoke\",351,0,\"break-br_table-num\",[\"i32:0\"]],[\"invoke\",352,0,\"break-br_table-num\",[\"i32:1\"]],[\"invoke\",353,0,\"break-br_table-num\",[\"i32:10\"]],[\"invoke\",354,0,\"break-br_table-num\",[\"i32:4294967196\"]],[\"invoke\",355,0,\"break-br_table-num-num\",[\"i32:0\"]],[\"invoke\",358,0,\"break-br_table-num-num\",[\"i32:1\"]],[\"invoke\",361,0,\"break-br_table-num-num\",[\"i32:10\"]],[\"invoke\",364,0,\"break-br_table-num-num\",[\"i32:4294967196\"]],[\"invoke\",367,0,\"break-br_table-nested-empty\",[\"i32:0\"]],[\"invoke\",368,0,\"break-br_table-nested-empty\",[\"i32:1\"]],[\"invoke\",369,0,\"break-br_table-nested-empty\",[\"i32:3\"]],[\"invoke\",370,0,\"break-br_table-nested-empty\",[\"i32:4294967294\"]],[\"invoke\",372,0,\"break-br_table-nested-num\",[\"i32:0\"]],[\"invoke\",375,0,\"break-br_table-nested-num\",[\"i32:1\"]],[\"invoke\",378,0,\"break-br_table-nested-num\",[\"i32:2\"]],[\"invoke\",381,0,\"break-br_table-nested-num\",[\"i32:4294967293\"]],[\"invoke\",384,0,\"break-br_table-nested-num-num\",[\"i32:0\"]],[\"invoke\",388,0,\"break-br_table-nested-num-num\",[\"i32:1\"]],[\"invoke\",392,0,\"break-br_table-nested-num-num\",[\"i32:2\"]],[\"invoke\",396,0,\"break-br_table-nested-num-num\",[\"i32:4294967293\"]],[\"invoke\",401,0,\"large-sig\",[\"i32:0\",\"i64:1\",\"f32:1073741824\",\"f32:1077936128\",\"i32:4\",\"f64:4617315517961601024\",\"f32:1086324736\",\"i32:7\",\"i32:8\",\"i32:9\",\"f32:1092616192\",\"f64:4622382067542392832\",\"f64:4622945017495814144\",\"f64:4623507967449235456\",\"i32:14\",\"i32:15\",\"f32:1098907648\"]],[\"invoke\",414,0,\"init-local-i32\",[]],[\"invoke\",415,0,\"init-local-i64\",[]],[\"invoke\",416,0,\"init-local-f32\",[]],[\"invoke\",417,0,\"init-local-f64\",[]],[\"module\",422,11],[\"module\",459,12],[\"invoke\",483,0,\"f\",[\"i32:42\"]],[\"invoke\",484,0,\"g\",[\"i32:42\"]],[\"invoke\",485,0,\"p\",[]],[\"module\",488,13],[\"invoke\",551,0,\"signature-explicit-reused\",[]],[\"invoke\",552,0,\"signature-implicit-reused\",[]],[\"invoke\",553,0,\"signature-explicit-duplicate\",[]],[\"invoke\",554,0,\"signature-implicit-duplicate\",[]],[\"file\",\"func_ptrs.wast\"],[\"module\",1,14],[\"invoke\",27,0,\"one\",[]],[\"invoke\",28,0,\"two\",[\"i32:13\"]],[\"invoke\",29,0,\"three\",[\"i32:13\"]],[\"invoke\",30,0,\"four\",[\"i32:83\"]],[\"module\",51,15],[\"invoke\",71,0,\"callt\",[\"i32:0\"]],[\"invoke\",72,0,\"callt\",[\"i32:1\"]],[\"invoke\",73,0,\"callt\",[\"i32:2\"]],[\"invoke\",74,0,\"callt\",[\"i32:3\"]],[\"invoke\",75,0,\"callt\",[\"i32:4\"]],[\"invoke\",76,0,\"callt\",[\"i32:5\"]],[\"invoke\",77,0,\"callt\",[\"i32:6\"]],[\"invoke\",78,0,\"callt\",[\"i32:7\"]],[\"invoke\",79,0,\"callt\",[\"i32:100\"]],[\"invoke\",80,0,\"callt\",[\"i32:4294967295\"]],[\"invoke\",82,0,\"callu\",[\"i32:0\"]],[\"invoke\",83,0,\"callu\",[\"i32:1\"]],[\"invoke\",84,0,\"callu\",[\"i32:2\"]],[\"invoke\",85,0,\"callu\",[\"i32:3\"]],[\"invoke\",86,0,\"callu\",[\"i32:4\"]],[\"invoke\",87,0,\"callu\",[\"i32:5\"]],[\"invoke\",88,0,\"callu\",[\"i32:6\"]],[\"invoke\",89,0,\"callu\",[\"i32:7\"]],[\"invoke\",90,0,\"callu\",[\"i32:100\"]],[\"invoke\",91,0,\"callu\",[\"i32:4294967295\"]],[\"module\",93,16],[\"invoke\",105,0,\"callt\",[\"i32:0\"]],[\"invoke\",106,0,\"callt\",[\"i32:1\"]],[\"file\",\"if.wast\"],[\"module\",3,17],[\"invoke\",537,0,\"empty\",[\"i32:0\"]],[\"invoke\",538,0,\"empty\",[\"i32:1\"]],[\"invoke\",539,0,\"empty\",[\"i32:100\"]],[\"invoke\",540,0,\"empty\",[\"i32:4294967294\"]],[\"invoke\",542,0,\"singular\",[\"i32:0\"]],[\"invoke\",543,0,\"singular\",[\"i32:1\"]],[\"invoke\",544,0,\"singular\",[\"i32:10\"]],[\"invoke\",545,0,\"singular\",[\"i32:4294967286\"]],[\"invoke\",547,0,\"multi\",[\"i32:0\"]],[\"invoke\",548,0,\"multi\",[\"i32:1\"]],[\"invoke\",549,0,\"multi\",[\"i32:13\"]],[\"invoke\",550,0,\"multi\",[\"i32:4294967291\"]],[\"invoke\",552,0,\"nested\",[\"i32:0\",\"i32:0\"]],[\"invoke\",553,0,\"nested\",[\"i32:1\",\"i32:0\"]],[\"invoke\",554,0,\"nested\",[\"i32:0\",\"i32:1\"]],[\"invoke\",555,0,\"nested\",[\"i32:3\",\"i32:2\"]],[\"invoke\",556,0,\"nested\",[\"i32:0\",\"i32:4294967196\"]],[\"invoke\",557,0,\"nested\",[\"i32:10\",\"i32:10\"]],[\"invoke\",558,0,\"nested\",[\"i32:0\",\"i32:4294967295\"]],[\"invoke\",559,0,\"nested\",[\"i32:4294967185\",\"i32:4294967294\"]],[\"invoke\",561,0,\"as-select-first\",[\"i32:0\"]],[\"invoke\",562,0,\"as-select-first\",[\"i32:1\"]],[\"invoke\",563,0,\"as-select-mid\",[\"i32:0\"]],[\"invoke\",564,0,\"as-select-mid\",[\"i32:1\"]],[\"invoke\",565,0,\"as-select-last\",[\"i32:0\"]],[\"invoke\",566,0,\"as-select-last\",[\"i32:1\"]],[\"invoke\",568,0,\"as-loop-first\",[\"i32:0\"]],[\"invoke\",569,0,\"as-loop-first\",[\"i32:1\"]],[\"invoke\",570,0,\"as-loop-mid\",[\"i32:0\"]],[\"invoke\",571,0,\"as-loop-mid\",[\"i32:1\"]],[\"invoke\",572,0,\"as-loop-last\",[\"i32:0\"]],[\"invoke\",573,0,\"as-loop-last\",[\"i32:1\"]],[\"invoke\",575,0,\"as-if-condition\",[\"i32:0\"]],[\"invoke\",576,0,\"as-if-condition\",[\"i32:1\"]],[\"invoke\",578,0,\"as-br_if-first\",[\"i32:0\"]],[\"invoke\",579,0,\"as-br_if-first\",[\"i32:1\"]],[\"invoke\",580,0,\"as-br_if-last\",[\"i32:0\"]],[\"invoke\",581,0,\"as-br_if-last\",[\"i32:1\"]],[\"invoke\",583,0,\"as-br_table-first\",[\"i32:0\"]],[\"invoke\",584,0,\"as-br_table-first\",[\"i32:1\"]],[\"invoke\",585,0,\"as-br_table-last\",[\"i32:0\"]],[\"invoke\",586,0,\"as-br_table-last\",[\"i32:1\"]],[\"invoke\",588,0,\"as-call_indirect-first\",[\"i32:0\"]],[\"invoke\",589,0,\"as-call_indirect-first\",[\"i32:1\"]],[\"invoke\",590,0,\"as-call_indirect-mid\",[\"i32:0\"]],[\"invoke\",591,0,\"as-call_indirect-mid\",[\"i32:1\"]],[\"invoke\",592,0,\"as-call_indirect-last\",[\"i32:0\"]],[\"invoke\",593,0,\"as-call_indirect-last\",[\"i32:1\"]],[\"invoke\",595,0,\"as-store-first\",[\"i32:0\"]],[\"invoke\",596,0,\"as-store-first\",[\"i32:1\"]],[\"invoke\",597,0,\"as-store-last\",[\"i32:0\"]],[\"invoke\",598,0,\"as-store-last\",[\"i32:1\"]],[\"invoke\",600,0,\"as-memory.grow-value\",[\"i32:0\"]],[\"invoke\",601,0,\"as-memory.grow-value\",[\"i32:1\"]],[\"invoke\",603,0,\"as-call-value\",[\"i32:0\"]],[\"invoke\",604,0,\"as-call-value\",[\"i32:1\"]],[\"invoke\",606,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",607,0,\"as-return-value\",[\"i32:1\"]],[\"invoke\",609,0,\"as-drop-operand\",[\"i32:0\"]],[\"invoke\",610,0,\"as-drop-operand\",[\"i32:1\"]],[\"invoke\",612,0,\"as-br-value\",[\"i32:0\"]],[\"invoke\",613,0,\"as-br-value\",[\"i32:1\"]],[\"invoke\",615,0,\"as-local.set-value\",[\"i32:0\"]],[\"invoke\",616,0,\"as-local.set-value\",[\"i32:1\"]],[\"invoke\",618,0,\"as-local.tee-value\",[\"i32:0\"]],[\"invoke\",619,0,\"as-local.tee-value\",[\"i32:1\"]],[\"invoke\",621,0,\"as-global.set-value\",[\"i32:0\"]],[\"invoke\",622,0,\"as-global.set-value\",[\"i32:1\"]],[\"invoke\",624,0,\"as-load-operand\",[\"i32:0\"]],[\"invoke\",625,0,\"as-load-operand\",[\"i32:1\"]],[\"invoke\",627,0,\"as-unary-operand\",[\"i32:0\"]],[\"invoke\",628,0,\"as-unary-operand\",[\"i32:1\"]],[\"invoke\",629,0,\"as-unary-operand\",[\"i32:4294967295\"]],[\"invoke\",631,0,\"as-binary-operand\",[\"i32:0\",\"i32:0\"]],[\"invoke\",632,0,\"as-binary-operand\",[\"i32:0\",\"i32:1\"]],[\"invoke\",633,0,\"as-binary-operand\",[\"i32:1\",\"i32:0\"]],[\"invoke\",634,0,\"as-binary-operand\",[\"i32:1\",\"i32:1\"]],[\"invoke\",636,0,\"as-test-operand\",[\"i32:0\"]],[\"invoke\",637,0,\"as-test-operand\",[\"i32:1\"]],[\"invoke\",639,0,\"as-compare-operand\",[\"i32:0\",\"i32:0\"]],[\"invoke\",640,0,\"as-compare-operand\",[\"i32:0\",\"i32:1\"]],[\"invoke\",641,0,\"as-compare-operand\",[\"i32:1\",\"i32:0\"]],[\"invoke\",642,0,\"as-compare-operand\",[\"i32:1\",\"i32:1\"]],[\"invoke\",644,0,\"as-binary-operands\",[\"i32:0\"]],[\"invoke\",645,0,\"as-binary-operands\",[\"i32:1\"]],[\"invoke\",647,0,\"as-compare-operands\",[\"i32:0\"]],[\"invoke\",648,0,\"as-compare-operands\",[\"i32:1\"]],[\"invoke\",650,0,\"as-mixed-operands\",[\"i32:0\"]],[\"invoke\",651,0,\"as-mixed-operands\",[\"i32:1\"]],[\"invoke\",653,0,\"break-bare\",[]],[\"invoke\",654,0,\"break-value\",[\"i32:1\"]],[\"invoke\",655,0,\"break-value\",[\"i32:0\"]],[\"invoke\",656,0,\"break-multi-value\",[\"i32:0\"]],[\"invoke\",659,0,\"break-multi-value\",[\"i32:1\"]],[\"invoke\",663,0,\"param\",[\"i32:0\"]],[\"invoke\",664,0,\"param\",[\"i32:1\"]],[\"invoke\",665,0,\"params\",[\"i32:0\"]],[\"invoke\",666,0,\"params\",[\"i32:1\"]],[\"invoke\",667,0,\"params-id\",[\"i32:0\"]],[\"invoke\",668,0,\"params-id\",[\"i32:1\"]],[\"invoke\",669,0,\"param-break\",[\"i32:0\"]],[\"invoke\",670,0,\"param-break\",[\"i32:1\"]],[\"invoke\",671,0,\"params-break\",[\"i32:0\"]],[\"invoke\",672,0,\"params-break\",[\"i32:1\"]],[\"invoke\",673,0,\"params-id-break\",[\"i32:0\"]],[\"invoke\",674,0,\"params-id-break\",[\"i32:1\"]],[\"invoke\",676,0,\"effects\",[\"i32:1\"]],[\"invoke\",677,0,\"effects\",[\"i32:0\"]],[\"invoke\",680,0,\"add64_u_with_carry\",[\"i64:0\",\"i64:0\",\"i32:0\"]],[\"invoke\",684,0,\"add64_u_with_carry\",[\"i64:100\",\"i64:124\",\"i32:0\"]],[\"invoke\",688,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:0\",\"i32:0\"]],[\"invoke\",692,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:1\",\"i32:0\"]],[\"invoke\",696,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\",\"i32:0\"]],[\"invoke\",700,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:0\",\"i32:1\"]],[\"invoke\",704,0,\"add64_u_with_carry\",[\"i64:18446744073709551615\",\"i64:1\",\"i32:1\"]],[\"invoke\",708,0,\"add64_u_with_carry\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\",\"i32:0\"]],[\"invoke\",713,0,\"add64_u_saturated\",[\"i64:0\",\"i64:0\"]],[\"invoke\",716,0,\"add64_u_saturated\",[\"i64:1230\",\"i64:23\"]],[\"invoke\",719,0,\"add64_u_saturated\",[\"i64:18446744073709551615\",\"i64:0\"]],[\"invoke\",722,0,\"add64_u_saturated\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",725,0,\"add64_u_saturated\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",728,0,\"add64_u_saturated\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",731,0,\"type-use\",[]],[\"invoke\",733,0,\"atypical-condition\",[]],[\"file\",\"labels.wast\"],[\"module\",1,18],[\"invoke\",291,0,\"block\",[]],[\"invoke\",292,0,\"loop1\",[]],[\"invoke\",293,0,\"loop2\",[]],[\"invoke\",294,0,\"loop3\",[]],[\"invoke\",295,0,\"loop4\",[\"i32:8\"]],[\"invoke\",296,0,\"loop5\",[]],[\"invoke\",297,0,\"loop6\",[]],[\"invoke\",298,0,\"if\",[]],[\"invoke\",299,0,\"if2\",[]],[\"invoke\",300,0,\"switch\",[\"i32:0\"]],[\"invoke\",301,0,\"switch\",[\"i32:1\"]],[\"invoke\",302,0,\"switch\",[\"i32:2\"]],[\"invoke\",303,0,\"switch\",[\"i32:3\"]],[\"invoke\",304,0,\"switch\",[\"i32:4\"]],[\"invoke\",305,0,\"switch\",[\"i32:5\"]],[\"invoke\",306,0,\"return\",[\"i32:0\"]],[\"invoke\",307,0,\"return\",[\"i32:1\"]],[\"invoke\",308,0,\"return\",[\"i32:2\"]],[\"invoke\",309,0,\"br_if0\",[]],[\"invoke\",310,0,\"br_if1\",[]],[\"invoke\",311,0,\"br_if2\",[]],[\"invoke\",312,0,\"br_if3\",[]],[\"invoke\",313,0,\"br\",[]],[\"invoke\",314,0,\"shadowing\",[]],[\"invoke\",315,0,\"redefinition\",[]],[\"file\",\"loop.wast\"],[\"module\",3,19],[\"invoke\",430,0,\"empty\",[]],[\"invoke\",431,0,\"singular\",[]],[\"invoke\",432,0,\"multi\",[]],[\"invoke\",433,0,\"nested\",[]],[\"invoke\",434,0,\"deep\",[]],[\"invoke\",436,0,\"as-select-first\",[]],[\"invoke\",437,0,\"as-select-mid\",[]],[\"invoke\",438,0,\"as-select-last\",[]],[\"invoke\",440,0,\"as-if-condition\",[]],[\"invoke\",441,0,\"as-if-then\",[]],[\"invoke\",442,0,\"as-if-else\",[]],[\"invoke\",444,0,\"as-br_if-first\",[]],[\"invoke\",445,0,\"as-br_if-last\",[]],[\"invoke\",447,0,\"as-br_table-first\",[]],[\"invoke\",448,0,\"as-br_table-last\",[]],[\"invoke\",450,0,\"as-call_indirect-first\",[]],[\"invoke\",451,0,\"as-call_indirect-mid\",[]],[\"invoke\",452,0,\"as-call_indirect-last\",[]],[\"invoke\",454,0,\"as-store-first\",[]],[\"invoke\",455,0,\"as-store-last\",[]],[\"invoke\",457,0,\"as-memory.grow-value\",[]],[\"invoke\",458,0,\"as-call-value\",[]],[\"invoke\",459,0,\"as-return-value\",[]],[\"invoke\",460,0,\"as-drop-operand\",[]],[\"invoke\",461,0,\"as-br-value\",[]],[\"invoke\",462,0,\"as-local.set-value\",[]],[\"invoke\",463,0,\"as-local.tee-value\",[]],[\"invoke\",464,0,\"as-global.set-value\",[]],[\"invoke\",465,0,\"as-load-operand\",[]],[\"invoke\",467,0,\"as-unary-operand\",[]],[\"invoke\",468,0,\"as-binary-operand\",[]],[\"invoke\",469,0,\"as-test-operand\",[]],[\"invoke\",470,0,\"as-compare-operand\",[]],[\"invoke\",471,0,\"as-binary-operands\",[]],[\"invoke\",472,0,\"as-compare-operands\",[]],[\"invoke\",473,0,\"as-mixed-operands\",[]],[\"invoke\",475,0,\"break-bare\",[]],[\"invoke\",476,0,\"break-value\",[]],[\"invoke\",477,0,\"break-multi-value\",[]],[\"invoke\",480,0,\"break-repeated\",[]],[\"invoke\",481,0,\"break-inner\",[]],[\"invoke\",483,0,\"param\",[]],[\"invoke\",484,0,\"params\",[]],[\"invoke\",485,0,\"params-id\",[]],[\"invoke\",486,0,\"param-break\",[]],[\"invoke\",487,0,\"params-break\",[]],[\"invoke\",488,0,\"params-id-break\",[]],[\"invoke\",490,0,\"effects\",[]],[\"invoke\",492,0,\"while\",[\"i64:0\"]],[\"invoke\",493,0,\"while\",[\"i64:1\"]],[\"invoke\",494,0,\"while\",[\"i64:2\"]],[\"invoke\",495,0,\"while\",[\"i64:3\"]],[\"invoke\",496,0,\"while\",[\"i64:5\"]],[\"invoke\",497,0,\"while\",[\"i64:20\"]],[\"invoke\",499,0,\"for\",[\"i64:0\"]],[\"invoke\",500,0,\"for\",[\"i64:1\"]],[\"invoke\",501,0,\"for\",[\"i64:2\"]],[\"invoke\",502,0,\"for\",[\"i64:3\"]],[\"invoke\",503,0,\"for\",[\"i64:5\"]],[\"invoke\",504,0,\"for\",[\"i64:20\"]],[\"invoke\",506,0,\"nesting\",[\"f32:0\",\"f32:1088421888\"]],[\"invoke\",507,0,\"nesting\",[\"f32:1088421888\",\"f32:0\"]],[\"invoke\",508,0,\"nesting\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"invoke\",509,0,\"nesting\",[\"f32:1065353216\",\"f32:1073741824\"]],[\"invoke\",510,0,\"nesting\",[\"f32:1065353216\",\"f32:1077936128\"]],[\"invoke\",511,0,\"nesting\",[\"f32:1065353216\",\"f32:1082130432\"]],[\"invoke\",512,0,\"nesting\",[\"f32:1065353216\",\"f32:1120403456\"]],[\"invoke\",513,0,\"nesting\",[\"f32:1065353216\",\"f32:1120534528\"]],[\"invoke\",514,0,\"nesting\",[\"f32:1073741824\",\"f32:1065353216\"]],[\"invoke\",515,0,\"nesting\",[\"f32:1077936128\",\"f32:1065353216\"]],[\"invoke\",516,0,\"nesting\",[\"f32:1092616192\",\"f32:1065353216\"]],[\"invoke\",517,0,\"nesting\",[\"f32:1073741824\",\"f32:1073741824\"]],[\"invoke\",518,0,\"nesting\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",519,0,\"nesting\",[\"f32:1088421888\",\"f32:1082130432\"]],[\"invoke\",520,0,\"nesting\",[\"f32:1088421888\",\"f32:1120403456\"]],[\"invoke\",521,0,\"nesting\",[\"f32:1088421888\",\"f32:1120534528\"]],[\"invoke\",523,0,\"type-use\",[]],[\"file\",\"local_get.wast\"],[\"module\",3,20],[\"invoke\",109,0,\"type-local-i32\",[]],[\"invoke\",110,0,\"type-local-i64\",[]],[\"invoke\",111,0,\"type-local-f32\",[]],[\"invoke\",112,0,\"type-local-f64\",[]],[\"invoke\",114,0,\"type-param-i32\",[\"i32:2\"]],[\"invoke\",115,0,\"type-param-i64\",[\"i64:3\"]],[\"invoke\",116,0,\"type-param-f32\",[\"f32:1082969293\"]],[\"invoke\",117,0,\"type-param-f64\",[\"f64:4617878467915022336\"]],[\"invoke\",119,0,\"as-block-value\",[\"i32:6\"]],[\"invoke\",120,0,\"as-loop-value\",[\"i32:7\"]],[\"invoke\",122,0,\"as-br-value\",[\"i32:8\"]],[\"invoke\",123,0,\"as-br_if-value\",[\"i32:9\"]],[\"invoke\",124,0,\"as-br_if-value-cond\",[\"i32:10\"]],[\"invoke\",125,0,\"as-br_table-value\",[\"i32:1\"]],[\"invoke\",127,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",129,0,\"as-if-then\",[\"i32:1\"]],[\"invoke\",130,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",133,0,\"type-mixed\",[\"i64:1\",\"f32:1074580685\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"invoke\",139,0,\"read\",[\"i64:1\",\"f32:1073741824\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"file\",\"local_set.wast\"],[\"module\",3,21],[\"invoke\",107,0,\"type-local-i32\",[]],[\"invoke\",108,0,\"type-local-i64\",[]],[\"invoke\",109,0,\"type-local-f32\",[]],[\"invoke\",110,0,\"type-local-f64\",[]],[\"invoke\",112,0,\"type-param-i32\",[\"i32:2\"]],[\"invoke\",113,0,\"type-param-i64\",[\"i64:3\"]],[\"invoke\",114,0,\"type-param-f32\",[\"f32:1082969293\"]],[\"invoke\",115,0,\"type-param-f64\",[\"f64:4617878467915022336\"]],[\"invoke\",117,0,\"as-block-value\",[\"i32:0\"]],[\"invoke\",118,0,\"as-loop-value\",[\"i32:0\"]],[\"invoke\",120,0,\"as-br-value\",[\"i32:0\"]],[\"invoke\",121,0,\"as-br_if-value\",[\"i32:0\"]],[\"invoke\",122,0,\"as-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",123,0,\"as-br_table-value\",[\"i32:0\"]],[\"invoke\",125,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",127,0,\"as-if-then\",[\"i32:1\"]],[\"invoke\",128,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",131,0,\"type-mixed\",[\"i64:1\",\"f32:1074580685\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"invoke\",137,0,\"write\",[\"i64:1\",\"f32:1073741824\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"file\",\"local_tee.wast\"],[\"module\",3,22],[\"invoke\",280,0,\"type-local-i32\",[]],[\"invoke\",281,0,\"type-local-i64\",[]],[\"invoke\",282,0,\"type-local-f32\",[]],[\"invoke\",283,0,\"type-local-f64\",[]],[\"invoke\",285,0,\"type-param-i32\",[\"i32:2\"]],[\"invoke\",286,0,\"type-param-i64\",[\"i64:3\"]],[\"invoke\",287,0,\"type-param-f32\",[\"f32:1082969293\"]],[\"invoke\",288,0,\"type-param-f64\",[\"f64:4617878467915022336\"]],[\"invoke\",290,0,\"as-block-first\",[\"i32:0\"]],[\"invoke\",291,0,\"as-block-mid\",[\"i32:0\"]],[\"invoke\",292,0,\"as-block-last\",[\"i32:0\"]],[\"invoke\",294,0,\"as-loop-first\",[\"i32:0\"]],[\"invoke\",295,0,\"as-loop-mid\",[\"i32:0\"]],[\"invoke\",296,0,\"as-loop-last\",[\"i32:0\"]],[\"invoke\",298,0,\"as-br-value\",[\"i32:0\"]],[\"invoke\",300,0,\"as-br_if-cond\",[\"i32:0\"]],[\"invoke\",301,0,\"as-br_if-value\",[\"i32:0\"]],[\"invoke\",302,0,\"as-br_if-value-cond\",[\"i32:0\"]],[\"invoke\",304,0,\"as-br_table-index\",[\"i32:0\"]],[\"invoke\",305,0,\"as-br_table-value\",[\"i32:0\"]],[\"invoke\",306,0,\"as-br_table-value-index\",[\"i32:0\"]],[\"invoke\",308,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",310,0,\"as-if-cond\",[\"i32:0\"]],[\"invoke\",311,0,\"as-if-then\",[\"i32:1\"]],[\"invoke\",312,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",314,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",315,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",316,0,\"as-select-cond\",[\"i32:0\"]],[\"invoke\",318,0,\"as-call-first\",[\"i32:0\"]],[\"invoke\",319,0,\"as-call-mid\",[\"i32:0\"]],[\"invoke\",320,0,\"as-call-last\",[\"i32:0\"]],[\"invoke\",322,0,\"as-call_indirect-first\",[\"i32:0\"]],[\"invoke\",323,0,\"as-call_indirect-mid\",[\"i32:0\"]],[\"invoke\",324,0,\"as-call_indirect-last\",[\"i32:0\"]],[\"invoke\",325,0,\"as-call_indirect-index\",[\"i32:0\"]],[\"invoke\",327,0,\"as-local.set-value\",[]],[\"invoke\",328,0,\"as-local.tee-value\",[\"i32:0\"]],[\"invoke\",329,0,\"as-global.set-value\",[]],[\"invoke\",331,0,\"as-load-address\",[\"i32:0\"]],[\"invoke\",332,0,\"as-loadN-address\",[\"i32:0\"]],[\"invoke\",333,0,\"as-store-address\",[\"i32:0\"]],[\"invoke\",334,0,\"as-store-value\",[\"i32:0\"]],[\"invoke\",335,0,\"as-storeN-address\",[\"i32:0\"]],[\"invoke\",336,0,\"as-storeN-value\",[\"i32:0\"]],[\"invoke\",338,0,\"as-unary-operand\",[\"f32:0\"]],[\"invoke\",339,0,\"as-binary-left\",[\"i32:0\"]],[\"invoke\",340,0,\"as-binary-right\",[\"i32:0\"]],[\"invoke\",341,0,\"as-test-operand\",[\"i32:0\"]],[\"invoke\",342,0,\"as-compare-left\",[\"i32:0\"]],[\"invoke\",343,0,\"as-compare-right\",[\"i32:0\"]],[\"invoke\",344,0,\"as-convert-operand\",[\"i64:0\"]],[\"invoke\",345,0,\"as-memory.grow-size\",[\"i32:0\"]],[\"invoke\",348,0,\"type-mixed\",[\"i64:1\",\"f32:1074580685\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"invoke\",354,0,\"write\",[\"i64:1\",\"f32:1073741824\",\"f64:4614613358185178726\",\"i32:4\",\"i32:5\"]],[\"invoke\",361,0,\"result\",[\"i64:18446744073709551615\",\"f32:3221225472\",\"f64:13837985395039954534\",\"i32:4294967292\",\"i32:4294967291\"]],[\"file\",\"nop.wast\"],[\"module\",3,23],[\"invoke\",306,0,\"as-func-first\",[]],[\"invoke\",307,0,\"as-func-mid\",[]],[\"invoke\",308,0,\"as-func-last\",[]],[\"invoke\",309,0,\"as-func-everywhere\",[]],[\"invoke\",311,0,\"as-drop-first\",[\"i32:0\"]],[\"invoke\",312,0,\"as-drop-last\",[\"i32:0\"]],[\"invoke\",313,0,\"as-drop-everywhere\",[\"i32:0\"]],[\"invoke\",315,0,\"as-select-first\",[\"i32:3\"]],[\"invoke\",316,0,\"as-select-mid1\",[\"i32:3\"]],[\"invoke\",317,0,\"as-select-mid2\",[\"i32:3\"]],[\"invoke\",318,0,\"as-select-last\",[\"i32:3\"]],[\"invoke\",319,0,\"as-select-everywhere\",[\"i32:3\"]],[\"invoke\",321,0,\"as-block-first\",[]],[\"invoke\",322,0,\"as-block-mid\",[]],[\"invoke\",323,0,\"as-block-last\",[]],[\"invoke\",324,0,\"as-block-everywhere\",[]],[\"invoke\",326,0,\"as-loop-first\",[]],[\"invoke\",327,0,\"as-loop-mid\",[]],[\"invoke\",328,0,\"as-loop-last\",[]],[\"invoke\",329,0,\"as-loop-everywhere\",[]],[\"invoke\",331,0,\"as-if-condition\",[\"i32:0\"]],[\"invoke\",332,0,\"as-if-condition\",[\"i32:4294967295\"]],[\"invoke\",333,0,\"as-if-then\",[\"i32:0\"]],[\"invoke\",334,0,\"as-if-then\",[\"i32:4\"]],[\"invoke\",335,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",336,0,\"as-if-else\",[\"i32:3\"]],[\"invoke\",338,0,\"as-br-first\",[\"i32:5\"]],[\"invoke\",339,0,\"as-br-last\",[\"i32:6\"]],[\"invoke\",340,0,\"as-br-everywhere\",[\"i32:7\"]],[\"invoke\",342,0,\"as-br_if-first\",[\"i32:4\"]],[\"invoke\",343,0,\"as-br_if-mid\",[\"i32:5\"]],[\"invoke\",344,0,\"as-br_if-last\",[\"i32:6\"]],[\"invoke\",345,0,\"as-br_if-everywhere\",[\"i32:7\"]],[\"invoke\",347,0,\"as-br_table-first\",[\"i32:4\"]],[\"invoke\",348,0,\"as-br_table-mid\",[\"i32:5\"]],[\"invoke\",349,0,\"as-br_table-last\",[\"i32:6\"]],[\"invoke\",350,0,\"as-br_table-everywhere\",[\"i32:7\"]],[\"invoke\",352,0,\"as-return-first\",[\"i32:5\"]],[\"invoke\",353,0,\"as-return-last\",[\"i32:6\"]],[\"invoke\",354,0,\"as-return-everywhere\",[\"i32:7\"]],[\"invoke\",356,0,\"as-call-first\",[\"i32:3\",\"i32:1\",\"i32:2\"]],[\"invoke\",357,0,\"as-call-mid1\",[\"i32:3\",\"i32:1\",\"i32:2\"]],[\"invoke\",358,0,\"as-call-mid2\",[\"i32:0\",\"i32:3\",\"i32:1\"]],[\"invoke\",359,0,\"as-call-last\",[\"i32:10\",\"i32:9\",\"i32:4294967295\"]],[\"invoke\",360,0,\"as-call-everywhere\",[\"i32:2\",\"i32:1\",\"i32:5\"]],[\"invoke\",362,0,\"as-unary-first\",[\"i32:30\"]],[\"invoke\",363,0,\"as-unary-last\",[\"i32:30\"]],[\"invoke\",364,0,\"as-unary-everywhere\",[\"i32:12\"]],[\"invoke\",366,0,\"as-binary-first\",[\"i32:3\"]],[\"invoke\",367,0,\"as-binary-mid\",[\"i32:3\"]],[\"invoke\",368,0,\"as-binary-last\",[\"i32:3\"]],[\"invoke\",369,0,\"as-binary-everywhere\",[\"i32:3\"]],[\"invoke\",371,0,\"as-test-first\",[\"i32:0\"]],[\"invoke\",372,0,\"as-test-last\",[\"i32:0\"]],[\"invoke\",373,0,\"as-test-everywhere\",[\"i32:0\"]],[\"invoke\",375,0,\"as-compare-first\",[\"i32:3\"]],[\"invoke\",376,0,\"as-compare-mid\",[\"i32:3\"]],[\"invoke\",377,0,\"as-compare-last\",[\"i32:3\"]],[\"invoke\",378,0,\"as-compare-everywhere\",[\"i32:3\"]],[\"invoke\",380,0,\"as-memory.grow-first\",[\"i32:0\"]],[\"invoke\",381,0,\"as-memory.grow-last\",[\"i32:2\"]],[\"invoke\",382,0,\"as-memory.grow-everywhere\",[\"i32:12\"]],[\"invoke\",384,0,\"as-call_indirect-first\",[]],[\"invoke\",385,0,\"as-call_indirect-mid1\",[]],[\"invoke\",386,0,\"as-call_indirect-mid2\",[]],[\"invoke\",387,0,\"as-call_indirect-last\",[]],[\"invoke\",388,0,\"as-call_indirect-everywhere\",[]],[\"invoke\",390,0,\"as-local.set-first\",[\"i32:1\"]],[\"invoke\",391,0,\"as-local.set-last\",[\"i32:1\"]],[\"invoke\",392,0,\"as-local.set-everywhere\",[\"i32:1\"]],[\"invoke\",394,0,\"as-local.tee-first\",[\"i32:1\"]],[\"invoke\",395,0,\"as-local.tee-last\",[\"i32:1\"]],[\"invoke\",396,0,\"as-local.tee-everywhere\",[\"i32:1\"]],[\"invoke\",398,0,\"as-global.set-first\",[]],[\"invoke\",399,0,\"as-global.set-last\",[]],[\"invoke\",400,0,\"as-global.set-everywhere\",[]],[\"invoke\",402,0,\"as-load-first\",[\"i32:100\"]],[\"invoke\",403,0,\"as-load-last\",[\"i32:100\"]],[\"invoke\",404,0,\"as-load-everywhere\",[\"i32:100\"]],[\"invoke\",406,0,\"as-store-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",407,0,\"as-store-mid\",[\"i32:0\",\"i32:2\"]],[\"invoke\",408,0,\"as-store-last\",[\"i32:0\",\"i32:3\"]],[\"invoke\",409,0,\"as-store-everywhere\",[\"i32:0\",\"i32:4\"]],[\"file\",\"return.wast\"],[\"module\",3,24],[\"invoke\",224,0,\"type-i32\",[]],[\"invoke\",225,0,\"type-i64\",[]],[\"invoke\",226,0,\"type-f32\",[]],[\"invoke\",227,0,\"type-f64\",[]],[\"invoke\",229,0,\"type-i32-value\",[]],[\"invoke\",230,0,\"type-i64-value\",[]],[\"invoke\",231,0,\"type-f32-value\",[]],[\"invoke\",232,0,\"type-f64-value\",[]],[\"invoke\",234,0,\"nullary\",[]],[\"invoke\",235,0,\"unary\",[]],[\"invoke\",237,0,\"as-func-first\",[]],[\"invoke\",238,0,\"as-func-mid\",[]],[\"invoke\",239,0,\"as-func-last\",[]],[\"invoke\",240,0,\"as-func-value\",[]],[\"invoke\",242,0,\"as-block-first\",[]],[\"invoke\",243,0,\"as-block-mid\",[]],[\"invoke\",244,0,\"as-block-last\",[]],[\"invoke\",245,0,\"as-block-value\",[]],[\"invoke\",247,0,\"as-loop-first\",[]],[\"invoke\",248,0,\"as-loop-mid\",[]],[\"invoke\",249,0,\"as-loop-last\",[]],[\"invoke\",251,0,\"as-br-value\",[]],[\"invoke\",253,0,\"as-br_if-cond\",[]],[\"invoke\",254,0,\"as-br_if-value\",[]],[\"invoke\",255,0,\"as-br_if-value-cond\",[]],[\"invoke\",257,0,\"as-br_table-index\",[]],[\"invoke\",258,0,\"as-br_table-value\",[]],[\"invoke\",259,0,\"as-br_table-value-index\",[]],[\"invoke\",261,0,\"as-return-value\",[]],[\"invoke\",263,0,\"as-if-cond\",[]],[\"invoke\",264,0,\"as-if-then\",[\"i32:1\",\"i32:6\"]],[\"invoke\",265,0,\"as-if-then\",[\"i32:0\",\"i32:6\"]],[\"invoke\",266,0,\"as-if-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",267,0,\"as-if-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",269,0,\"as-select-first\",[\"i32:0\",\"i32:6\"]],[\"invoke\",270,0,\"as-select-first\",[\"i32:1\",\"i32:6\"]],[\"invoke\",271,0,\"as-select-second\",[\"i32:0\",\"i32:6\"]],[\"invoke\",272,0,\"as-select-second\",[\"i32:1\",\"i32:6\"]],[\"invoke\",273,0,\"as-select-cond\",[]],[\"invoke\",275,0,\"as-call-first\",[]],[\"invoke\",276,0,\"as-call-mid\",[]],[\"invoke\",277,0,\"as-call-last\",[]],[\"invoke\",279,0,\"as-call_indirect-func\",[]],[\"invoke\",280,0,\"as-call_indirect-first\",[]],[\"invoke\",281,0,\"as-call_indirect-mid\",[]],[\"invoke\",282,0,\"as-call_indirect-last\",[]],[\"invoke\",284,0,\"as-local.set-value\",[]],[\"invoke\",285,0,\"as-local.tee-value\",[]],[\"invoke\",286,0,\"as-global.set-value\",[]],[\"invoke\",288,0,\"as-load-address\",[]],[\"invoke\",289,0,\"as-loadN-address\",[]],[\"invoke\",291,0,\"as-store-address\",[]],[\"invoke\",292,0,\"as-store-value\",[]],[\"invoke\",293,0,\"as-storeN-address\",[]],[\"invoke\",294,0,\"as-storeN-value\",[]],[\"invoke\",296,0,\"as-unary-operand\",[]],[\"invoke\",298,0,\"as-binary-left\",[]],[\"invoke\",299,0,\"as-binary-right\",[]],[\"invoke\",301,0,\"as-test-operand\",[]],[\"invoke\",303,0,\"as-compare-left\",[]],[\"invoke\",304,0,\"as-compare-right\",[]],[\"invoke\",306,0,\"as-convert-operand\",[]],[\"invoke\",308,0,\"as-memory.grow-size\",[]],[\"file\",\"select.wast\"],[\"module\",1,25],[\"invoke\",183,0,\"select-i32\",[\"i32:1\",\"i32:2\",\"i32:1\"]],[\"invoke\",184,0,\"select-i64\",[\"i64:2\",\"i64:1\",\"i32:1\"]],[\"invoke\",185,0,\"select-f32\",[\"f32:1065353216\",\"f32:1073741824\",\"i32:1\"]],[\"invoke\",186,0,\"select-f64\",[\"f64:4607182418800017408\",\"f64:4611686018427387904\",\"i32:1\"]],[\"invoke\",188,0,\"select-i32\",[\"i32:1\",\"i32:2\",\"i32:0\"]],[\"invoke\",189,0,\"select-i32\",[\"i32:2\",\"i32:1\",\"i32:0\"]],[\"invoke\",190,0,\"select-i64\",[\"i64:2\",\"i64:1\",\"i32:4294967295\"]],[\"invoke\",191,0,\"select-i64\",[\"i64:2\",\"i64:1\",\"i32:4042322160\"]],[\"invoke\",193,0,\"select-f32\",[\"f32:2143289344\",\"f32:1065353216\",\"i32:1\"]],[\"invoke\",195,0,\"select-f32\",[\"f32:2143289344\",\"f32:1065353216\",\"i32:0\"]],[\"invoke\",197,0,\"select-f32\",[\"f32:1073741824\",\"f32:2143289344\",\"i32:1\"]],[\"invoke\",199,0,\"select-f32\",[\"f32:1073741824\",\"f32:2143289344\",\"i32:0\"]],[\"invoke\",202,0,\"select-f64\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\",\"i32:1\"]],[\"invoke\",204,0,\"select-f64\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\",\"i32:0\"]],[\"invoke\",206,0,\"select-f64\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\",\"i32:1\"]],[\"invoke\",208,0,\"select-f64\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\",\"i32:0\"]],[\"invoke\",211,0,\"select-i32-t\",[\"i32:1\",\"i32:2\",\"i32:1\"]],[\"invoke\",212,0,\"select-i64-t\",[\"i64:2\",\"i64:1\",\"i32:1\"]],[\"invoke\",213,0,\"select-f32-t\",[\"f32:1065353216\",\"f32:1073741824\",\"i32:1\"]],[\"invoke\",214,0,\"select-f64-t\",[\"f64:4607182418800017408\",\"f64:4611686018427387904\",\"i32:1\"]],[\"invoke\",215,0,\"select-funcref\",[\"funcref:null\",\"funcref:null\",\"i32:1\"]],[\"invoke\",216,0,\"select-externref\",[\"externref:1\",\"externref:2\",\"i32:1\"]],[\"invoke\",218,0,\"select-i32-t\",[\"i32:1\",\"i32:2\",\"i32:0\"]],[\"invoke\",219,0,\"select-i32-t\",[\"i32:2\",\"i32:1\",\"i32:0\"]],[\"invoke\",220,0,\"select-i64-t\",[\"i64:2\",\"i64:1\",\"i32:4294967295\"]],[\"invoke\",221,0,\"select-i64-t\",[\"i64:2\",\"i64:1\",\"i32:4042322160\"]],[\"invoke\",222,0,\"select-externref\",[\"externref:1\",\"externref:2\",\"i32:0\"]],[\"invoke\",223,0,\"select-externref\",[\"externref:2\",\"externref:1\",\"i32:0\"]],[\"invoke\",225,0,\"select-f32-t\",[\"f32:2143289344\",\"f32:1065353216\",\"i32:1\"]],[\"invoke\",227,0,\"select-f32-t\",[\"f32:2143289344\",\"f32:1065353216\",\"i32:0\"]],[\"invoke\",229,0,\"select-f32-t\",[\"f32:1073741824\",\"f32:2143289344\",\"i32:1\"]],[\"invoke\",231,0,\"select-f32-t\",[\"f32:1073741824\",\"f32:2143289344\",\"i32:0\"]],[\"invoke\",234,0,\"select-f64-t\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\",\"i32:1\"]],[\"invoke\",236,0,\"select-f64-t\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\",\"i32:0\"]],[\"invoke\",238,0,\"select-f64-t\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\",\"i32:1\"]],[\"invoke\",240,0,\"select-f64-t\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\",\"i32:0\"]],[\"invoke\",243,0,\"as-select-first\",[\"i32:0\"]],[\"invoke\",244,0,\"as-select-first\",[\"i32:1\"]],[\"invoke\",245,0,\"as-select-mid\",[\"i32:0\"]],[\"invoke\",246,0,\"as-select-mid\",[\"i32:1\"]],[\"invoke\",247,0,\"as-select-last\",[\"i32:0\"]],[\"invoke\",248,0,\"as-select-last\",[\"i32:1\"]],[\"invoke\",250,0,\"as-loop-first\",[\"i32:0\"]],[\"invoke\",251,0,\"as-loop-first\",[\"i32:1\"]],[\"invoke\",252,0,\"as-loop-mid\",[\"i32:0\"]],[\"invoke\",253,0,\"as-loop-mid\",[\"i32:1\"]],[\"invoke\",254,0,\"as-loop-last\",[\"i32:0\"]],[\"invoke\",255,0,\"as-loop-last\",[\"i32:1\"]],[\"invoke\",257,0,\"as-if-condition\",[\"i32:0\"]],[\"invoke\",258,0,\"as-if-condition\",[\"i32:1\"]],[\"invoke\",259,0,\"as-if-then\",[\"i32:0\"]],[\"invoke\",260,0,\"as-if-then\",[\"i32:1\"]],[\"invoke\",261,0,\"as-if-else\",[\"i32:0\"]],[\"invoke\",262,0,\"as-if-else\",[\"i32:1\"]],[\"invoke\",264,0,\"as-br_if-first\",[\"i32:0\"]],[\"invoke\",265,0,\"as-br_if-first\",[\"i32:1\"]],[\"invoke\",266,0,\"as-br_if-last\",[\"i32:0\"]],[\"invoke\",267,0,\"as-br_if-last\",[\"i32:1\"]],[\"invoke\",269,0,\"as-br_table-first\",[\"i32:0\"]],[\"invoke\",270,0,\"as-br_table-first\",[\"i32:1\"]],[\"invoke\",271,0,\"as-br_table-last\",[\"i32:0\"]],[\"invoke\",272,0,\"as-br_table-last\",[\"i32:1\"]],[\"invoke\",274,0,\"as-call_indirect-first\",[\"i32:0\"]],[\"invoke\",275,0,\"as-call_indirect-first\",[\"i32:1\"]],[\"invoke\",276,0,\"as-call_indirect-mid\",[\"i32:0\"]],[\"invoke\",277,0,\"as-call_indirect-mid\",[\"i32:1\"]],[\"invoke\",278,0,\"as-call_indirect-last\",[\"i32:0\"]],[\"invoke\",279,0,\"as-call_indirect-last\",[\"i32:1\"]],[\"invoke\",281,0,\"as-store-first\",[\"i32:0\"]],[\"invoke\",282,0,\"as-store-first\",[\"i32:1\"]],[\"invoke\",283,0,\"as-store-last\",[\"i32:0\"]],[\"invoke\",284,0,\"as-store-last\",[\"i32:1\"]],[\"invoke\",286,0,\"as-memory.grow-value\",[\"i32:0\"]],[\"invoke\",287,0,\"as-memory.grow-value\",[\"i32:1\"]],[\"invoke\",289,0,\"as-call-value\",[\"i32:0\"]],[\"invoke\",290,0,\"as-call-value\",[\"i32:1\"]],[\"invoke\",291,0,\"as-return-value\",[\"i32:0\"]],[\"invoke\",292,0,\"as-return-value\",[\"i32:1\"]],[\"invoke\",293,0,\"as-drop-operand\",[\"i32:0\"]],[\"invoke\",294,0,\"as-drop-operand\",[\"i32:1\"]],[\"invoke\",295,0,\"as-br-value\",[\"i32:0\"]],[\"invoke\",296,0,\"as-br-value\",[\"i32:1\"]],[\"invoke\",297,0,\"as-local.set-value\",[\"i32:0\"]],[\"invoke\",298,0,\"as-local.set-value\",[\"i32:1\"]],[\"invoke\",299,0,\"as-local.tee-value\",[\"i32:0\"]],[\"invoke\",300,0,\"as-local.tee-value\",[\"i32:1\"]],[\"invoke\",301,0,\"as-global.set-value\",[\"i32:0\"]],[\"invoke\",302,0,\"as-global.set-value\",[\"i32:1\"]],[\"invoke\",303,0,\"as-load-operand\",[\"i32:0\"]],[\"invoke\",304,0,\"as-load-operand\",[\"i32:1\"]],[\"invoke\",306,0,\"as-unary-operand\",[\"i32:0\"]],[\"invoke\",307,0,\"as-unary-operand\",[\"i32:1\"]],[\"invoke\",308,0,\"as-binary-operand\",[\"i32:0\"]],[\"invoke\",309,0,\"as-binary-operand\",[\"i32:1\"]],[\"invoke\",310,0,\"as-test-operand\",[\"i32:0\"]],[\"invoke\",311,0,\"as-test-operand\",[\"i32:1\"]],[\"invoke\",312,0,\"as-compare-left\",[\"i32:0\"]],[\"invoke\",313,0,\"as-compare-left\",[\"i32:1\"]],[\"invoke\",314,0,\"as-compare-right\",[\"i32:0\"]],[\"invoke\",315,0,\"as-compare-right\",[\"i32:1\"]],[\"invoke\",316,0,\"as-convert-operand\",[\"i32:0\"]],[\"invoke\",317,0,\"as-convert-operand\",[\"i32:1\"]],[\"module\",518,26],[\"file\",\"stack.wast\"],[\"module\",1,27],[\"invoke\",146,0,\"fac-expr\",[\"i64:25\"]],[\"invoke\",147,0,\"fac-stack\",[\"i64:25\"]],[\"invoke\",148,0,\"fac-mixed\",[\"i64:25\"]],[\"invoke\",150,0,\"not-quite-a-tree\",[]],[\"invoke\",151,0,\"not-quite-a-tree\",[]],[\"module\",156,28],[\"file\",\"switch.wast\"],[\"module\",1,29],[\"invoke\",120,0,\"stmt\",[\"i32:0\"]],[\"invoke\",121,0,\"stmt\",[\"i32:1\"]],[\"invoke\",122,0,\"stmt\",[\"i32:2\"]],[\"invoke\",123,0,\"stmt\",[\"i32:3\"]],[\"invoke\",124,0,\"stmt\",[\"i32:4\"]],[\"invoke\",125,0,\"stmt\",[\"i32:5\"]],[\"invoke\",126,0,\"stmt\",[\"i32:6\"]],[\"invoke\",127,0,\"stmt\",[\"i32:7\"]],[\"invoke\",128,0,\"stmt\",[\"i32:4294967286\"]],[\"invoke\",130,0,\"expr\",[\"i64:0\"]],[\"invoke\",131,0,\"expr\",[\"i64:1\"]],[\"invoke\",132,0,\"expr\",[\"i64:2\"]],[\"invoke\",133,0,\"expr\",[\"i64:3\"]],[\"invoke\",134,0,\"expr\",[\"i64:6\"]],[\"invoke\",135,0,\"expr\",[\"i64:7\"]],[\"invoke\",136,0,\"expr\",[\"i64:18446744073709551606\"]],[\"invoke\",138,0,\"arg\",[\"i32:0\"]],[\"invoke\",139,0,\"arg\",[\"i32:1\"]],[\"invoke\",140,0,\"arg\",[\"i32:2\"]],[\"invoke\",141,0,\"arg\",[\"i32:3\"]],[\"invoke\",142,0,\"arg\",[\"i32:4\"]],[\"invoke\",143,0,\"arg\",[\"i32:5\"]],[\"invoke\",144,0,\"arg\",[\"i32:6\"]],[\"invoke\",145,0,\"arg\",[\"i32:7\"]],[\"invoke\",146,0,\"arg\",[\"i32:8\"]],[\"invoke\",148,0,\"corner\",[]],[\"file\",\"unreachable.wast\"],[\"module\",3,30],[\"invoke\",221,0,\"type-i32\",[]],[\"invoke\",222,0,\"type-i64\",[]],[\"invoke\",223,0,\"type-f32\",[]],[\"invoke\",224,0,\"type-f64\",[]],[\"invoke\",226,0,\"as-func-first\",[]],[\"invoke\",227,0,\"as-func-mid\",[]],[\"invoke\",228,0,\"as-func-last\",[]],[\"invoke\",229,0,\"as-func-value\",[]],[\"invoke\",231,0,\"as-block-first\",[]],[\"invoke\",232,0,\"as-block-mid\",[]],[\"invoke\",233,0,\"as-block-last\",[]],[\"invoke\",234,0,\"as-block-value\",[]],[\"invoke\",235,0,\"as-block-broke\",[]],[\"invoke\",237,0,\"as-loop-first\",[]],[\"invoke\",238,0,\"as-loop-mid\",[]],[\"invoke\",239,0,\"as-loop-last\",[]],[\"invoke\",240,0,\"as-loop-broke\",[]],[\"invoke\",242,0,\"as-br-value\",[]],[\"invoke\",244,0,\"as-br_if-cond\",[]],[\"invoke\",245,0,\"as-br_if-value\",[]],[\"invoke\",246,0,\"as-br_if-value-cond\",[]],[\"invoke\",248,0,\"as-br_table-index\",[]],[\"invoke\",249,0,\"as-br_table-value\",[]],[\"invoke\",250,0,\"as-br_table-value-2\",[]],[\"invoke\",251,0,\"as-br_table-value-index\",[]],[\"invoke\",252,0,\"as-br_table-value-and-index\",[]],[\"invoke\",254,0,\"as-return-value\",[]],[\"invoke\",256,0,\"as-if-cond\",[]],[\"invoke\",257,0,\"as-if-then\",[\"i32:1\",\"i32:6\"]],[\"invoke\",258,0,\"as-if-then\",[\"i32:0\",\"i32:6\"]],[\"invoke\",259,0,\"as-if-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",260,0,\"as-if-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",261,0,\"as-if-then-no-else\",[\"i32:1\",\"i32:6\"]],[\"invoke\",262,0,\"as-if-then-no-else\",[\"i32:0\",\"i32:6\"]],[\"invoke\",264,0,\"as-select-first\",[\"i32:0\",\"i32:6\"]],[\"invoke\",265,0,\"as-select-first\",[\"i32:1\",\"i32:6\"]],[\"invoke\",266,0,\"as-select-second\",[\"i32:0\",\"i32:6\"]],[\"invoke\",267,0,\"as-select-second\",[\"i32:1\",\"i32:6\"]],[\"invoke\",268,0,\"as-select-cond\",[]],[\"invoke\",270,0,\"as-call-first\",[]],[\"invoke\",271,0,\"as-call-mid\",[]],[\"invoke\",272,0,\"as-call-last\",[]],[\"invoke\",274,0,\"as-call_indirect-func\",[]],[\"invoke\",275,0,\"as-call_indirect-first\",[]],[\"invoke\",276,0,\"as-call_indirect-mid\",[]],[\"invoke\",277,0,\"as-call_indirect-last\",[]],[\"invoke\",279,0,\"as-local.set-value\",[]],[\"invoke\",280,0,\"as-local.tee-value\",[]],[\"invoke\",281,0,\"as-global.set-value\",[]],[\"invoke\",283,0,\"as-load-address\",[]],[\"invoke\",284,0,\"as-loadN-address\",[]],[\"invoke\",286,0,\"as-store-address\",[]],[\"invoke\",287,0,\"as-store-value\",[]],[\"invoke\",288,0,\"as-storeN-address\",[]],[\"invoke\",289,0,\"as-storeN-value\",[]],[\"invoke\",291,0,\"as-unary-operand\",[]],[\"invoke\",293,0,\"as-binary-left\",[]],[\"invoke\",294,0,\"as-binary-right\",[]],[\"invoke\",296,0,\"as-test-operand\",[]],[\"invoke\",298,0,\"as-compare-left\",[]],[\"invoke\",299,0,\"as-compare-right\",[]],[\"invoke\",301,0,\"as-convert-operand\",[]],[\"invoke\",303,0,\"as-memory.grow-size\",[]],[\"file\",\"unwind.wast\"],[\"module\",3,31],[\"invoke\",212,0,\"func-unwind-by-unreachable\",[]],[\"invoke\",213,0,\"func-unwind-by-br\",[]],[\"invoke\",214,0,\"func-unwind-by-br-value\",[]],[\"invoke\",215,0,\"func-unwind-by-br_if\",[]],[\"invoke\",216,0,\"func-unwind-by-br_if-value\",[]],[\"invoke\",217,0,\"func-unwind-by-br_table\",[]],[\"invoke\",218,0,\"func-unwind-by-br_table-value\",[]],[\"invoke\",219,0,\"func-unwind-by-return\",[]],[\"invoke\",221,0,\"block-unwind-by-unreachable\",[]],[\"invoke\",222,0,\"block-unwind-by-br\",[]],[\"invoke\",223,0,\"block-unwind-by-br-value\",[]],[\"invoke\",224,0,\"block-unwind-by-br_if\",[]],[\"invoke\",225,0,\"block-unwind-by-br_if-value\",[]],[\"invoke\",226,0,\"block-unwind-by-br_table\",[]],[\"invoke\",227,0,\"block-unwind-by-br_table-value\",[]],[\"invoke\",228,0,\"block-unwind-by-return\",[]],[\"invoke\",230,0,\"block-nested-unwind-by-unreachable\",[]],[\"invoke\",231,0,\"block-nested-unwind-by-br\",[]],[\"invoke\",232,0,\"block-nested-unwind-by-br-value\",[]],[\"invoke\",233,0,\"block-nested-unwind-by-br_if\",[]],[\"invoke\",234,0,\"block-nested-unwind-by-br_if-value\",[]],[\"invoke\",235,0,\"block-nested-unwind-by-br_table\",[]],[\"invoke\",236,0,\"block-nested-unwind-by-br_table-value\",[]],[\"invoke\",237,0,\"block-nested-unwind-by-return\",[]],[\"invoke\",239,0,\"unary-after-unreachable\",[]],[\"invoke\",240,0,\"unary-after-br\",[]],[\"invoke\",241,0,\"unary-after-br_if\",[]],[\"invoke\",242,0,\"unary-after-br_table\",[]],[\"invoke\",243,0,\"unary-after-return\",[]],[\"invoke\",245,0,\"binary-after-unreachable\",[]],[\"invoke\",246,0,\"binary-after-br\",[]],[\"invoke\",247,0,\"binary-after-br_if\",[]],[\"invoke\",248,0,\"binary-after-br_table\",[]],[\"invoke\",249,0,\"binary-after-return\",[]],[\"invoke\",251,0,\"select-after-unreachable\",[]],[\"invoke\",252,0,\"select-after-br\",[]],[\"invoke\",253,0,\"select-after-br_if\",[]],[\"invoke\",254,0,\"select-after-br_table\",[]],[\"invoke\",255,0,\"select-after-return\",[]],[\"invoke\",257,0,\"block-value-after-unreachable\",[]],[\"invoke\",258,0,\"block-value-after-br\",[]],[\"invoke\",259,0,\"block-value-after-br_if\",[]],[\"invoke\",260,0,\"block-value-after-br_table\",[]],[\"invoke\",261,0,\"block-value-after-return\",[]],[\"invoke\",263,0,\"loop-value-after-unreachable\",[]],[\"invoke\",264,0,\"loop-value-after-br\",[]],[\"invoke\",265,0,\"loop-value-after-br_if\",[]],[\"invoke\",266,0,\"loop-value-after-br_table\",[]],[\"invoke\",267,0,\"loop-value-after-return\",[]],[\"file\",\"left-to-right.wast\"],[\"module\",1,32],[\"invoke\",181,0,\"i32_add\",[]],[\"invoke\",181,0,\"i64_add\",[]],[\"invoke\",182,0,\"i32_sub\",[]],[\"invoke\",182,0,\"i64_sub\",[]],[\"invoke\",183,0,\"i32_mul\",[]],[\"invoke\",183,0,\"i64_mul\",[]],[\"invoke\",184,0,\"i32_div_s\",[]],[\"invoke\",184,0,\"i64_div_s\",[]],[\"invoke\",185,0,\"i32_div_u\",[]],[\"invoke\",185,0,\"i64_div_u\",[]],[\"invoke\",186,0,\"i32_rem_s\",[]],[\"invoke\",186,0,\"i64_rem_s\",[]],[\"invoke\",187,0,\"i32_rem_u\",[]],[\"invoke\",187,0,\"i64_rem_u\",[]],[\"invoke\",188,0,\"i32_and\",[]],[\"invoke\",188,0,\"i64_and\",[]],[\"invoke\",189,0,\"i32_or\",[]],[\"invoke\",189,0,\"i64_or\",[]],[\"invoke\",190,0,\"i32_xor\",[]],[\"invoke\",190,0,\"i64_xor\",[]],[\"invoke\",191,0,\"i32_shl\",[]],[\"invoke\",191,0,\"i64_shl\",[]],[\"invoke\",192,0,\"i32_shr_u\",[]],[\"invoke\",192,0,\"i64_shr_u\",[]],[\"invoke\",193,0,\"i32_shr_s\",[]],[\"invoke\",193,0,\"i64_shr_s\",[]],[\"invoke\",194,0,\"i32_eq\",[]],[\"invoke\",194,0,\"i64_eq\",[]],[\"invoke\",195,0,\"i32_ne\",[]],[\"invoke\",195,0,\"i64_ne\",[]],[\"invoke\",196,0,\"i32_lt_s\",[]],[\"invoke\",196,0,\"i64_lt_s\",[]],[\"invoke\",197,0,\"i32_le_s\",[]],[\"invoke\",197,0,\"i64_le_s\",[]],[\"invoke\",198,0,\"i32_lt_u\",[]],[\"invoke\",198,0,\"i64_lt_u\",[]],[\"invoke\",199,0,\"i32_le_u\",[]],[\"invoke\",199,0,\"i64_le_u\",[]],[\"invoke\",200,0,\"i32_gt_s\",[]],[\"invoke\",200,0,\"i64_gt_s\",[]],[\"invoke\",201,0,\"i32_ge_s\",[]],[\"invoke\",201,0,\"i64_ge_s\",[]],[\"invoke\",202,0,\"i32_gt_u\",[]],[\"invoke\",202,0,\"i64_gt_u\",[]],[\"invoke\",203,0,\"i32_ge_u\",[]],[\"invoke\",203,0,\"i64_ge_u\",[]],[\"invoke\",204,0,\"i32_store\",[]],[\"invoke\",204,0,\"i64_store\",[]],[\"invoke\",205,0,\"i32_store8\",[]],[\"invoke\",205,0,\"i64_store8\",[]],[\"invoke\",206,0,\"i32_store16\",[]],[\"invoke\",206,0,\"i64_store16\",[]],[\"invoke\",207,0,\"i64_store32\",[]],[\"invoke\",208,0,\"i32_call\",[]],[\"invoke\",208,0,\"i64_call\",[]],[\"invoke\",209,0,\"i32_call_indirect\",[]],[\"invoke\",210,0,\"i64_call_indirect\",[]],[\"invoke\",211,0,\"i32_select\",[]],[\"invoke\",211,0,\"i64_select\",[]],[\"invoke\",213,0,\"f32_add\",[]],[\"invoke\",213,0,\"f64_add\",[]],[\"invoke\",214,0,\"f32_sub\",[]],[\"invoke\",214,0,\"f64_sub\",[]],[\"invoke\",215,0,\"f32_mul\",[]],[\"invoke\",215,0,\"f64_mul\",[]],[\"invoke\",216,0,\"f32_div\",[]],[\"invoke\",216,0,\"f64_div\",[]],[\"invoke\",217,0,\"f32_copysign\",[]],[\"invoke\",217,0,\"f64_copysign\",[]],[\"invoke\",218,0,\"f32_eq\",[]],[\"invoke\",218,0,\"f64_eq\",[]],[\"invoke\",219,0,\"f32_ne\",[]],[\"invoke\",219,0,\"f64_ne\",[]],[\"invoke\",220,0,\"f32_lt\",[]],[\"invoke\",220,0,\"f64_lt\",[]],[\"invoke\",221,0,\"f32_le\",[]],[\"invoke\",221,0,\"f64_le\",[]],[\"invoke\",222,0,\"f32_gt\",[]],[\"invoke\",222,0,\"f64_gt\",[]],[\"invoke\",223,0,\"f32_ge\",[]],[\"invoke\",223,0,\"f64_ge\",[]],[\"invoke\",224,0,\"f32_min\",[]],[\"invoke\",224,0,\"f64_min\",[]],[\"invoke\",225,0,\"f32_max\",[]],[\"invoke\",225,0,\"f64_max\",[]],[\"invoke\",226,0,\"f32_store\",[]],[\"invoke\",226,0,\"f64_store\",[]],[\"invoke\",227,0,\"f32_call\",[]],[\"invoke\",227,0,\"f64_call\",[]],[\"invoke\",228,0,\"f32_call_indirect\",[]],[\"invoke\",229,0,\"f64_call_indirect\",[]],[\"invoke\",230,0,\"f32_select\",[]],[\"invoke\",230,0,\"f64_select\",[]],[\"invoke\",232,0,\"br_if\",[]],[\"invoke\",233,0,\"br_table\",[]],[\"file\",\"global.wast\"],[\"module\",3,33],[\"invoke\",196,0,\"get-a\",[]],[\"invoke\",197,0,\"get-b\",[]],[\"invoke\",198,0,\"get-r\",[]],[\"invoke\",199,0,\"get-mr\",[]],[\"invoke\",200,0,\"get-x\",[]],[\"invoke\",201,0,\"get-y\",[]],[\"invoke\",202,0,\"get-z1\",[]],[\"invoke\",203,0,\"get-z2\",[]],[\"invoke\",205,0,\"get-3\",[]],[\"invoke\",206,0,\"get-4\",[]],[\"invoke\",207,0,\"get-7\",[]],[\"invoke\",208,0,\"get-8\",[]],[\"invoke\",210,0,\"set-x\",[\"i32:6\"]],[\"invoke\",211,0,\"set-y\",[\"i64:7\"]],[\"invoke\",213,0,\"set-7\",[\"f32:1090519040\"]],[\"invoke\",214,0,\"set-8\",[\"f64:4621256167635550208\"]],[\"invoke\",216,0,\"get-x\",[]],[\"invoke\",217,0,\"get-y\",[]],[\"invoke\",218,0,\"get-7\",[]],[\"invoke\",219,0,\"get-8\",[]],[\"invoke\",221,0,\"set-7\",[\"f32:1090519040\"]],[\"invoke\",222,0,\"set-8\",[\"f64:4621256167635550208\"]],[\"invoke\",223,0,\"set-mr\",[\"externref:10\"]],[\"invoke\",225,0,\"get-x\",[]],[\"invoke\",226,0,\"get-y\",[]],[\"invoke\",227,0,\"get-7\",[]],[\"invoke\",228,0,\"get-8\",[]],[\"invoke\",229,0,\"get-mr\",[]],[\"invoke\",231,0,\"as-select-first\",[]],[\"invoke\",232,0,\"as-select-mid\",[]],[\"invoke\",233,0,\"as-select-last\",[]],[\"invoke\",235,0,\"as-loop-first\",[]],[\"invoke\",236,0,\"as-loop-mid\",[]],[\"invoke\",237,0,\"as-loop-last\",[]],[\"invoke\",239,0,\"as-if-condition\",[]],[\"invoke\",240,0,\"as-if-then\",[]],[\"invoke\",241,0,\"as-if-else\",[]],[\"invoke\",243,0,\"as-br_if-first\",[]],[\"invoke\",244,0,\"as-br_if-last\",[]],[\"invoke\",246,0,\"as-br_table-first\",[]],[\"invoke\",247,0,\"as-br_table-last\",[]],[\"invoke\",249,0,\"as-call_indirect-first\",[]],[\"invoke\",250,0,\"as-call_indirect-mid\",[]],[\"invoke\",251,0,\"as-call_indirect-last\",[]],[\"invoke\",253,0,\"as-store-first\",[]],[\"invoke\",254,0,\"as-store-last\",[]],[\"invoke\",255,0,\"as-load-operand\",[]],[\"invoke\",256,0,\"as-memory.grow-value\",[]],[\"invoke\",258,0,\"as-call-value\",[]],[\"invoke\",260,0,\"as-return-value\",[]],[\"invoke\",261,0,\"as-drop-operand\",[]],[\"invoke\",262,0,\"as-br-value\",[]],[\"invoke\",264,0,\"as-local.set-value\",[\"i32:1\"]],[\"invoke\",265,0,\"as-local.tee-value\",[\"i32:1\"]],[\"invoke\",266,0,\"as-global.set-value\",[]],[\"invoke\",268,0,\"as-unary-operand\",[]],[\"invoke\",269,0,\"as-binary-operand\",[]],[\"invoke\",270,0,\"as-compare-operand\",[]],[\"module\",283,34],[\"module\",284,34],[\"module\",375,35],[\"module\",405,36],[\"file\",\"start.wast\"],[\"module\",21,37],[\"invoke\",45,0,\"get\",[]],[\"invoke\",46,0,\"inc\",[]],[\"invoke\",47,0,\"get\",[]],[\"invoke\",48,0,\"inc\",[]],[\"invoke\",49,0,\"get\",[]],[\"module\",51,37],[\"invoke\",74,0,\"get\",[]],[\"invoke\",75,0,\"inc\",[]],[\"invoke\",76,0,\"get\",[]],[\"invoke\",77,0,\"inc\",[]],[\"invoke\",78,0,\"get\",[]],[\"module\",80,38],[\"module\",86,39],[\"module\",92,40],[\"instantiate\",98,41]]"; diff --git a/tests/corpus/webassembly-spec-control/main.mjs b/tests/corpus/webassembly-spec-control/main.mjs new file mode 100644 index 000000000..2451a5a1c --- /dev/null +++ b/tests/corpus/webassembly-spec-control/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (26 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-float/data.mjs b/tests/corpus/webassembly-spec-float/data.mjs new file mode 100644 index 000000000..814d702e3 --- /dev/null +++ b/tests/corpus/webassembly-spec-float/data.mjs @@ -0,0 +1,113 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * f32.wast, f64.wast, f32_cmp.wast, f64_cmp.wast, f32_bitwise.wast, f64_bitwise.wast, float_exprs.wast, float_literals.wast, float_misc.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABDAJgAn19AX1gAX0BfQMMCwAAAAABAAABAQEBB00LA2FkZAAAA3N1YgABA211bAACA2RpdgADBHNxcnQABANtaW4ABQNtYXgABgRjZWlsAAcFZmxvb3IACAV0cnVuYwAJB25lYXJlc3QACgpPCwcAIAAgAZILBwAgACABkwsHACAAIAGUCwcAIAAgAZULBQAgAJELBwAgACABlgsHACAAIAGXCwUAIACNCwUAIACOCwUAIACPCwUAIACQCw==", + "AGFzbQEAAAABDAJgAnx8AXxgAXwBfAMMCwAAAAABAAABAQEBB00LA2FkZAAAA3N1YgABA211bAACA2RpdgADBHNxcnQABANtaW4ABQNtYXgABgRjZWlsAAcFZmxvb3IACAV0cnVuYwAJB25lYXJlc3QACgpPCwcAIAAgAaALBwAgACABoQsHACAAIAGiCwcAIAAgAaMLBQAgAJ8LBwAgACABpAsHACAAIAGlCwUAIACbCwUAIACcCwUAIACdCwUAIACeCw==", + "AGFzbQEAAAABBwFgAn19AX8DBwYAAAAAAAAHHwYCZXEAAAJuZQABAmx0AAICbGUAAwJndAAEAmdlAAUKMQYHACAAIAFbCwcAIAAgAVwLBwAgACABXQsHACAAIAFfCwcAIAAgAV4LBwAgACABYAs=", + "AGFzbQEAAAABBwFgAnx8AX8DBwYAAAAAAAAHHwYCZXEAAAJuZQABAmx0AAICbGUAAwJndAAEAmdlAAUKMQYHACAAIAFhCwcAIAAgAWILBwAgACABYwsHACAAIAFlCwcAIAAgAWQLBwAgACABZgs=", + "AGFzbQEAAAABDAJgAX0BfWACfX0BfQMEAwAAAQcYAwNhYnMAAANuZWcAAQhjb3B5c2lnbgACChUDBQAgAIsLBQAgAIwLBwAgACABmAs=", + "AGFzbQEAAAABDAJgAXwBfGACfHwBfAMEAwAAAQcYAwNhYnMAAANuZWcAAQhjb3B5c2lnbgACChUDBQAgAJkLBQAgAJoLBwAgACABpgs=", + "AGFzbQEAAAABCAFgA3x8fAF8AwIBAAcWARJmNjQubm9fY29udHJhY3Rpb24AAAoMAQoAIAAgAaIgAqAL", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBxsCCmYzMi5ub19mbWEAAApmNjQubm9fZm1hAAEKFwIKACAAIAGUIAKSCwoAIAAgAaIgAqAL", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfYWRkX3plcm8AABRmNjQubm9fZm9sZF9hZGRfemVybwABChsCCgAgAEMAAAAAkgsOACAARAAAAAAAAAAAoAs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfemVyb19zdWIAABRmNjQubm9fZm9sZF96ZXJvX3N1YgABChsCCgBDAAAAACAAkwsOAEQAAAAAAAAAACAAoQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfc3ViX3plcm8AABRmNjQubm9fZm9sZF9zdWJfemVybwABChsCCgAgAEMAAAAAkwsOACAARAAAAAAAAAAAoQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbXVsX3plcm8AABRmNjQubm9fZm9sZF9tdWxfemVybwABChsCCgAgAEMAAAAAlAsOACAARAAAAAAAAAAAogs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfbXVsX29uZQAAE2Y2NC5ub19mb2xkX211bF9vbmUAAQobAgoAIABDAACAP5QLDgAgAEQAAAAAAADwP6IL", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfemVyb19kaXYAABRmNjQubm9fZm9sZF96ZXJvX2RpdgABChsCCgBDAAAAACAAlQsOAEQAAAAAAAAAACAAows=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfZGl2X29uZQAAE2Y2NC5ub19mb2xkX2Rpdl9vbmUAAQobAgoAIABDAACAP5ULDgAgAEQAAAAAAADwP6ML", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X25lZzEAABRmNjQubm9fZm9sZF9kaXZfbmVnMQABChsCCgAgAEMAAIC/lQsOACAARAAAAAAAAPC/ows=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbmVnMF9zdWIAABRmNjQubm9fZm9sZF9uZWcwX3N1YgABChsCCgBDAAAAgCAAkwsOAEQAAAAAAAAAgCAAoQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbmVnMV9tdWwAABRmNjQubm9fZm9sZF9uZWcxX211bAABChsCCgBDAACAvyAAlAsOAEQAAAAAAADwvyAAogs=", + "AGFzbQEAAAABCwJgAX0Bf2ABfAF/AwMCAAEHLQITZjMyLm5vX2ZvbGRfZXFfc2VsZgAAE2Y2NC5ub19mb2xkX2VxX3NlbGYAAQoRAgcAIAAgAFsLBwAgACAAYQs=", + "AGFzbQEAAAABCwJgAX0Bf2ABfAF/AwMCAAEHLQITZjMyLm5vX2ZvbGRfbmVfc2VsZgAAE2Y2NC5ub19mb2xkX25lX3NlbGYAAQoRAgcAIAAgAFwLBwAgACAAYgs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfc3ViX3NlbGYAABRmNjQubm9fZm9sZF9zdWJfc2VsZgABChECBwAgACAAkwsHACAAIAChCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X3NlbGYAABRmNjQubm9fZm9sZF9kaXZfc2VsZgABChECBwAgACAAlQsHACAAIACjCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHKQIRZjMyLm5vX2ZvbGRfZGl2XzMAABFmNjQubm9fZm9sZF9kaXZfMwABChsCCgAgAEMAAEBAlQsOACAARAAAAAAAAAhAows=", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABByECDWYzMi5ub19mYWN0b3IAAA1mNjQubm9fZmFjdG9yAAEKHQINACAAIAKUIAEgApSSCw0AIAAgAqIgASACoqAL", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBykCEWYzMi5ub19kaXN0cmlidXRlAAARZjY0Lm5vX2Rpc3RyaWJ1dGUAAQoXAgoAIAAgAZIgApQLCgAgACABoCACogs=", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBzMCFmYzMi5ub19yZWdyb3VwX2Rpdl9tdWwAABZmNjQubm9fcmVncm91cF9kaXZfbXVsAAEKFwIKACAAIAEgApWUCwoAIAAgASACo6IL", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBzMCFmYzMi5ub19yZWdyb3VwX211bF9kaXYAABZmNjQubm9fcmVncm91cF9tdWxfZGl2AAEKFwIKACAAIAGUIAKVCwoAIAAgAaIgAqML", + "AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHMwIWZjMyLm5vX3JlYXNzb2NpYXRlX2FkZAAAFmY2NC5ub19yZWFzc29jaWF0ZV9hZGQAAQodAg0AIAAgAZIgApIgA5ILDQAgACABoCACoCADoAs=", + "AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHMwIWZjMyLm5vX3JlYXNzb2NpYXRlX211bAAAFmY2NC5ub19yZWFzc29jaWF0ZV9tdWwAAQodAg0AIAAgAZQgApQgA5QLDQAgACABoiACoiADogs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHKQIRZjMyLm5vX2ZvbGRfZGl2XzAAABFmNjQubm9fZm9sZF9kaXZfMAABChsCCgAgAEMAAAAAlQsOACAARAAAAAAAAAAAows=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfZGl2X25lZzAAABRmNjQubm9fZm9sZF9kaXZfbmVnMAABChsCCgAgAEMAAACAlQsOACAARAAAAAAAAACAows=", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcvAhRmMzIubm9fZm9sZF90b19oeXBvdAAAFGY2NC5ub19mb2xkX3RvX2h5cG90AAEKHwIOACAAIACUIAEgAZSSkQsOACAAIACiIAEgAaKgnws=", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHIQEdZjMyLm5vX2FwcHJveGltYXRlX3JlY2lwcm9jYWwAAAoMAQoAQwAAgD8gAJUL", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHRAIiZjMyLm5vX2FwcHJveGltYXRlX3JlY2lwcm9jYWxfc3FydAAAG2Y2NC5ub19mdXNlX3JlY2lwcm9jYWxfc3FydAABCh0CCwBDAACAPyAAkZULDwBEAAAAAAAA8D8gAJ+jCw==", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHJgEiZjMyLm5vX2FwcHJveGltYXRlX3NxcnRfcmVjaXByb2NhbAAACg0BCwBDAACAPyAAlZEL", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdRBBFpMzIubm9fZm9sZF9mMzJfcwAAEWkzMi5ub19mb2xkX2YzMl91AAERaTY0Lm5vX2ZvbGRfZjY0X3MAAhFpNjQubm9fZm9sZF9mNjRfdQADCh0EBgAgALKoCwYAIACzqQsGACAAubALBgAgALqxCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9hZGRfc3ViAAATZjY0Lm5vX2ZvbGRfYWRkX3N1YgABChcCCgAgACABkiABkwsKACAAIAGgIAGhCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9zdWJfYWRkAAATZjY0Lm5vX2ZvbGRfc3ViX2FkZAABChcCCgAgACABkyABkgsKACAAIAGhIAGgCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9tdWxfZGl2AAATZjY0Lm5vX2ZvbGRfbXVsX2RpdgABChcCCgAgACABlCABlQsKACAAIAGiIAGjCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9kaXZfbXVsAAATZjY0Lm5vX2ZvbGRfZGl2X211bAABChcCCgAgACABlSABlAsKACAAIAGjIAGiCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHMQIVZjMyLm5vX2ZvbGRfZGl2Ml9tdWwyAAAVZjY0Lm5vX2ZvbGRfZGl2Ml9tdWwyAAEKKwIQACAAQwAAAECVQwAAAECUCxgAIABEAAAAAAAAAECjRAAAAAAAAABAogs=", + "AGFzbQEAAAABBgFgAXwBfAMCAQAHGgEWbm9fZm9sZF9kZW1vdGVfcHJvbW90ZQAACggBBgAgALa7Cw==", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHGgEWbm9fZm9sZF9wcm9tb3RlX2RlbW90ZQAACggBBgAgALu2Cw==", + "AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfYWRkAAAcbm9fZGVtb3RlX21peGVkX2FkZF9jb21tdXRlZAABChUCCQAgACABu6C2CwkAIAC7IAGgtgs=", + "AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfc3ViAAAcbm9fZGVtb3RlX21peGVkX3N1Yl9jb21tdXRlZAABChUCCQAgACABu6G2CwkAIAC7IAGhtgs=", + "AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfbXVsAAAcbm9fZGVtb3RlX21peGVkX211bF9jb21tdXRlZAABChUCCQAgACABu6K2CwkAIAC7IAGitgs=", + "AGFzbQEAAAABDQJgAnx9AX1gAn18AX0DAwIAAQc2AhNub19kZW1vdGVfbWl4ZWRfZGl2AAAcbm9fZGVtb3RlX21peGVkX2Rpdl9jb21tdXRlZAABChUCCQAgACABu6O2CwkAIAC7IAGjtgs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AxEQAAAAAAEBAQEAAAAAAQEBAQfBBBAhZjMyLmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAAhZjMyLmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAEhZjMyLmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAIhZjMyLmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAMhZjY0LmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAQhZjY0LmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAUhZjY0LmkzMi5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAYhZjY0LmkzMi5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAchZjMyLmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAghZjMyLmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAAkhZjMyLmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF91AAohZjMyLmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF91AAshZjY0Lmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF9zAAwhZjY0Lmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF9zAA0hZjY0Lmk2NC5ub19mb2xkX3RydW5jX3NfY29udmVydF91AA4hZjY0Lmk2NC5ub19mb2xkX3RydW5jX3VfY29udmVydF91AA8KcRAGACAAqLILBgAgAKmyCwYAIACoswsGACAAqbMLBgAgAKq3CwYAIACrtwsGACAAqrgLBgAgAKu4CwYAIACutAsGACAAr7QLBgAgAK61CwYAIACvtQsGACAAsLkLBgAgALG5CwYAIACwugsGACAAsboL", + "AGFzbQEAAAABCwJgAn99AGABfwF9AwQDAAABBQQBAQEBBxYDBGluaXQAAANydW4AAQVjaGVjawACCjkDCQAgACABOAIACyUBAX8CQANAIAIgAioCACABlTgCACACQQRqIQIgAiAASQ0ACwsLBwAgACoCAAs=", + "AGFzbQEAAAABCwJgAn98AGABfwF8AwQDAAABBQQBAQEBBxYDBGluaXQAAANydW4AAQVjaGVjawACCjkDCQAgACABOQMACyUBAX8CQANAIAIgAisDACABozkDACACQQhqIQIgAiAASQ0ACwsLBwAgACsDAAs=", + "AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQdRCAdmMzIudWx0AAAHZjMyLnVsZQABB2YzMi51Z3QAAgdmMzIudWdlAAMHZjY0LnVsdAAEB2Y2NC51bGUABQdmNjQudWd0AAYHZjY0LnVnZQAHCkkICAAgACABYEULCAAgACABXkULCAAgACABX0ULCAAgACABXUULCAAgACABZkULCAAgACABZEULCAAgACABZUULCAAgACABY0UL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDCQgAAAAAAQEBAQfBAQgVZjMyLm5vX2ZvbGRfbHRfc2VsZWN0AAAVZjMyLm5vX2ZvbGRfbGVfc2VsZWN0AAEVZjMyLm5vX2ZvbGRfZ3Rfc2VsZWN0AAIVZjMyLm5vX2ZvbGRfZ2Vfc2VsZWN0AAMVZjY0Lm5vX2ZvbGRfbHRfc2VsZWN0AAQVZjY0Lm5vX2ZvbGRfbGVfc2VsZWN0AAUVZjY0Lm5vX2ZvbGRfZ3Rfc2VsZWN0AAYVZjY0Lm5vX2ZvbGRfZ2Vfc2VsZWN0AAcKaQgMACAAIAEgACABXRsLDAAgACABIAAgAV8bCwwAIAAgASAAIAFeGwsMACAAIAEgACABYBsLDAAgACABIAAgAWMbCwwAIAAgASAAIAFlGwsMACAAIAEgACABZBsLDAAgACABIAAgAWYbCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDCQgAAAAAAQEBAQehAQgRZjMyLm5vX2ZvbGRfbHRfaWYAABFmMzIubm9fZm9sZF9sZV9pZgABEWYzMi5ub19mb2xkX2d0X2lmAAIRZjMyLm5vX2ZvbGRfZ2VfaWYAAxFmNjQubm9fZm9sZF9sdF9pZgAEEWY2NC5ub19mb2xkX2xlX2lmAAURZjY0Lm5vX2ZvbGRfZ3RfaWYABhFmNjQubm9fZm9sZF9nZV9pZgAHCoEBCA8AIAAgAV0EfSAABSABCwsPACAAIAFfBH0gAAUgAQsLDwAgACABXgR9IAAFIAELCw8AIAAgAWAEfSAABSABCwsPACAAIAFjBHwgAAUgAQsLDwAgACABZQR8IAAFIAELCw8AIAAgAWQEfCAABSABCwsPACAAIAFmBHwgAAUgAQsL", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwkIAAAAAAEBAQEH+QEIHGYzMi5ub19mb2xkX2x0X3NlbGVjdF90b19hYnMAABxmMzIubm9fZm9sZF9sZV9zZWxlY3RfdG9fYWJzAAEcZjMyLm5vX2ZvbGRfZ3Rfc2VsZWN0X3RvX2FicwACHGYzMi5ub19mb2xkX2dlX3NlbGVjdF90b19hYnMAAxxmNjQubm9fZm9sZF9sdF9zZWxlY3RfdG9fYWJzAAQcZjY0Lm5vX2ZvbGRfbGVfc2VsZWN0X3RvX2FicwAFHGY2NC5ub19mb2xkX2d0X3NlbGVjdF90b19hYnMABhxmNjQubm9fZm9sZF9nZV9zZWxlY3RfdG9fYWJzAAcKmQEIEAAgAIwgACAAQwAAAABdGwsQACAAjCAAIABDAAAAgF8bCxAAIAAgAIwgAEMAAACAXhsLEAAgACAAjCAAQwAAAABgGwsUACAAmiAAIABEAAAAAAAAAABjGwsUACAAmiAAIABEAAAAAAAAAIBlGwsUACAAIACaIABEAAAAAAAAAIBkGwsUACAAIACaIABEAAAAAAAAAABmGws=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwkIAAAAAAEBAQEH2QEIGGYzMi5ub19mb2xkX2x0X2lmX3RvX2FicwAAGGYzMi5ub19mb2xkX2xlX2lmX3RvX2FicwABGGYzMi5ub19mb2xkX2d0X2lmX3RvX2FicwACGGYzMi5ub19mb2xkX2dlX2lmX3RvX2FicwADGGY2NC5ub19mb2xkX2x0X2lmX3RvX2FicwAEGGY2NC5ub19mb2xkX2xlX2lmX3RvX2FicwAFGGY2NC5ub19mb2xkX2d0X2lmX3RvX2FicwAGGGY2NC5ub19mb2xkX2dlX2lmX3RvX2FicwAHCrEBCBMAIABDAAAAAF0EfSAAjAUgAAsLEwAgAEMAAACAXwR9IACMBSAACwsTACAAQwAAAIBeBH0gAAUgAIwLCxMAIABDAAAAAGAEfSAABSAAjAsLFwAgAEQAAAAAAAAAAGMEfCAAmgUgAAsLFwAgAEQAAAAAAAAAgGUEfCAAmgUgAAsLFwAgAEQAAAAAAAAAgGQEfCAABSAAmgsLFwAgAEQAAAAAAAAAAGYEfCAABSAAmgsL", + "AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBzcCGGYzMi5pbmNvcnJlY3RfY29ycmVjdGlvbgAAGGY2NC5pbmNvcnJlY3RfY29ycmVjdGlvbgABCkUCGQBDvp+qP0PNzJw/kkO+n6o/k0PNzJw/kwspAESHFtnO91P1P0SamZmZmZnzP6BEhxbZzvdT9T+hRJqZmZmZmfM/oQs=", + "AGFzbQEAAAABBQFgAAF9AwIBAAcNAQljYWxjdWxhdGUAAApKAUgBBX1DAEAcQyEAQ1VVUEMhAUMVx+M/IQIgAYwgAJQgACAClCABk5UhAyABjCAAlCAAIAKUIAGTlSEEAkAgAyAEWw0AAAsgBAs=", + "AGFzbQEAAAABBQFgAAF8AwIBAAcNAQljYWxjdWxhdGUAAApWAVQBBXxEAAAAAACIY0AhAERLBquqqgpqQCEBRJbQ46riePw/IQIgAZogAKIgACACoiABoaMhAyABmiAAoiAAIAKiIAGhoyEEAkAgAyAEYQ0AAAsgBAs=", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHEAEMbGx2bV9wcjI2NzQ2AAAKEgEQAEMAAAAAQwAAAIAgAJOTCw==", + "AGFzbQEAAAABBgFgAX8BfQMCAQAHEAEMbGx2bV9wcjI3MTUzAAAKFAESACAAQf////8AcbJDAAAAy5IL", + "AGFzbQEAAAABBwFgAn9/AX0DAgEABxABDGxsdm1fcHIyNzAzNgAAChcBFQAgAEHL/4d0crIgAUGAgJAHcbKSCw==", + "AGFzbQEAAAABFwNgBHx8fHwBfGADfHx8AXxgA319fQF9AwQDAAECByIDCHRoZXBhc3QwAAAIdGhlcGFzdDEAAQh0aGVwYXN0MgACCiUDDQAgACABoiACIAOiowsKACAAIAGiIAKhCwoAIAAgAZQgApQL", + "AGFzbQEAAAABBgFgAX0BfQMCAQAHCwEHaW52ZXJzZQAACgwBCgBDAACAPyAAlQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHJwIQZjMyX3NxcnRfbWludXNfMgAAEGY2NF9zcXJ0X21pbnVzXzIAAQodAgsAIACRQwAAAECTCw8AIACfRAAAAAAAAABAoQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2ZvbGRfcmVjaXBfcmVjaXAAABdmNjQubm9fZm9sZF9yZWNpcF9yZWNpcAABCisCEABDAACAP0MAAIA/IACVlQsYAEQAAAAAAADwP0QAAAAAAADwPyAAo6ML", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc7AhpmMzIubm9fYWxnZWJyYWljX2ZhY3RvcmluZwAAGmY2NC5ub19hbGdlYnJhaWNfZmFjdG9yaW5nAAEKHQINACAAIAGSIAAgAZOUCw0AIAAgAaAgACABoaIL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc7AhpmMzIubm9fYWxnZWJyYWljX2ZhY3RvcmluZwAAGmY2NC5ub19hbGdlYnJhaWNfZmFjdG9yaW5nAAEKHQINACAAIACUIAEgAZSTCw0AIAAgAKIgASABoqEL", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfQMDAgABBQQBAQEBByACEWYzMi5zaW1wbGVfeDRfc3VtAAAIZjMyLmxvYWQAAQpuAmQBCH0gACoCACEDIAAqAgQhBCAAKgIIIQUgACoCDCEGIAEqAgAhByABKgIEIQggASoCCCEJIAEqAgwhCiACIAMgB5I4AgAgAiAEIAiSOAIEIAIgBSAJkjgCCCACIAYgCpI4AgwLBwAgACoCAAsLNgEAQQALMAEAAAABAACAAQAAAAEAAIABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfAMDAgABBQQBAQEBByACEWY2NC5zaW1wbGVfeDRfc3VtAAAIZjY0LmxvYWQAAQpuAmQBCHwgACsDACEDIAArAwghBCAAKwMQIQUgACsDGCEGIAErAwAhByABKwMIIQggASsDECEJIAErAxghCiACIAMgB6A5AwAgAiAEIAigOQMIIAIgBSAJoDkDECACIAYgCqA5AxgLBwAgACsDAAsLZgEAQQALYAEAAAAAAAAAAQAAAAAAAIABAAAAAAAAAAEAAAAAAACAAQAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", + "AGFzbQEAAAABBwFgAn9/AX0DAwIAAAUEAQEBAQchAg1mMzIua2FoYW5fc3VtAAANZjMyLnBsYWluX3N1bQABCmICNwEDfQJAA0AgAyAAKgIAIASTIgSSIgIgA5MgBJMhBCAAQQRqIQAgAiEDIAFBf2oiAQ0ACwsgAgsoAQF9AkADQCACIAAqAgCSIQIgAEEEaiEAIAFBf2ohASABDQALCyACCwuHCAEAQQALgAjExVckpYTIC224Sy7ydhccykpWHhtucSJdFx5uv80UXMchVVE5nB+yUfCjk9fBLK5+qCg6ASH0CliT+EJ3n4M5al+69wrYUWo0yq3GNA7YJtxMMxztKZCoeA/RznYxI4O4NejyRLDTofy7MuGwumlECdbZff8uwFo2FDMUPqn6h22LvM6dp/3E6YU/3dfhGKZQJnJuP3MP+BKTIzRhdhJIwJsFk+usht6UPlXojOjd5PyVR75WAyEgTOa/e/Z/1bpzHMEUj8QnlrO9M/94QV/AWs72Z25zmhdmcAP4ziejUrKfO7/7ru3TWvg3V/D1bu+xTXA9VKcBmoUISJH1nQxgh1vZVB5RbYiOCIylcTpWCGdGj48TKizsLB+0YitvQQrEZUKiMWssfT67dayGlzDZSM2aH1bExuQSwJ377gKMzhzyHqF4I9vEHkkD03HMCFDF2Fzt1bVlrLXJIdLJKXbe8DAaWzzyO9s6OYI6FghvqPG+aWmZcaYF0xSTKhbyLxHHfiC7kUTu+OQBU8C5f/C/8AOcbbHfokQBbWtxK1yzIRlGXo/bkdN8eGu3EgCP672K9dQuxMEe33NjWUdJAwq3zyTPnA5Eep4U+0K/nTkwnqCrL9GunmqDQ+NVfYW/Y4r4lhAf/m3nIhvhaUaKRMjI+QwrGQelAj7yMBCahYpf74FFoHexAxBzS66YnUe/mi061Q8DZuM9U9lAzh9vMi8hKyMhbGLUpz6ozigxLQA9Z16voM8u0rlrhOtpCDxiNr4S/TZ/iD6tvAvAQcRQtuNQMejO4pZlVZwWRuawLTrogQWwvzT3vBAc+8w88YWXQp/rFI08v9cXiEmdiyuyOoPRTwSeoQ+tCJ1Ur9GCw+wyLwKPBSEtorfk9G8ugSsLnPzL/nQC+dv08+oAqOzRmXQm3dY01SWxRt2cqnH1YLCIyOALWVolTylm+eMu/una5RhPJ2L0zqQhlXTHV2Qnmkz9VH1hzsOsh0ac+v8JynmXZyR0ytQhgyYlGRI3ZBnlZeB0dY7dyO90x9ghK3kEUUZlYANd+tj0ZaSeXSPa14qSgKTeeDzxV0Jtzckv1aSeq0D0yxvXo8r866cBspppTkabGE7deaeqplI5Hu8wzJu9W+5MIW0wAHKwRl8Iz8W54D7CswzcjmTeGUJ5z0PqQ12OiPerFdw/yGcg27hksUcf3vLLP1mf2EaQ3K4vIvniMYnZnBxM06lKV4Scn+osPK48wx6L5U4XASXbNEZfFeoFDHzZRYwZ0HOKlhbdRPkFt1txsOYhNl91iZFzdat9rtNz7DfG6lV17+qri3sR3G0asmrEJc+q459JSYnLN5sKpwFgcNy3yIPhQvW+rWKUrY2h", + "AGFzbQEAAAABBwFgAn9/AXwDAwIAAAUEAQEBAQchAg1mNjQua2FoYW5fc3VtAAANZjY0LnBsYWluX3N1bQABCmICNwEDfAJAA0AgAyAAKwMAIAShIgSgIgIgA6EgBKEhBCAAQQhqIQAgAiEDIAFBf2oiAQ0ACwsgAgsoAQF8AkADQCACIAArAwCgIQIgAEEIaiEAIAFBf2ohASABDQALCyACCwuHEAEAQQALgBATBYRCXaIsxkPbVanN2lXjc/xY1rrVAP2DNUKIixNdOEpHDXJzoRrvxEUXV9jJRuCNbOE3cMiDW1VeWi1zHlbI4W1pFHgKilpkOgnHqIfF8NNd5gP8k74mytapkWC9sO2u9zB+kjpvp1mOqn2/Z1gqVPhO/u01WKZRv0LlS2YnJG1/Qi0okhjsCK7nVdqxpmWlclBHG7ipVNemBlsPQliDiheCxhBDoMAubbxahVNyf61EvDA8VbIkmnQ6nuHYD3D8qTrNk0vs437dXSfN+KCdHBHAVy79yBMyzDoafaNBVe3DgkkqBB7vc7kuLuNf9N/msjMMOT9vRGoDwUK5+rHI7aVYmX/ttHKeeev7Q4JFqruV0v8onvahrZXWVZUNb2ARx3g+SfJ+SPSicdATjrPemVLjRXTqdg4bKsjuFAHEUFs2PO+6cqKmCPh7Np357wvHVi1c8J1d3vy4rQ9kDpcVMibCMeYFHu/LFxttFQt0XdMu+GuGtLpzUlOZqXYgRclAgGsU7aH6gEbmJtLmmMRXv8QcpJB6NpQUuhWJbuacN4z03hIiXaF5UGcNPXrp1Koufyp6MD3qXRJI/uEYzaRXooc+tpqL29qdeJzPjbFPkLQ04J32yv5MO3htClwYn2G53bTgD3bgG2kNXlhzcF4OLaF9/yDrkTSSrDhyKh+OcS5q8a/HJ3DZxFf30jwduPDwZM/crr6jzD4ifU5pIWMX7QMCVJoPUE4TWjWhIqTfhsJ0eRa4aWmgUl0RZL1bk/xpoPQT0IFR3foMFcN6yWJ6qR3J5lqzW5cCPGQiEjwikGQtMFRMtKEiCVciXo44KwKorva+DSvyA636EAFxdyowApX2AD7QxI00GVAhCrxQ2jww1joxlI06/u8UV51LkwCWJAxv/bwjdgJs61JygBF+gDoTEjgdOEmVQCeKRHvo3G2MjI48tbMYDvYIGoRBNf+LuJNA6uFRHYmljUJoKeovwXpS65BdTdaA49d1SM7t0wEcjVullA14z/EGEy+YAqRtLmzy1XQpiUz5A/XHGK168Gj4XNZZh27WPwa+hiDjQZEi826L8GgcV6f8sHyemQuWGolf5g18CFGgomeaRwCTa/ko8GjbYvHgZSxTM+CnyhFCMPavAcFlPTIBb6suvtOLvhTD/+z78PnFDAVvAQlr4zQxDB9mpkK8GodJFhaMsJANNIwK4QleEKRrVszwybvcuFzO9syNdX6zB4gEL7ReyeNKI3MZYmyaA3ZEhpxg/Ntyjyeg3bPF2v/57Gqxe9PPUDfJengM5Dq29eb0mG5CfTVzi0XAVpfNbc7PrTGzw1T679XA9GpfVOdJPjMKMDj92QX/pT9XRhS1kRfKa5gjemWzbAK0zHldWNiz1ZSu9G11ZfeSv35HTDzu26zxMl37b0EcNMiDT8JYAb4FPmYWpgRtXU+GCSeCJRLNOs3Oa7zKrCib7molhp5FcMbSvTt9QuUnr8cd9IHIs3aKqDajrirmGOE2Iq32JXKwOYsBmiJ7hMMtX3KkmKwVcOfUGOJ90jB8MwjNysQihYh1gcZKdFiN4Oisxat1WvQoEvAYRVLyl7KTQW+Nf9tw+6NdH6eNmCArIp86AbWLG9LLFAMOFBTSGVofzl7NgXkVAcrec3SMViCfdy0lFvZhUR2kjpuYpcbsqEVXgll4DZC031Gww4KUzLNTCRVtlmw6QEe3SnoFL6EejJ2gIIj7Uref8/O7X+eKYachsaz6CaqkbLwkgLoq6WX/cP/M+mWHdvPFFc7L6EIxAAyRV9ngnTVUJK2k2PkIZ2PIz4HdkKLXxAdK5hBvZ+cn1CNZGPKonV/YlDCqVIZPh52CtSbKppa/z1X5nTcBGUhDxZRs83SXWEw8nQjoBMJYMHbhoPjq6cWuz3ieqQyss0RC4LxdG5xJWEocGUnBOur16zuBqUtwDMyeGtMvt1IvIDvrZFEdoC2yPr4ThUiSMi7bXKHnjEWRNQEKk8LrCc7z0iIk0IzMHZ04yE3jgsxkFQYt5wEvq7u1BEySHHrWP+hfMRUM3OQxtMQlPiqqAJ7I5SF6fynxwK8dXuhjOa34fmzIxX/CqJcnCtn0IWrqAwn795Y7g3lffEswn1Y13rRz1JXwFMN0Lw2jHU6NMSSzGoSFYlp7PBQ5F+Zt6zfCAFhbC+M8imLh+DVLVuKHYIu+pziRd1SpWiQlkJ+lQnfzXDnf/3QHdqHNH2ILgYForwXBwH8m7sCRo2p9KWFFJ+VXiNwNlwQaM6lEitoCEEU/jlWmdoxN4/GJg8jQ+JtQd59H30ycZg2qGLhfT8QBztyErEaeaeF2RWthieRdlLsRg5942ArS9X5dQ+q8EPE6yeJk+1Nl0Me0p/vUBVMl0M0piABWJSR9XbTzQZ/ptfeuZCzjyW3VhDpyErh62RsJ6DjaJk8EzgNxbopEe1yBWZzS5MO6WablKKePmuTVTrnKf8t1uCtDPrMVRrGlvJ2eOBXxvRshqvGCAJX8p3dHOaczQ5LXUkBLBoGKoL3xa5mEQlviO8VeElwoTbYOTshc6AGKxefknULuXZzE6+toCSeSlZoRVHPEEoD7ff7FCGB/NkHgELrWK2zxtBf+JjTjS/io45G+Tyr82oG45/7VJlBH8xplMoHgBbhPMjEmAEpTl8LDDi6hJlSrBY5WL32vIoRopYuX9qT9qMx1QZaG/Sc9KYaNf0zUjnNB9B7i3Vgnl86clM96BC/c7Q==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9uZWdfc3ViAAATZjY0Lm5vX2ZvbGRfbmVnX3N1YgABChMCCAAgACABk4wLCAAgACABoZoL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQctAhNmMzIubm9fZm9sZF9uZWdfYWRkAAATZjY0Lm5vX2ZvbGRfbmVnX2FkZAABChMCCAAgACABkowLCAAgACABoJoL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc1AhdmMzIubm9fZm9sZF9hZGRfbmVnX25lZwAAF2Y2NC5ub19mb2xkX2FkZF9uZWdfbmVnAAEKFQIJACAAjCABjJILCQAgAJogAZqgCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHLQITZjMyLm5vX2ZvbGRfYWRkX25lZwAAE2Y2NC5ub19mb2xkX2FkZF9uZWcAAQoTAggAIACMIACSCwgAIACaIACgCw==", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHMwIWZjMyLm5vX2ZvbGRfNnhfdmlhX2FkZAAAFmY2NC5ub19mb2xkXzZ4X3ZpYV9hZGQAAQopAhMAIAAgAJIgAJIgAJIgAJIgAJILEwAgACAAoCAAoCAAoCAAoCAAoAs=", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBy0CE2YzMi5ub19mb2xkX2Rpdl9kaXYAABNmNjQubm9fZm9sZF9kaXZfZGl2AAEKFwIKACAAIAGVIAKVCwoAIAAgAaMgAqML", + "AGFzbQEAAAABEQJgBH19fX0BfWAEfHx8fAF8AwMCAAEHLwIUZjMyLm5vX2ZvbGRfbXVsX2RpdnMAABRmNjQubm9fZm9sZF9tdWxfZGl2cwABCh0CDQAgACABlSACIAOVlAsNACAAIAGjIAIgA6OiCw==", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBy8CFGYzMi5ub19mb2xkX2FkZF9kaXZzAAAUZjY0Lm5vX2ZvbGRfYWRkX2RpdnMAAQodAg0AIAAgApUgASAClZILDQAgACACoyABIAKjoAs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2ZvbGRfc3FydF9zcXVhcmUAABdmNjQubm9fZm9sZF9zcXJ0X3NxdWFyZQABChMCCAAgACAAlJELCAAgACAAop8L", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcxAhVmMzIubm9fZm9sZF9tdWxfc3FydHMAABVmNjQubm9fZm9sZF9tdWxfc3FydHMAAQoVAgkAIACRIAGRlAsJACAAnyABn6IL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcxAhVmMzIubm9fZm9sZF9kaXZfc3FydHMAABVmNjQubm9fZm9sZF9kaXZfc3FydHMAAQoVAgkAIACRIAGRlQsJACAAnyABn6ML", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc3AhhmMzIubm9fZm9sZF9tdWxfc3FydF9kaXYAABhmNjQubm9fZm9sZF9tdWxfc3FydF9kaXYAAQoZAgsAIAAgAZGUIAGVCwsAIAAgAZ+iIAGjCw==", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABB00CI2YzMi5ub19mbHVzaF9pbnRlcm1lZGlhdGVfc3Vibm9ybWFsAAAjZjY0Lm5vX2ZsdXNoX2ludGVybWVkaWF0ZV9zdWJub3JtYWwAAQoXAgoAIAAgAZQgApQLCgAgACABoiACogs=", + "AGFzbQEAAAABEwNgAn19AX9gAnx8AX9gAnx9AX0DCAcAAAABAQECB38HD2YzMi5yZWNvZGluZ19lcQAAD2YzMi5yZWNvZGluZ19sZQABD2YzMi5yZWNvZGluZ19sdAACD2Y2NC5yZWNvZGluZ19lcQADD2Y2NC5yZWNvZGluZ19sZQAED2Y2NC5yZWNvZGluZ19sdAAFD3JlY29kaW5nX2RlbW90ZQAGCkwHCgAgACABlCAAWwsKACAAIAGUIABfCwoAIAAgAZQgAF0LCgAgACABoiAAYQsKACAAIAGiIABlCwoAIAAgAaIgAGMLCAAgALYgAZQL", + "AGFzbQEAAAABDwJgA319fQF/YAN8fHwBfwMDAgABB0ECHWYzMi5ub19leHRlbmRlZF9wcmVjaXNpb25fZGl2AAAdZjY0Lm5vX2V4dGVuZGVkX3ByZWNpc2lvbl9kaXYAAQoXAgoAIAAgAZUgAlsLCgAgACABoyACYQs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHNQIXZjMyLm5vX2Rpc3RyaWJ1dGVfZXhhY3QAABdmNjQubm9fZGlzdHJpYnV0ZV9leGFjdAABCjECEwBDAAAAwSAAlEMAAABBIACUkgsbAEQAAAAAAAAgwCAAokQAAAAAAAAgQCAAoqAL", + "AGFzbQEAAAABKQZgAX0BfWAEfX19fQF9YAN9fX0BfWABfAF8YAR8fHx8AXxgA3x8fAF8AwsKAAECAgEDBAUFBAe1AQoIZjMyLnNxcnQAAA9mMzIueGtjZF9zcXJ0XzIAAQ9mMzIueGtjZF9zcXJ0XzMAAg9mMzIueGtjZF9zcXJ0XzUAAxZmMzIueGtjZF9iZXR0ZXJfc3FydF81AAQIZjY0LnNxcnQABQ9mNjQueGtjZF9zcXJ0XzIABg9mNjQueGtjZF9zcXJ0XzMABw9mNjQueGtjZF9zcXJ0XzUACBZmNjQueGtjZF9iZXR0ZXJfc3FydF81AAkKiQEKBQAgAJELEAAgACABlSACIAMgApOVkgsKACAAIAGUIAKVCw0AIAAgAZUgAiAAlZILEwAgACABIAKUkiADIAEgApSTlQsFACAAnwsQACAAIAGjIAIgAyACoaOgCwoAIAAgAaIgAqMLDQAgACABoyACIACjoAsTACAAIAEgAqKgIAMgASACoqGjCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcpAhFmMzIuY29tcHV0ZV9yYWRpeAAAEWY2NC5jb21wdXRlX3JhZGl4AAEKnQECQwADQCAAIACSIgBDAACAP5IgAJNDAACAv5JDAAAAAFsNAAsDQCAAIAFDAACAP5IiAZIgAJMgAZNDAAAAAFwNAAsgAQtXAANAIAAgAKAiAEQAAAAAAADwP6AgAKFEAAAAAAAA8L+gRAAAAAAAAAAAYQ0ACwNAIAAgAUQAAAAAAADwP6AiAaAgAKEgAaFEAAAAAAAAAABiDQALIAEL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc3AhhmMzIubm9fZm9sZF9zdWIxX211bF9hZGQAABhmNjQubm9fZm9sZF9zdWIxX211bF9hZGQAAQonAhAAIABDAACAP5MgAZQgAZILFAAgAEQAAAAAAADwP6EgAaIgAaAL", + "AGFzbQEAAAABDwJgA319fQF/YAN8fHwBfwMFBAAAAQEHiQEEH2YzMi5ub19mb2xkX2FkZF9sZV9tb25vdG9uaWNpdHkAAB9mMzIubm9fZm9sZF9hZGRfZ2VfbW9ub3RvbmljaXR5AAEfZjY0Lm5vX2ZvbGRfYWRkX2xlX21vbm90b25pY2l0eQACH2Y2NC5ub19mb2xkX2FkZF9nZV9tb25vdG9uaWNpdHkAAwo5BA0AIAAgApIgASACkl8LDQAgACACkiABIAKSYAsNACAAIAKgIAEgAqBlCw0AIAAgAqAgASACoGYL", + "AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQdpCApmMzIubm90X2x0AAAKZjMyLm5vdF9sZQABCmYzMi5ub3RfZ3QAAgpmMzIubm90X2dlAAMKZjY0Lm5vdF9sdAAECmY2NC5ub3RfbGUABQpmNjQubm90X2d0AAYKZjY0Lm5vdF9nZQAHCkkICAAgACABXUULCAAgACABX0ULCAAgACABXkULCAAgACABYEULCAAgACABY0ULCAAgACABZUULCAAgACABZEULCAAgACABZkUL", + "AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBx0CC2YzMi5lcHNpbG9uAAALZjY0LmVwc2lsb24AAQpVAh8AQwAAgD9DAABAQEMAAIBAQwAAQECVQwAAgD+TlJMLMwBEAAAAAAAA8D9EAAAAAAAACEBEAAAAAAAAEEBEAAAAAAAACECjRAAAAAAAAPA/oaKhCw==", + "AGFzbQEAAAABCQJgAAF9YAABfAMDAgABBx0CC2YzMi5lcHNpbG9uAAALZjY0LmVwc2lsb24AAQpnAioBAn1DAACAPyEAA0AgACIBQwAAAD+UIgBDAACAP5JDAACAP14NAAsgAQs6AQJ8RAAAAAAAAPA/IQADQCAAIgFEAAAAAAAA4D+iIgBEAAAAAAAA8D+gRAAAAAAAAPA/ZA0ACyABCw==", + "AGFzbQEAAAABDQJgAn19AX9gAnx8AX8DCQgAAAAAAQEBAQe5AQgUZjMyLm5vX3RyaWNob3RvbXlfbHQAABRmMzIubm9fdHJpY2hvdG9teV9sZQABFGYzMi5ub190cmljaG90b215X2d0AAIUZjMyLm5vX3RyaWNob3RvbXlfZ2UAAxRmNjQubm9fdHJpY2hvdG9teV9sdAAEFGY2NC5ub190cmljaG90b215X2xlAAUUZjY0Lm5vX3RyaWNob3RvbXlfZ3QABhRmNjQubm9fdHJpY2hvdG9teV9nZQAHCnEIDQAgACABXSAAIAFgcgsNACAAIAFfIAAgAV5yCw0AIAAgAV4gACABX3ILDQAgACABYCAAIAFdcgsNACAAIAFjIAAgAWZyCw0AIAAgAWUgACABZHILDQAgACABZCAAIAFlcgsNACAAIAFmIAAgAWNyCw==", + "AGFzbQEAAAABFwRgAn9/AX9gAX8Bf2ACfn4BfmABfgF+AxQTAAABAgIDAQEBAQEBAwMDAwMDAQfuAxMdZjMyLmFyaXRobWV0aWNfbmFuX2JpdHBhdHRlcm4AABxmMzIuY2Fub25pY2FsX25hbl9iaXRwYXR0ZXJuAAEgZjMyLm5vbmFyaXRobWV0aWNfbmFuX2JpdHBhdHRlcm4AAh1mNjQuYXJpdGhtZXRpY19uYW5fYml0cGF0dGVybgADHGY2NC5jYW5vbmljYWxfbmFuX2JpdHBhdHRlcm4ABCBmNjQubm9uYXJpdGhtZXRpY19uYW5fYml0cGF0dGVybgAFFGYzMi5ub19mb2xkX3N1Yl96ZXJvAAYUZjMyLm5vX2ZvbGRfbmVnMF9zdWIABxNmMzIubm9fZm9sZF9tdWxfb25lAAgUZjMyLm5vX2ZvbGRfbmVnMV9tdWwACRNmMzIubm9fZm9sZF9kaXZfb25lAAoUZjMyLm5vX2ZvbGRfZGl2X25lZzEACxRmNjQubm9fZm9sZF9zdWJfemVybwAMFGY2NC5ub19mb2xkX25lZzBfc3ViAA0TZjY0Lm5vX2ZvbGRfbXVsX29uZQAOFGY2NC5ub19mb2xkX25lZzFfbXVsAA8TZjY0Lm5vX2ZvbGRfZGl2X29uZQAQFGY2NC5ub19mb2xkX2Rpdl9uZWcxABEWbm9fZm9sZF9wcm9tb3RlX2RlbW90ZQASCpkDExEAIAC+IAG+lbxBgICA/gdxCxEAIAC+IAG+lbxB/////wdxCwcAIAC+jLwLFgAgAL8gAb+jvUKAgICAgICA/P8AgwsWACAAvyABv6O9Qv///////////wCDCwcAIAC/mr0LEwAgAL5DAAAAAJO8QYCAgP4HcQsTAEMAAACAIAC+k7xBgICA/gdxCxMAIAC+QwAAgD+UvEGAgID+B3ELEwBDAACAvyAAvpS8QYCAgP4HcQsTACAAvkMAAIA/lbxBgICA/gdxCxMAIAC+QwAAgL+VvEGAgID+B3ELHAAgAL9EAAAAAAAAAAChvUKAgICAgICA/P8AgwscAEQAAAAAAAAAgCAAv6G9QoCAgICAgID8/wCDCxwAIAC/RAAAAAAAAPA/or1CgICAgICAgPz/AIMLHABEAAAAAAAA8L8gAL+ivUKAgICAgICA/P8AgwscACAAv0QAAAAAAADwP6O9QoCAgICAgID8/wCDCxwAIAC/RAAAAAAAAPC/o71CgICAgICAgPz/AIMLDwAgAL67trxBgICA/gdxCw==", + "AGFzbQEAAAABDQFgCHx8fHx8fHx8AXwDAwIAAAcyAhNkb3RfcHJvZHVjdF9leGFtcGxlAAAYd2l0aF9iaW5hcnlfc3VtX2NvbGxhcHNlAAEKNQIZACAAIASiIAEgBaKgIAIgBqKgIAMgB6KgCxkAIAAgBKIgASAFoqAgAiAGoiADIAeioKAL", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQcnAhBmMzIuY29udHJhY3QyZm1hAAAQZjY0LmNvbnRyYWN0MmZtYQABCh8CDgAgACAAlCABIAGUk5ELDgAgACAAoiABIAGioZ8L", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBz8CHGYzMi5kaXZpc2lvbl9ieV9zbWFsbF9udW1iZXIAABxmNjQuZGl2aXNpb25fYnlfc21hbGxfbnVtYmVyAAEKFwIKACAAIAEgApWTCwoAIAAgASACo6EL", + "AGFzbQEAAAABDwJgA319fQF9YAN8fHwBfAMDAgABBycCEGYzMi5nb2xkZW5fcmF0aW8AABBmNjQuZ29sZGVuX3JhdGlvAAEKGQILACAAIAEgApGSlAsLACAAIAEgAp+gogs=", + "AGFzbQEAAAABCwJgAX0BfWABfAF8AwMCAAEHJwIQZjMyLnNpbHZlcl9tZWFucwAAEGY2NC5zaWx2ZXJfbWVhbnMAAQo5AhcAQwAAAD8gACAAIACUQwAAgECSkZKUCx8ARAAAAAAAAOA/IAAgACAAokQAAAAAAAAQQKCfoKIL", + "AGFzbQEAAAABBwFgAnx8AX8DAgEABw4BCnBvaW50X2ZvdXIAAAoTAREAIAAgAaNEmpmZmZmZ2T9jCw==", + "AGFzbQEAAAABBgFgAX8BfAMCAQAHBwEDdGF1AAAKywEByAEBBHxEAAAAAAAAAAAhAQJAIABBAUgNAEQAAAAAAADwPyECRAAAAAAAAAAAIQMDQCABIAJEAAAAAAAAIEAgA0QAAAAAAAAgQKIiBEQAAAAAAADwP6CjRAAAAAAAABBAIAREAAAAAAAAEECgo6FEAAAAAAAAAEAgBEQAAAAAAAAUQKCjoUQAAAAAAAAAQCAERAAAAAAAABhAoKOhoqAhASADRAAAAAAAAPA/oCEDIAJEAAAAAAAAsD+iIQIgAEF/aiIADQALCyABCw==", + "AGFzbQEAAAABDQJgAn19AX1gAnx8AXwDAwIAAQc9AhtmMzIubm9fZm9sZF9jb25kaXRpb25hbF9pbmMAABtmNjQubm9fZm9sZF9jb25kaXRpb25hbF9pbmMAAQo1AhUAIAAgAEMAAIA/kiABQwAAAABdGwsdACAAIABEAAAAAAAA8D+gIAFEAAAAAAAAAABjGws=", + "AGFzbQEAAAABEQRgAAF/YAABfmAAAX1gAAF8A2NiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAgICAgICAgICAgMDAwMDAwMDAwMHmQ5iB2YzMi5uYW4AABBmMzIucG9zaXRpdmVfbmFuAAEQZjMyLm5lZ2F0aXZlX25hbgACDWYzMi5wbGFpbl9uYW4AAyJmMzIuaW5mb3JtYWxseV9rbm93bl9hc19wbGFpbl9zbmFuAAQQZjMyLmFsbF9vbmVzX25hbgAFDGYzMi5taXNjX25hbgAGFWYzMi5taXNjX3Bvc2l0aXZlX25hbgAHFWYzMi5taXNjX25lZ2F0aXZlX25hbgAIDGYzMi5pbmZpbml0eQAJFWYzMi5wb3NpdGl2ZV9pbmZpbml0eQAKFWYzMi5uZWdhdGl2ZV9pbmZpbml0eQALCGYzMi56ZXJvAAwRZjMyLnBvc2l0aXZlX3plcm8ADRFmMzIubmVnYXRpdmVfemVybwAOCGYzMi5taXNjAA8QZjMyLm1pbl9wb3NpdGl2ZQAQDmYzMi5taW5fbm9ybWFsABEOZjMyLm1heF9maW5pdGUAEhFmMzIubWF4X3N1Ym5vcm1hbAATEGYzMi50cmFpbGluZ19kb3QAFAxmMzIubWlzY19pbnQAFQ1mMzIubGFyZ2VfaW50ABYNZjMyLm1pbl9pbnQzMgAXDWYzMi5taW5faW50NjQAGAxmMzJfZGVjLnplcm8AGRVmMzJfZGVjLnBvc2l0aXZlX3plcm8AGhVmMzJfZGVjLm5lZ2F0aXZlX3plcm8AGwxmMzJfZGVjLm1pc2MAHBRmMzJfZGVjLm1pbl9wb3NpdGl2ZQAdEmYzMl9kZWMubWluX25vcm1hbAAeFWYzMl9kZWMubWF4X3N1Ym5vcm1hbAAfEmYzMl9kZWMubWF4X2Zpbml0ZQAgFGYzMl9kZWMudHJhaWxpbmdfZG90ACEQZjMyX2RlYy5taXNjX2ludAAiEWYzMl9kZWMubGFyZ2VfaW50ACMRZjMyX2RlYy5taW5faW50MzIAJBFmMzJfZGVjLm1pbl9pbnQ2NAAlF2YzMl9kZWMucm9vdF9iZWVyX2Zsb2F0ACYHZjY0Lm5hbgAnEGY2NC5wb3NpdGl2ZV9uYW4AKBBmNjQubmVnYXRpdmVfbmFuACkNZjY0LnBsYWluX25hbgAqImY2NC5pbmZvcm1hbGx5X2tub3duX2FzX3BsYWluX3NuYW4AKxBmNjQuYWxsX29uZXNfbmFuACwMZjY0Lm1pc2NfbmFuAC0VZjY0Lm1pc2NfcG9zaXRpdmVfbmFuAC4VZjY0Lm1pc2NfbmVnYXRpdmVfbmFuAC8MZjY0LmluZmluaXR5ADAVZjY0LnBvc2l0aXZlX2luZmluaXR5ADEVZjY0Lm5lZ2F0aXZlX2luZmluaXR5ADIIZjY0Lnplcm8AMxFmNjQucG9zaXRpdmVfemVybwA0EWY2NC5uZWdhdGl2ZV96ZXJvADUIZjY0Lm1pc2MANhBmNjQubWluX3Bvc2l0aXZlADcOZjY0Lm1pbl9ub3JtYWwAOBFmNjQubWF4X3N1Ym5vcm1hbAA5DmY2NC5tYXhfZmluaXRlADoQZjY0LnRyYWlsaW5nX2RvdAA7DGY2NC5taXNjX2ludAA8DWY2NC5sYXJnZV9pbnQAPQ1mNjQubWluX2ludDMyAD4NZjY0Lm1pbl9pbnQ2NAA/DGY2NF9kZWMuemVybwBAFWY2NF9kZWMucG9zaXRpdmVfemVybwBBFWY2NF9kZWMubmVnYXRpdmVfemVybwBCDGY2NF9kZWMubWlzYwBDFGY2NF9kZWMubWluX3Bvc2l0aXZlAEQSZjY0X2RlYy5taW5fbm9ybWFsAEUVZjY0X2RlYy5tYXhfc3Vibm9ybWFsAEYSZjY0X2RlYy5tYXhfZmluaXRlAEcUZjY0X2RlYy50cmFpbGluZ19kb3QASBBmNjRfZGVjLm1pc2NfaW50AEkRZjY0X2RlYy5sYXJnZV9pbnQAShFmNjRfZGVjLm1pbl9pbnQzMgBLEWY2NF9kZWMubWluX2ludDY0AEwXZjY0X2RlYy5yb290X2JlZXJfZmxvYXQATQxmMzItZGVjLXNlcDEATgxmMzItZGVjLXNlcDIATwxmMzItZGVjLXNlcDMAUAxmMzItZGVjLXNlcDQAUQxmMzItZGVjLXNlcDUAUgxmMzItaGV4LXNlcDEAUwxmMzItaGV4LXNlcDIAVAxmMzItaGV4LXNlcDMAVQxmMzItaGV4LXNlcDQAVgxmMzItaGV4LXNlcDUAVwxmNjQtZGVjLXNlcDEAWAxmNjQtZGVjLXNlcDIAWQxmNjQtZGVjLXNlcDMAWgxmNjQtZGVjLXNlcDQAWwxmNjQtZGVjLXNlcDUAXAxmNjQtaGV4LXNlcDEAXQxmNjQtaGV4LXNlcDIAXgxmNjQtaGV4LXNlcDMAXwxmNjQtaGV4LXNlcDQAYAxmNjQtaGV4LXNlcDUAYQqjCGIIAEMAAMB/vAsIAEMAAMB/vAsIAEMAAMD/vAsIAEMAAMB/vAsIAEMAAKB/vAsIAEP/////vAsIAENFI4F/vAsIAENQQLB/vAsIAEPevKr/vAsIAEMAAIB/vAsIAEMAAIB/vAsIAEMAAID/vAsIAEMAAAAAvAsIAEMAAAAAvAsIAEMAAACAvAsIAEPbD8lAvAsIAEMBAAAAvAsIAEMAAIAAvAsIAEP//39/vAsIAEP//38AvAsIAEMAAIBEvAsIAEOAopFHvAsIAEMAAIBnvAsIAEMAAADPvAsIAEMAAADfvAsIAEMAAAAAvAsIAEMAAAAAvAsIAEMAAACAvAsIAEPbD8lAvAsIAEMBAAAAvAsIAEMAAIAAvAsIAEP//38AvAsIAEP//39/vAsIAEP5AhVQvAsIAEMA5EBGvAsIAEPseK1gvAsIAEMAAADPvAsIAEMAAADfvAsIAEMBAIA/vAsMAEQAAAAAAAD4f70LDABEAAAAAAAA+H+9CwwARAAAAAAAAPj/vQsMAEQAAAAAAAD4f70LDABEAAAAAAAA9H+9CwwARP//////////vQsMAES8mnhWNBLwf70LDABECQgHBgUE83+9CwwAREUjAe/Nq/L/vQsMAEQAAAAAAADwf70LDABEAAAAAAAA8H+9CwwARAAAAAAAAPD/vQsMAEQAAAAAAAAAAL0LDABEAAAAAAAAAAC9CwwARAAAAAAAAACAvQsMAEQYLURU+yEZQL0LDABEAQAAAAAAAAC9CwwARAAAAAAAABAAvQsMAET///////8PAL0LDABE////////73+9CwwARAAAAAAAADBGvQsMAEQAAAAAUDTyQL0LDABEAAAAAAAA8ES9CwwARAAAAAAAAODBvQsMAEQAAAAAAADgw70LDABEAAAAAAAAAAC9CwwARAAAAAAAAAAAvQsMAEQAAAAAAAAAgL0LDABEGC1EVPshGUC9CwwARAEAAAAAAAAAvQsMAEQAAAAAAAAQAL0LDABE////////DwC9CwwARP///////+9/vQsMAER9w5QlrUmyVL0LDABEAAAAAIAcyEC9CwwARECMtXgdrxVEvQsMAEQAAAAAAADgwb0LDABEAAAAAAAA4MO9CwwARCSe8R8AAPA/vQsHAEMAJHRJCwcAQwAAekQLBwBDEMl6RAsHAEOWGWFYCwcAQ3SuHW4LBwBDCvAgTQsHAEOAB9VHCwcAQ/H/IEcLBwBDAADwSQsHAEMowKtVCwsARAAAAACAhC5BCwsARAAAAAAAQI9ACwsARACL/PohWY9ACwsAREXMt1tzXNAmCwsARJzeW3LOtcNFCwsARDIzAQAe4CVDCwsARAAAAADwoPpACwsARECzNCj+H+RACwsARAAAAAAAAD5BCwsARABwnw8FeLVCCw==", + "AGFzbQEAAAABhYCAgAABYAABfAOCgICAAAEAB46AgIAAAQo0Mjk0OTY3MjQ5AAAKkYCAgAABi4CAgAAARAAAIPr//+9BCw==", + "AGFzbQEAAAABFwRgAn19AX1gAX0BfWACfHwBfGABfAF8Ax0cAAAAAAEBAQABAQEBAAACAgICAwMDAgMDAwMCAge3AhwHZjMyLmFkZAAAB2YzMi5zdWIAAQdmMzIubXVsAAIHZjMyLmRpdgADCGYzMi5zcXJ0AAQHZjMyLmFicwAFB2YzMi5uZWcABgxmMzIuY29weXNpZ24ABwhmMzIuY2VpbAAICWYzMi5mbG9vcgAJCWYzMi50cnVuYwAKC2YzMi5uZWFyZXN0AAsHZjMyLm1pbgAMB2YzMi5tYXgADQdmNjQuYWRkAA4HZjY0LnN1YgAPB2Y2NC5tdWwAEAdmNjQuZGl2ABEIZjY0LnNxcnQAEgdmNjQuYWJzABMHZjY0Lm5lZwAUDGY2NC5jb3B5c2lnbgAVCGY2NC5jZWlsABYJZjY0LmZsb29yABcJZjY0LnRydW5jABgLZjY0Lm5lYXJlc3QAGQdmNjQubWluABoHZjY0Lm1heAAbCsUBHAcAIAAgAZILBwAgACABkwsHACAAIAGUCwcAIAAgAZULBQAgAJELBQAgAIsLBQAgAIwLBwAgACABmAsFACAAjQsFACAAjgsFACAAjwsFACAAkAsHACAAIAGWCwcAIAAgAZcLBwAgACABoAsHACAAIAGhCwcAIAAgAaILBwAgACABowsFACAAnwsFACAAmQsFACAAmgsHACAAIAGmCwUAIACbCwUAIACcCwUAIACdCwUAIACeCwcAIAAgAaQLBwAgACABpQs=", +]; +export const script = "[[\"file\",\"f32.wast\"],[\"module\",5,0],[\"invoke\",19,0,\"add\",[\"f32:2147483648\",\"f32:2147483648\"]],[\"invoke\",25,0,\"add\",[\"f32:0\",\"f32:2147483649\"]],[\"invoke\",30,0,\"add\",[\"f32:0\",\"f32:8388608\"]],[\"invoke\",35,0,\"add\",[\"f32:2147483648\",\"f32:3212836864\"]],[\"invoke\",40,0,\"add\",[\"f32:2147483648\",\"f32:1086918619\"]],[\"invoke\",45,0,\"add\",[\"f32:0\",\"f32:4286578687\"]],[\"invoke\",50,0,\"add\",[\"f32:0\",\"f32:2139095040\"]],[\"invoke\",59,0,\"add\",[\"f32:2147483649\",\"f32:2147483648\"]],[\"invoke\",64,0,\"add\",[\"f32:2147483649\",\"f32:1\"]],[\"invoke\",69,0,\"add\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",74,0,\"add\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",79,0,\"add\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",85,0,\"add\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",90,0,\"add\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",99,0,\"add\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",104,0,\"add\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",109,0,\"add\",[\"f32:8388608\",\"f32:2155872256\"]],[\"invoke\",114,0,\"add\",[\"f32:8388608\",\"f32:1056964608\"]],[\"invoke\",119,0,\"add\",[\"f32:2155872256\",\"f32:3234402267\"]],[\"invoke\",124,0,\"add\",[\"f32:2155872256\",\"f32:2139095039\"]],[\"invoke\",129,0,\"add\",[\"f32:8388608\",\"f32:4286578688\"]],[\"invoke\",137,0,\"add\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",143,0,\"add\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",148,0,\"add\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",154,0,\"add\",[\"f32:1056964608\",\"f32:1056964608\"]],[\"invoke\",159,0,\"add\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",164,0,\"add\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",169,0,\"add\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",177,0,\"add\",[\"f32:1056964608\",\"f32:2143289344\"]],[\"invoke\",183,0,\"add\",[\"f32:3212836864\",\"f32:2147483649\"]],[\"invoke\",188,0,\"add\",[\"f32:3212836864\",\"f32:8388608\"]],[\"invoke\",193,0,\"add\",[\"f32:1065353216\",\"f32:3204448256\"]],[\"invoke\",198,0,\"add\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"invoke\",203,0,\"add\",[\"f32:3212836864\",\"f32:4286578687\"]],[\"invoke\",208,0,\"add\",[\"f32:3212836864\",\"f32:2139095040\"]],[\"invoke\",215,0,\"add\",[\"f32:1065353216\",\"f32:4290772992\"]],[\"invoke\",223,0,\"add\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",228,0,\"add\",[\"f32:3234402267\",\"f32:8388608\"]],[\"invoke\",233,0,\"add\",[\"f32:1086918619\",\"f32:3204448256\"]],[\"invoke\",238,0,\"add\",[\"f32:1086918619\",\"f32:1065353216\"]],[\"invoke\",243,0,\"add\",[\"f32:3234402267\",\"f32:4286578687\"]],[\"invoke\",248,0,\"add\",[\"f32:3234402267\",\"f32:2139095040\"]],[\"invoke\",255,0,\"add\",[\"f32:1086918619\",\"f32:4290772992\"]],[\"invoke\",262,0,\"add\",[\"f32:2139095039\",\"f32:0\"]],[\"invoke\",267,0,\"add\",[\"f32:4286578687\",\"f32:2155872256\"]],[\"invoke\",272,0,\"add\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",277,0,\"add\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",282,0,\"add\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",288,0,\"add\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",295,0,\"add\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",302,0,\"add\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",307,0,\"add\",[\"f32:4286578688\",\"f32:2155872256\"]],[\"invoke\",312,0,\"add\",[\"f32:4286578688\",\"f32:1056964608\"]],[\"invoke\",317,0,\"add\",[\"f32:2139095040\",\"f32:3212836864\"]],[\"invoke\",322,0,\"add\",[\"f32:2139095040\",\"f32:1086918619\"]],[\"invoke\",327,0,\"add\",[\"f32:4286578688\",\"f32:4286578688\"]],[\"invoke\",333,0,\"add\",[\"f32:4286578688\",\"f32:2143289344\"]],[\"invoke\",343,0,\"add\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",353,0,\"add\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",365,0,\"add\",[\"f32:4290772992\",\"f32:1056964608\"]],[\"invoke\",375,0,\"add\",[\"f32:2143289344\",\"f32:3212836864\"]],[\"invoke\",385,0,\"add\",[\"f32:2143289344\",\"f32:1086918619\"]],[\"invoke\",395,0,\"add\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",407,0,\"add\",[\"f32:4290772992\",\"f32:2143289344\"]],[\"invoke\",421,0,\"sub\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",426,0,\"sub\",[\"f32:0\",\"f32:1\"]],[\"invoke\",431,0,\"sub\",[\"f32:2147483648\",\"f32:3204448256\"]],[\"invoke\",436,0,\"sub\",[\"f32:2147483648\",\"f32:1065353216\"]],[\"invoke\",441,0,\"sub\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",446,0,\"sub\",[\"f32:0\",\"f32:2139095039\"]],[\"invoke\",451,0,\"sub\",[\"f32:2147483648\",\"f32:4290772992\"]],[\"invoke\",461,0,\"sub\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",466,0,\"sub\",[\"f32:1\",\"f32:1\"]],[\"invoke\",471,0,\"sub\",[\"f32:2147483649\",\"f32:3204448256\"]],[\"invoke\",476,0,\"sub\",[\"f32:2147483649\",\"f32:1065353216\"]],[\"invoke\",481,0,\"sub\",[\"f32:1\",\"f32:3234402267\"]],[\"invoke\",486,0,\"sub\",[\"f32:1\",\"f32:2139095039\"]],[\"invoke\",491,0,\"sub\",[\"f32:2147483649\",\"f32:4290772992\"]],[\"invoke\",500,0,\"sub\",[\"f32:2155872256\",\"f32:0\"]],[\"invoke\",505,0,\"sub\",[\"f32:8388608\",\"f32:2147483649\"]],[\"invoke\",510,0,\"sub\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",515,0,\"sub\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",520,0,\"sub\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",526,0,\"sub\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",531,0,\"sub\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",540,0,\"sub\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",545,0,\"sub\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",550,0,\"sub\",[\"f32:1056964608\",\"f32:8388608\"]],[\"invoke\",555,0,\"sub\",[\"f32:3204448256\",\"f32:3212836864\"]],[\"invoke\",560,0,\"sub\",[\"f32:3204448256\",\"f32:1086918619\"]],[\"invoke\",565,0,\"sub\",[\"f32:1056964608\",\"f32:4286578687\"]],[\"invoke\",570,0,\"sub\",[\"f32:1056964608\",\"f32:2139095040\"]],[\"invoke\",579,0,\"sub\",[\"f32:3212836864\",\"f32:2147483648\"]],[\"invoke\",584,0,\"sub\",[\"f32:3212836864\",\"f32:1\"]],[\"invoke\",589,0,\"sub\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",595,0,\"sub\",[\"f32:3212836864\",\"f32:3212836864\"]],[\"invoke\",600,0,\"sub\",[\"f32:3212836864\",\"f32:1086918619\"]],[\"invoke\",605,0,\"sub\",[\"f32:1065353216\",\"f32:4286578687\"]],[\"invoke\",610,0,\"sub\",[\"f32:1065353216\",\"f32:2139095040\"]],[\"invoke\",619,0,\"sub\",[\"f32:3234402267\",\"f32:2147483648\"]],[\"invoke\",624,0,\"sub\",[\"f32:3234402267\",\"f32:1\"]],[\"invoke\",629,0,\"sub\",[\"f32:1086918619\",\"f32:2155872256\"]],[\"invoke\",634,0,\"sub\",[\"f32:1086918619\",\"f32:1056964608\"]],[\"invoke\",639,0,\"sub\",[\"f32:3234402267\",\"f32:3234402267\"]],[\"invoke\",644,0,\"sub\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",649,0,\"sub\",[\"f32:1086918619\",\"f32:4286578688\"]],[\"invoke\",659,0,\"sub\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",664,0,\"sub\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",669,0,\"sub\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",674,0,\"sub\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",679,0,\"sub\",[\"f32:4286578687\",\"f32:3234402267\"]],[\"invoke\",684,0,\"sub\",[\"f32:4286578687\",\"f32:2139095039\"]],[\"invoke\",689,0,\"sub\",[\"f32:2139095039\",\"f32:4286578688\"]],[\"invoke\",697,0,\"sub\",[\"f32:2139095039\",\"f32:2143289344\"]],[\"invoke\",703,0,\"sub\",[\"f32:4286578688\",\"f32:2147483649\"]],[\"invoke\",708,0,\"sub\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",713,0,\"sub\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",718,0,\"sub\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",724,0,\"sub\",[\"f32:4286578688\",\"f32:2139095039\"]],[\"invoke\",729,0,\"sub\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",737,0,\"sub\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",747,0,\"sub\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",757,0,\"sub\",[\"f32:4290772992\",\"f32:8388608\"]],[\"invoke\",767,0,\"sub\",[\"f32:2143289344\",\"f32:3204448256\"]],[\"invoke\",777,0,\"sub\",[\"f32:2143289344\",\"f32:1065353216\"]],[\"invoke\",787,0,\"sub\",[\"f32:4290772992\",\"f32:4286578687\"]],[\"invoke\",797,0,\"sub\",[\"f32:4290772992\",\"f32:2139095040\"]],[\"invoke\",811,0,\"sub\",[\"f32:2143289344\",\"f32:4290772992\"]],[\"invoke\",822,0,\"mul\",[\"f32:0\",\"f32:0\"]],[\"invoke\",827,0,\"mul\",[\"f32:2147483648\",\"f32:2155872256\"]],[\"invoke\",833,0,\"mul\",[\"f32:0\",\"f32:3204448256\"]],[\"invoke\",838,0,\"mul\",[\"f32:0\",\"f32:1065353216\"]],[\"invoke\",843,0,\"mul\",[\"f32:2147483648\",\"f32:4286578687\"]],[\"invoke\",848,0,\"mul\",[\"f32:2147483648\",\"f32:2139095040\"]],[\"invoke\",855,0,\"mul\",[\"f32:0\",\"f32:4290772992\"]],[\"invoke\",862,0,\"mul\",[\"f32:1\",\"f32:0\"]],[\"invoke\",867,0,\"mul\",[\"f32:2147483649\",\"f32:2155872256\"]],[\"invoke\",872,0,\"mul\",[\"f32:2147483649\",\"f32:1056964608\"]],[\"invoke\",877,0,\"mul\",[\"f32:1\",\"f32:3212836864\"]],[\"invoke\",882,0,\"mul\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",887,0,\"mul\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",893,0,\"mul\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",902,0,\"mul\",[\"f32:8388608\",\"f32:0\"]],[\"invoke\",907,0,\"mul\",[\"f32:2155872256\",\"f32:2155872256\"]],[\"invoke\",912,0,\"mul\",[\"f32:2155872256\",\"f32:1056964608\"]],[\"invoke\",917,0,\"mul\",[\"f32:8388608\",\"f32:3212836864\"]],[\"invoke\",922,0,\"mul\",[\"f32:8388608\",\"f32:1086918619\"]],[\"invoke\",927,0,\"mul\",[\"f32:2155872256\",\"f32:4286578688\"]],[\"invoke\",933,0,\"mul\",[\"f32:2155872256\",\"f32:2143289344\"]],[\"invoke\",941,0,\"mul\",[\"f32:1056964608\",\"f32:2147483648\"]],[\"invoke\",946,0,\"mul\",[\"f32:1056964608\",\"f32:1\"]],[\"invoke\",951,0,\"mul\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",956,0,\"mul\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",962,0,\"mul\",[\"f32:1056964608\",\"f32:1086918619\"]],[\"invoke\",967,0,\"mul\",[\"f32:3204448256\",\"f32:4286578688\"]],[\"invoke\",973,0,\"mul\",[\"f32:3204448256\",\"f32:2143289344\"]],[\"invoke\",981,0,\"mul\",[\"f32:1065353216\",\"f32:2147483648\"]],[\"invoke\",986,0,\"mul\",[\"f32:1065353216\",\"f32:1\"]],[\"invoke\",991,0,\"mul\",[\"f32:3212836864\",\"f32:3204448256\"]],[\"invoke\",996,0,\"mul\",[\"f32:3212836864\",\"f32:1065353216\"]],[\"invoke\",1001,0,\"mul\",[\"f32:1065353216\",\"f32:3234402267\"]],[\"invoke\",1006,0,\"mul\",[\"f32:1065353216\",\"f32:2139095039\"]],[\"invoke\",1011,0,\"mul\",[\"f32:3212836864\",\"f32:4290772992\"]],[\"invoke\",1020,0,\"mul\",[\"f32:3234402267\",\"f32:0\"]],[\"invoke\",1025,0,\"mul\",[\"f32:1086918619\",\"f32:2147483649\"]],[\"invoke\",1031,0,\"mul\",[\"f32:3234402267\",\"f32:3204448256\"]],[\"invoke\",1036,0,\"mul\",[\"f32:3234402267\",\"f32:1065353216\"]],[\"invoke\",1041,0,\"mul\",[\"f32:1086918619\",\"f32:3234402267\"]],[\"invoke\",1046,0,\"mul\",[\"f32:1086918619\",\"f32:2139095039\"]],[\"invoke\",1051,0,\"mul\",[\"f32:3234402267\",\"f32:4290772992\"]],[\"invoke\",1060,0,\"mul\",[\"f32:4286578687\",\"f32:0\"]],[\"invoke\",1065,0,\"mul\",[\"f32:2139095039\",\"f32:2147483649\"]],[\"invoke\",1070,0,\"mul\",[\"f32:2139095039\",\"f32:8388608\"]],[\"invoke\",1075,0,\"mul\",[\"f32:4286578687\",\"f32:3212836864\"]],[\"invoke\",1080,0,\"mul\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",1085,0,\"mul\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",1090,0,\"mul\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",1100,0,\"mul\",[\"f32:4286578688\",\"f32:0\"]],[\"invoke\",1105,0,\"mul\",[\"f32:2139095040\",\"f32:2147483649\"]],[\"invoke\",1110,0,\"mul\",[\"f32:2139095040\",\"f32:8388608\"]],[\"invoke\",1115,0,\"mul\",[\"f32:4286578688\",\"f32:3212836864\"]],[\"invoke\",1120,0,\"mul\",[\"f32:4286578688\",\"f32:1086918619\"]],[\"invoke\",1125,0,\"mul\",[\"f32:2139095040\",\"f32:4286578687\"]],[\"invoke\",1130,0,\"mul\",[\"f32:2139095040\",\"f32:2139095040\"]],[\"invoke\",1139,0,\"mul\",[\"f32:4290772992\",\"f32:2147483648\"]],[\"invoke\",1149,0,\"mul\",[\"f32:4290772992\",\"f32:1\"]],[\"invoke\",1159,0,\"mul\",[\"f32:2143289344\",\"f32:2155872256\"]],[\"invoke\",1169,0,\"mul\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",1179,0,\"mul\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",1191,0,\"mul\",[\"f32:2143289344\",\"f32:4286578687\"]],[\"invoke\",1201,0,\"mul\",[\"f32:2143289344\",\"f32:2139095040\"]],[\"invoke\",1219,0,\"div\",[\"f32:2147483648\",\"f32:2147483648\"]],[\"invoke\",1224,0,\"div\",[\"f32:2147483648\",\"f32:1\"]],[\"invoke\",1229,0,\"div\",[\"f32:0\",\"f32:2155872256\"]],[\"invoke\",1234,0,\"div\",[\"f32:0\",\"f32:1056964608\"]],[\"invoke\",1239,0,\"div\",[\"f32:2147483648\",\"f32:3234402267\"]],[\"invoke\",1244,0,\"div\",[\"f32:2147483648\",\"f32:2139095039\"]],[\"invoke\",1249,0,\"div\",[\"f32:0\",\"f32:4286578688\"]],[\"invoke\",1257,0,\"div\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1263,0,\"div\",[\"f32:2147483649\",\"f32:2147483649\"]],[\"invoke\",1269,0,\"div\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",1274,0,\"div\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",1279,0,\"div\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",1284,0,\"div\",[\"f32:2147483649\",\"f32:2139095039\"]],[\"invoke\",1289,0,\"div\",[\"f32:1\",\"f32:4286578688\"]],[\"invoke\",1297,0,\"div\",[\"f32:1\",\"f32:2143289344\"]],[\"invoke\",1303,0,\"div\",[\"f32:2155872256\",\"f32:2147483649\"]],[\"invoke\",1308,0,\"div\",[\"f32:2155872256\",\"f32:8388608\"]],[\"invoke\",1313,0,\"div\",[\"f32:8388608\",\"f32:3204448256\"]],[\"invoke\",1318,0,\"div\",[\"f32:8388608\",\"f32:1065353216\"]],[\"invoke\",1323,0,\"div\",[\"f32:2155872256\",\"f32:4286578687\"]],[\"invoke\",1328,0,\"div\",[\"f32:2155872256\",\"f32:2139095040\"]],[\"invoke\",1337,0,\"div\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",1343,0,\"div\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",1348,0,\"div\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",1353,0,\"div\",[\"f32:1056964608\",\"f32:3204448256\"]],[\"invoke\",1358,0,\"div\",[\"f32:1056964608\",\"f32:1065353216\"]],[\"invoke\",1363,0,\"div\",[\"f32:3204448256\",\"f32:4286578687\"]],[\"invoke\",1368,0,\"div\",[\"f32:3204448256\",\"f32:2139095040\"]],[\"invoke\",1375,0,\"div\",[\"f32:1056964608\",\"f32:4290772992\"]],[\"invoke\",1382,0,\"div\",[\"f32:1065353216\",\"f32:0\"]],[\"invoke\",1387,0,\"div\",[\"f32:3212836864\",\"f32:2155872256\"]],[\"invoke\",1392,0,\"div\",[\"f32:3212836864\",\"f32:1056964608\"]],[\"invoke\",1397,0,\"div\",[\"f32:1065353216\",\"f32:3212836864\"]],[\"invoke\",1403,0,\"div\",[\"f32:3212836864\",\"f32:4286578687\"]],[\"invoke\",1408,0,\"div\",[\"f32:3212836864\",\"f32:2139095040\"]],[\"invoke\",1415,0,\"div\",[\"f32:1065353216\",\"f32:4290772992\"]],[\"invoke\",1422,0,\"div\",[\"f32:1086918619\",\"f32:0\"]],[\"invoke\",1427,0,\"div\",[\"f32:3234402267\",\"f32:2155872256\"]],[\"invoke\",1432,0,\"div\",[\"f32:3234402267\",\"f32:1056964608\"]],[\"invoke\",1437,0,\"div\",[\"f32:1086918619\",\"f32:3212836864\"]],[\"invoke\",1442,0,\"div\",[\"f32:1086918619\",\"f32:1086918619\"]],[\"invoke\",1447,0,\"div\",[\"f32:3234402267\",\"f32:4286578688\"]],[\"invoke\",1453,0,\"div\",[\"f32:3234402267\",\"f32:2143289344\"]],[\"invoke\",1461,0,\"div\",[\"f32:2139095039\",\"f32:2147483648\"]],[\"invoke\",1466,0,\"div\",[\"f32:2139095039\",\"f32:1\"]],[\"invoke\",1472,0,\"div\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",1477,0,\"div\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",1482,0,\"div\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",1487,0,\"div\",[\"f32:4286578687\",\"f32:4286578688\"]],[\"invoke\",1493,0,\"div\",[\"f32:4286578687\",\"f32:2143289344\"]],[\"invoke\",1501,0,\"div\",[\"f32:2139095040\",\"f32:2147483648\"]],[\"invoke\",1506,0,\"div\",[\"f32:2139095040\",\"f32:1\"]],[\"invoke\",1511,0,\"div\",[\"f32:4286578688\",\"f32:3204448256\"]],[\"invoke\",1516,0,\"div\",[\"f32:4286578688\",\"f32:1065353216\"]],[\"invoke\",1521,0,\"div\",[\"f32:2139095040\",\"f32:3234402267\"]],[\"invoke\",1526,0,\"div\",[\"f32:2139095040\",\"f32:2139095039\"]],[\"invoke\",1531,0,\"div\",[\"f32:4286578688\",\"f32:4290772992\"]],[\"invoke\",1543,0,\"div\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",1553,0,\"div\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",1563,0,\"div\",[\"f32:4290772992\",\"f32:3204448256\"]],[\"invoke\",1573,0,\"div\",[\"f32:4290772992\",\"f32:1065353216\"]],[\"invoke\",1583,0,\"div\",[\"f32:2143289344\",\"f32:3234402267\"]],[\"invoke\",1593,0,\"div\",[\"f32:2143289344\",\"f32:2139095039\"]],[\"invoke\",1603,0,\"div\",[\"f32:4290772992\",\"f32:4290772992\"]],[\"invoke\",1620,0,\"min\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",1625,0,\"min\",[\"f32:0\",\"f32:2147483649\"]],[\"invoke\",1630,0,\"min\",[\"f32:0\",\"f32:8388608\"]],[\"invoke\",1635,0,\"min\",[\"f32:2147483648\",\"f32:3212836864\"]],[\"invoke\",1641,0,\"min\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",1646,0,\"min\",[\"f32:0\",\"f32:2139095039\"]],[\"invoke\",1651,0,\"min\",[\"f32:2147483648\",\"f32:4290772992\"]],[\"invoke\",1660,0,\"min\",[\"f32:2147483649\",\"f32:0\"]],[\"invoke\",1665,0,\"min\",[\"f32:1\",\"f32:2147483649\"]],[\"invoke\",1670,0,\"min\",[\"f32:1\",\"f32:8388608\"]],[\"invoke\",1675,0,\"min\",[\"f32:2147483649\",\"f32:3212836864\"]],[\"invoke\",1680,0,\"min\",[\"f32:2147483649\",\"f32:1086918619\"]],[\"invoke\",1685,0,\"min\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",1690,0,\"min\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",1699,0,\"min\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",1704,0,\"min\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",1710,0,\"min\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",1715,0,\"min\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",1720,0,\"min\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",1725,0,\"min\",[\"f32:8388608\",\"f32:4286578687\"]],[\"invoke\",1730,0,\"min\",[\"f32:8388608\",\"f32:2139095040\"]],[\"invoke\",1739,0,\"min\",[\"f32:3204448256\",\"f32:2147483648\"]],[\"invoke\",1744,0,\"min\",[\"f32:3204448256\",\"f32:1\"]],[\"invoke\",1749,0,\"min\",[\"f32:1056964608\",\"f32:2155872256\"]],[\"invoke\",1754,0,\"min\",[\"f32:1056964608\",\"f32:1056964608\"]],[\"invoke\",1759,0,\"min\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",1764,0,\"min\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",1769,0,\"min\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",1779,0,\"min\",[\"f32:3212836864\",\"f32:2147483648\"]],[\"invoke\",1784,0,\"min\",[\"f32:3212836864\",\"f32:1\"]],[\"invoke\",1789,0,\"min\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",1794,0,\"min\",[\"f32:1065353216\",\"f32:1056964608\"]],[\"invoke\",1799,0,\"min\",[\"f32:3212836864\",\"f32:3234402267\"]],[\"invoke\",1804,0,\"min\",[\"f32:3212836864\",\"f32:2139095039\"]],[\"invoke\",1809,0,\"min\",[\"f32:1065353216\",\"f32:4286578688\"]],[\"invoke\",1817,0,\"min\",[\"f32:1065353216\",\"f32:2143289344\"]],[\"invoke\",1823,0,\"min\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",1828,0,\"min\",[\"f32:3234402267\",\"f32:8388608\"]],[\"invoke\",1833,0,\"min\",[\"f32:1086918619\",\"f32:3204448256\"]],[\"invoke\",1838,0,\"min\",[\"f32:1086918619\",\"f32:1065353216\"]],[\"invoke\",1844,0,\"min\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",1849,0,\"min\",[\"f32:1086918619\",\"f32:4286578688\"]],[\"invoke\",1857,0,\"min\",[\"f32:1086918619\",\"f32:2143289344\"]],[\"invoke\",1863,0,\"min\",[\"f32:4286578687\",\"f32:2147483649\"]],[\"invoke\",1868,0,\"min\",[\"f32:4286578687\",\"f32:8388608\"]],[\"invoke\",1873,0,\"min\",[\"f32:2139095039\",\"f32:3204448256\"]],[\"invoke\",1878,0,\"min\",[\"f32:2139095039\",\"f32:1065353216\"]],[\"invoke\",1883,0,\"min\",[\"f32:4286578687\",\"f32:4286578687\"]],[\"invoke\",1888,0,\"min\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",1895,0,\"min\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",1902,0,\"min\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",1908,0,\"min\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",1913,0,\"min\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",1918,0,\"min\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",1923,0,\"min\",[\"f32:4286578688\",\"f32:4286578687\"]],[\"invoke\",1928,0,\"min\",[\"f32:4286578688\",\"f32:2139095040\"]],[\"invoke\",1935,0,\"min\",[\"f32:2139095040\",\"f32:4290772992\"]],[\"invoke\",1945,0,\"min\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",1955,0,\"min\",[\"f32:4290772992\",\"f32:2155872256\"]],[\"invoke\",1965,0,\"min\",[\"f32:4290772992\",\"f32:1056964608\"]],[\"invoke\",1975,0,\"min\",[\"f32:2143289344\",\"f32:3212836864\"]],[\"invoke\",1985,0,\"min\",[\"f32:2143289344\",\"f32:1086918619\"]],[\"invoke\",1995,0,\"min\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",2011,0,\"min\",[\"f32:2143289344\",\"f32:4290772992\"]],[\"invoke\",2022,0,\"max\",[\"f32:0\",\"f32:0\"]],[\"invoke\",2027,0,\"max\",[\"f32:2147483648\",\"f32:2155872256\"]],[\"invoke\",2032,0,\"max\",[\"f32:2147483648\",\"f32:1056964608\"]],[\"invoke\",2037,0,\"max\",[\"f32:0\",\"f32:3212836864\"]],[\"invoke\",2042,0,\"max\",[\"f32:0\",\"f32:1086918619\"]],[\"invoke\",2047,0,\"max\",[\"f32:2147483648\",\"f32:4286578688\"]],[\"invoke\",2053,0,\"max\",[\"f32:2147483648\",\"f32:2143289344\"]],[\"invoke\",2061,0,\"max\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",2066,0,\"max\",[\"f32:1\",\"f32:1\"]],[\"invoke\",2071,0,\"max\",[\"f32:2147483649\",\"f32:3204448256\"]],[\"invoke\",2076,0,\"max\",[\"f32:2147483649\",\"f32:1065353216\"]],[\"invoke\",2082,0,\"max\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",2087,0,\"max\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",2093,0,\"max\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",2101,0,\"max\",[\"f32:8388608\",\"f32:2147483648\"]],[\"invoke\",2106,0,\"max\",[\"f32:8388608\",\"f32:1\"]],[\"invoke\",2111,0,\"max\",[\"f32:2155872256\",\"f32:3204448256\"]],[\"invoke\",2116,0,\"max\",[\"f32:2155872256\",\"f32:1065353216\"]],[\"invoke\",2121,0,\"max\",[\"f32:8388608\",\"f32:3234402267\"]],[\"invoke\",2126,0,\"max\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",2131,0,\"max\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",2140,0,\"max\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",2145,0,\"max\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",2151,0,\"max\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",2156,0,\"max\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",2161,0,\"max\",[\"f32:1056964608\",\"f32:3234402267\"]],[\"invoke\",2166,0,\"max\",[\"f32:1056964608\",\"f32:2139095039\"]],[\"invoke\",2171,0,\"max\",[\"f32:3204448256\",\"f32:4290772992\"]],[\"invoke\",2180,0,\"max\",[\"f32:3212836864\",\"f32:0\"]],[\"invoke\",2185,0,\"max\",[\"f32:1065353216\",\"f32:2147483649\"]],[\"invoke\",2190,0,\"max\",[\"f32:1065353216\",\"f32:8388608\"]],[\"invoke\",2195,0,\"max\",[\"f32:3212836864\",\"f32:3212836864\"]],[\"invoke\",2200,0,\"max\",[\"f32:3212836864\",\"f32:1086918619\"]],[\"invoke\",2205,0,\"max\",[\"f32:1065353216\",\"f32:4286578687\"]],[\"invoke\",2211,0,\"max\",[\"f32:3212836864\",\"f32:4290772992\"]],[\"invoke\",2220,0,\"max\",[\"f32:3234402267\",\"f32:0\"]],[\"invoke\",2225,0,\"max\",[\"f32:1086918619\",\"f32:2147483649\"]],[\"invoke\",2230,0,\"max\",[\"f32:1086918619\",\"f32:8388608\"]],[\"invoke\",2235,0,\"max\",[\"f32:3234402267\",\"f32:3212836864\"]],[\"invoke\",2240,0,\"max\",[\"f32:3234402267\",\"f32:1086918619\"]],[\"invoke\",2245,0,\"max\",[\"f32:1086918619\",\"f32:4286578687\"]],[\"invoke\",2250,0,\"max\",[\"f32:1086918619\",\"f32:2139095040\"]],[\"invoke\",2259,0,\"max\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",2264,0,\"max\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",2269,0,\"max\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",2274,0,\"max\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",2280,0,\"max\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",2285,0,\"max\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",2290,0,\"max\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",2299,0,\"max\",[\"f32:4286578688\",\"f32:2147483648\"]],[\"invoke\",2304,0,\"max\",[\"f32:4286578688\",\"f32:1\"]],[\"invoke\",2309,0,\"max\",[\"f32:2139095040\",\"f32:2155872256\"]],[\"invoke\",2314,0,\"max\",[\"f32:2139095040\",\"f32:1056964608\"]],[\"invoke\",2319,0,\"max\",[\"f32:4286578688\",\"f32:3234402267\"]],[\"invoke\",2324,0,\"max\",[\"f32:4286578688\",\"f32:2139095039\"]],[\"invoke\",2329,0,\"max\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",2337,0,\"max\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",2347,0,\"max\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",2359,0,\"max\",[\"f32:2143289344\",\"f32:2155872256\"]],[\"invoke\",2369,0,\"max\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",2379,0,\"max\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",2389,0,\"max\",[\"f32:4290772992\",\"f32:2139095039\"]],[\"invoke\",2399,0,\"max\",[\"f32:2143289344\",\"f32:4286578688\"]],[\"invoke\",2415,0,\"max\",[\"f32:2143289344\",\"f32:2143289344\"]],[\"invoke\",2423,0,\"sqrt\",[\"f32:2155872256\"]],[\"invoke\",2428,0,\"sqrt\",[\"f32:1065353216\"]],[\"invoke\",2433,0,\"sqrt\",[\"f32:4286578688\"]],[\"invoke\",2440,0,\"floor\",[\"f32:0\"]],[\"invoke\",2445,0,\"floor\",[\"f32:3204448256\"]],[\"invoke\",2450,0,\"floor\",[\"f32:1086918619\"]],[\"invoke\",2457,0,\"floor\",[\"f32:2143289344\"]],[\"invoke\",2463,0,\"ceil\",[\"f32:2155872256\"]],[\"invoke\",2468,0,\"ceil\",[\"f32:1065353216\"]],[\"invoke\",2473,0,\"ceil\",[\"f32:4286578688\"]],[\"invoke\",2480,0,\"trunc\",[\"f32:0\"]],[\"invoke\",2485,0,\"trunc\",[\"f32:3204448256\"]],[\"invoke\",2490,0,\"trunc\",[\"f32:1086918619\"]],[\"invoke\",2495,0,\"trunc\",[\"f32:4290772992\"]],[\"invoke\",2502,0,\"nearest\",[\"f32:1\"]],[\"invoke\",2507,0,\"nearest\",[\"f32:3212836864\"]],[\"invoke\",2512,0,\"nearest\",[\"f32:2139095039\"]],[\"file\",\"f64.wast\"],[\"module\",5,1],[\"invoke\",19,0,\"add\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",25,0,\"add\",[\"f64:0\",\"f64:9223372036854775809\"]],[\"invoke\",30,0,\"add\",[\"f64:0\",\"f64:4503599627370496\"]],[\"invoke\",35,0,\"add\",[\"f64:9223372036854775808\",\"f64:13830554455654793216\"]],[\"invoke\",40,0,\"add\",[\"f64:9223372036854775808\",\"f64:4618760256179416344\"]],[\"invoke\",45,0,\"add\",[\"f64:0\",\"f64:18442240474082181119\"]],[\"invoke\",50,0,\"add\",[\"f64:0\",\"f64:9218868437227405312\"]],[\"invoke\",59,0,\"add\",[\"f64:9223372036854775809\",\"f64:9223372036854775808\"]],[\"invoke\",64,0,\"add\",[\"f64:9223372036854775809\",\"f64:1\"]],[\"invoke\",69,0,\"add\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",74,0,\"add\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",79,0,\"add\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",85,0,\"add\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",90,0,\"add\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",99,0,\"add\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",104,0,\"add\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",109,0,\"add\",[\"f64:4503599627370496\",\"f64:9227875636482146304\"]],[\"invoke\",114,0,\"add\",[\"f64:4503599627370496\",\"f64:4602678819172646912\"]],[\"invoke\",119,0,\"add\",[\"f64:9227875636482146304\",\"f64:13842132293034192152\"]],[\"invoke\",124,0,\"add\",[\"f64:9227875636482146304\",\"f64:9218868437227405311\"]],[\"invoke\",129,0,\"add\",[\"f64:4503599627370496\",\"f64:18442240474082181120\"]],[\"invoke\",137,0,\"add\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",143,0,\"add\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",148,0,\"add\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",154,0,\"add\",[\"f64:4602678819172646912\",\"f64:4602678819172646912\"]],[\"invoke\",159,0,\"add\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",164,0,\"add\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",169,0,\"add\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",177,0,\"add\",[\"f64:4602678819172646912\",\"f64:9221120237041090560\"]],[\"invoke\",183,0,\"add\",[\"f64:13830554455654793216\",\"f64:9223372036854775809\"]],[\"invoke\",188,0,\"add\",[\"f64:13830554455654793216\",\"f64:4503599627370496\"]],[\"invoke\",193,0,\"add\",[\"f64:4607182418800017408\",\"f64:13826050856027422720\"]],[\"invoke\",198,0,\"add\",[\"f64:4607182418800017408\",\"f64:4607182418800017408\"]],[\"invoke\",203,0,\"add\",[\"f64:13830554455654793216\",\"f64:18442240474082181119\"]],[\"invoke\",208,0,\"add\",[\"f64:13830554455654793216\",\"f64:9218868437227405312\"]],[\"invoke\",215,0,\"add\",[\"f64:4607182418800017408\",\"f64:18444492273895866368\"]],[\"invoke\",223,0,\"add\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",228,0,\"add\",[\"f64:13842132293034192152\",\"f64:4503599627370496\"]],[\"invoke\",233,0,\"add\",[\"f64:4618760256179416344\",\"f64:13826050856027422720\"]],[\"invoke\",238,0,\"add\",[\"f64:4618760256179416344\",\"f64:4607182418800017408\"]],[\"invoke\",243,0,\"add\",[\"f64:13842132293034192152\",\"f64:18442240474082181119\"]],[\"invoke\",248,0,\"add\",[\"f64:13842132293034192152\",\"f64:9218868437227405312\"]],[\"invoke\",255,0,\"add\",[\"f64:4618760256179416344\",\"f64:18444492273895866368\"]],[\"invoke\",262,0,\"add\",[\"f64:9218868437227405311\",\"f64:0\"]],[\"invoke\",267,0,\"add\",[\"f64:18442240474082181119\",\"f64:9227875636482146304\"]],[\"invoke\",272,0,\"add\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",277,0,\"add\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",282,0,\"add\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",288,0,\"add\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",295,0,\"add\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",302,0,\"add\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",307,0,\"add\",[\"f64:18442240474082181120\",\"f64:9227875636482146304\"]],[\"invoke\",312,0,\"add\",[\"f64:18442240474082181120\",\"f64:4602678819172646912\"]],[\"invoke\",317,0,\"add\",[\"f64:9218868437227405312\",\"f64:13830554455654793216\"]],[\"invoke\",322,0,\"add\",[\"f64:9218868437227405312\",\"f64:4618760256179416344\"]],[\"invoke\",327,0,\"add\",[\"f64:18442240474082181120\",\"f64:18442240474082181120\"]],[\"invoke\",333,0,\"add\",[\"f64:18442240474082181120\",\"f64:9221120237041090560\"]],[\"invoke\",343,0,\"add\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",353,0,\"add\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",365,0,\"add\",[\"f64:18444492273895866368\",\"f64:4602678819172646912\"]],[\"invoke\",375,0,\"add\",[\"f64:9221120237041090560\",\"f64:13830554455654793216\"]],[\"invoke\",385,0,\"add\",[\"f64:9221120237041090560\",\"f64:4618760256179416344\"]],[\"invoke\",395,0,\"add\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",407,0,\"add\",[\"f64:18444492273895866368\",\"f64:9221120237041090560\"]],[\"invoke\",421,0,\"sub\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",426,0,\"sub\",[\"f64:0\",\"f64:1\"]],[\"invoke\",431,0,\"sub\",[\"f64:9223372036854775808\",\"f64:13826050856027422720\"]],[\"invoke\",436,0,\"sub\",[\"f64:9223372036854775808\",\"f64:4607182418800017408\"]],[\"invoke\",441,0,\"sub\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",446,0,\"sub\",[\"f64:0\",\"f64:9218868437227405311\"]],[\"invoke\",451,0,\"sub\",[\"f64:9223372036854775808\",\"f64:18444492273895866368\"]],[\"invoke\",461,0,\"sub\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",466,0,\"sub\",[\"f64:1\",\"f64:1\"]],[\"invoke\",471,0,\"sub\",[\"f64:9223372036854775809\",\"f64:13826050856027422720\"]],[\"invoke\",476,0,\"sub\",[\"f64:9223372036854775809\",\"f64:4607182418800017408\"]],[\"invoke\",481,0,\"sub\",[\"f64:1\",\"f64:13842132293034192152\"]],[\"invoke\",486,0,\"sub\",[\"f64:1\",\"f64:9218868437227405311\"]],[\"invoke\",491,0,\"sub\",[\"f64:9223372036854775809\",\"f64:18444492273895866368\"]],[\"invoke\",500,0,\"sub\",[\"f64:9227875636482146304\",\"f64:0\"]],[\"invoke\",505,0,\"sub\",[\"f64:4503599627370496\",\"f64:9223372036854775809\"]],[\"invoke\",510,0,\"sub\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",515,0,\"sub\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",520,0,\"sub\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",526,0,\"sub\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",531,0,\"sub\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",540,0,\"sub\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",545,0,\"sub\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",550,0,\"sub\",[\"f64:4602678819172646912\",\"f64:4503599627370496\"]],[\"invoke\",555,0,\"sub\",[\"f64:13826050856027422720\",\"f64:13830554455654793216\"]],[\"invoke\",560,0,\"sub\",[\"f64:13826050856027422720\",\"f64:4618760256179416344\"]],[\"invoke\",565,0,\"sub\",[\"f64:4602678819172646912\",\"f64:18442240474082181119\"]],[\"invoke\",570,0,\"sub\",[\"f64:4602678819172646912\",\"f64:9218868437227405312\"]],[\"invoke\",579,0,\"sub\",[\"f64:13830554455654793216\",\"f64:9223372036854775808\"]],[\"invoke\",584,0,\"sub\",[\"f64:13830554455654793216\",\"f64:1\"]],[\"invoke\",589,0,\"sub\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",595,0,\"sub\",[\"f64:13830554455654793216\",\"f64:13830554455654793216\"]],[\"invoke\",600,0,\"sub\",[\"f64:13830554455654793216\",\"f64:4618760256179416344\"]],[\"invoke\",605,0,\"sub\",[\"f64:4607182418800017408\",\"f64:18442240474082181119\"]],[\"invoke\",610,0,\"sub\",[\"f64:4607182418800017408\",\"f64:9218868437227405312\"]],[\"invoke\",619,0,\"sub\",[\"f64:13842132293034192152\",\"f64:9223372036854775808\"]],[\"invoke\",624,0,\"sub\",[\"f64:13842132293034192152\",\"f64:1\"]],[\"invoke\",629,0,\"sub\",[\"f64:4618760256179416344\",\"f64:9227875636482146304\"]],[\"invoke\",634,0,\"sub\",[\"f64:4618760256179416344\",\"f64:4602678819172646912\"]],[\"invoke\",639,0,\"sub\",[\"f64:13842132293034192152\",\"f64:13842132293034192152\"]],[\"invoke\",644,0,\"sub\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",649,0,\"sub\",[\"f64:4618760256179416344\",\"f64:18442240474082181120\"]],[\"invoke\",659,0,\"sub\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",664,0,\"sub\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",669,0,\"sub\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",674,0,\"sub\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",679,0,\"sub\",[\"f64:18442240474082181119\",\"f64:13842132293034192152\"]],[\"invoke\",684,0,\"sub\",[\"f64:18442240474082181119\",\"f64:9218868437227405311\"]],[\"invoke\",689,0,\"sub\",[\"f64:9218868437227405311\",\"f64:18442240474082181120\"]],[\"invoke\",697,0,\"sub\",[\"f64:9218868437227405311\",\"f64:9221120237041090560\"]],[\"invoke\",703,0,\"sub\",[\"f64:18442240474082181120\",\"f64:9223372036854775809\"]],[\"invoke\",708,0,\"sub\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",713,0,\"sub\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",718,0,\"sub\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",724,0,\"sub\",[\"f64:18442240474082181120\",\"f64:9218868437227405311\"]],[\"invoke\",729,0,\"sub\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",737,0,\"sub\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",747,0,\"sub\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",757,0,\"sub\",[\"f64:18444492273895866368\",\"f64:4503599627370496\"]],[\"invoke\",767,0,\"sub\",[\"f64:9221120237041090560\",\"f64:13826050856027422720\"]],[\"invoke\",777,0,\"sub\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\"]],[\"invoke\",787,0,\"sub\",[\"f64:18444492273895866368\",\"f64:18442240474082181119\"]],[\"invoke\",797,0,\"sub\",[\"f64:18444492273895866368\",\"f64:9218868437227405312\"]],[\"invoke\",811,0,\"sub\",[\"f64:9221120237041090560\",\"f64:18444492273895866368\"]],[\"invoke\",822,0,\"mul\",[\"f64:0\",\"f64:0\"]],[\"invoke\",827,0,\"mul\",[\"f64:9223372036854775808\",\"f64:9227875636482146304\"]],[\"invoke\",833,0,\"mul\",[\"f64:0\",\"f64:13826050856027422720\"]],[\"invoke\",838,0,\"mul\",[\"f64:0\",\"f64:4607182418800017408\"]],[\"invoke\",843,0,\"mul\",[\"f64:9223372036854775808\",\"f64:18442240474082181119\"]],[\"invoke\",848,0,\"mul\",[\"f64:9223372036854775808\",\"f64:9218868437227405312\"]],[\"invoke\",855,0,\"mul\",[\"f64:0\",\"f64:18444492273895866368\"]],[\"invoke\",862,0,\"mul\",[\"f64:1\",\"f64:0\"]],[\"invoke\",867,0,\"mul\",[\"f64:9223372036854775809\",\"f64:9227875636482146304\"]],[\"invoke\",872,0,\"mul\",[\"f64:9223372036854775809\",\"f64:4602678819172646912\"]],[\"invoke\",877,0,\"mul\",[\"f64:1\",\"f64:13830554455654793216\"]],[\"invoke\",882,0,\"mul\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",887,0,\"mul\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",893,0,\"mul\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",902,0,\"mul\",[\"f64:4503599627370496\",\"f64:0\"]],[\"invoke\",907,0,\"mul\",[\"f64:9227875636482146304\",\"f64:9227875636482146304\"]],[\"invoke\",912,0,\"mul\",[\"f64:9227875636482146304\",\"f64:4602678819172646912\"]],[\"invoke\",917,0,\"mul\",[\"f64:4503599627370496\",\"f64:13830554455654793216\"]],[\"invoke\",922,0,\"mul\",[\"f64:4503599627370496\",\"f64:4618760256179416344\"]],[\"invoke\",927,0,\"mul\",[\"f64:9227875636482146304\",\"f64:18442240474082181120\"]],[\"invoke\",933,0,\"mul\",[\"f64:9227875636482146304\",\"f64:9221120237041090560\"]],[\"invoke\",941,0,\"mul\",[\"f64:4602678819172646912\",\"f64:9223372036854775808\"]],[\"invoke\",946,0,\"mul\",[\"f64:4602678819172646912\",\"f64:1\"]],[\"invoke\",951,0,\"mul\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",956,0,\"mul\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",962,0,\"mul\",[\"f64:4602678819172646912\",\"f64:4618760256179416344\"]],[\"invoke\",967,0,\"mul\",[\"f64:13826050856027422720\",\"f64:18442240474082181120\"]],[\"invoke\",973,0,\"mul\",[\"f64:13826050856027422720\",\"f64:9221120237041090560\"]],[\"invoke\",981,0,\"mul\",[\"f64:4607182418800017408\",\"f64:9223372036854775808\"]],[\"invoke\",986,0,\"mul\",[\"f64:4607182418800017408\",\"f64:1\"]],[\"invoke\",991,0,\"mul\",[\"f64:13830554455654793216\",\"f64:13826050856027422720\"]],[\"invoke\",996,0,\"mul\",[\"f64:13830554455654793216\",\"f64:4607182418800017408\"]],[\"invoke\",1001,0,\"mul\",[\"f64:4607182418800017408\",\"f64:13842132293034192152\"]],[\"invoke\",1006,0,\"mul\",[\"f64:4607182418800017408\",\"f64:9218868437227405311\"]],[\"invoke\",1011,0,\"mul\",[\"f64:13830554455654793216\",\"f64:18444492273895866368\"]],[\"invoke\",1020,0,\"mul\",[\"f64:13842132293034192152\",\"f64:0\"]],[\"invoke\",1025,0,\"mul\",[\"f64:4618760256179416344\",\"f64:9223372036854775809\"]],[\"invoke\",1031,0,\"mul\",[\"f64:13842132293034192152\",\"f64:13826050856027422720\"]],[\"invoke\",1036,0,\"mul\",[\"f64:13842132293034192152\",\"f64:4607182418800017408\"]],[\"invoke\",1041,0,\"mul\",[\"f64:4618760256179416344\",\"f64:13842132293034192152\"]],[\"invoke\",1046,0,\"mul\",[\"f64:4618760256179416344\",\"f64:9218868437227405311\"]],[\"invoke\",1051,0,\"mul\",[\"f64:13842132293034192152\",\"f64:18444492273895866368\"]],[\"invoke\",1060,0,\"mul\",[\"f64:18442240474082181119\",\"f64:0\"]],[\"invoke\",1065,0,\"mul\",[\"f64:9218868437227405311\",\"f64:9223372036854775809\"]],[\"invoke\",1070,0,\"mul\",[\"f64:9218868437227405311\",\"f64:4503599627370496\"]],[\"invoke\",1075,0,\"mul\",[\"f64:18442240474082181119\",\"f64:13830554455654793216\"]],[\"invoke\",1080,0,\"mul\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",1085,0,\"mul\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",1090,0,\"mul\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",1100,0,\"mul\",[\"f64:18442240474082181120\",\"f64:0\"]],[\"invoke\",1105,0,\"mul\",[\"f64:9218868437227405312\",\"f64:9223372036854775809\"]],[\"invoke\",1110,0,\"mul\",[\"f64:9218868437227405312\",\"f64:4503599627370496\"]],[\"invoke\",1115,0,\"mul\",[\"f64:18442240474082181120\",\"f64:13830554455654793216\"]],[\"invoke\",1120,0,\"mul\",[\"f64:18442240474082181120\",\"f64:4618760256179416344\"]],[\"invoke\",1125,0,\"mul\",[\"f64:9218868437227405312\",\"f64:18442240474082181119\"]],[\"invoke\",1130,0,\"mul\",[\"f64:9218868437227405312\",\"f64:9218868437227405312\"]],[\"invoke\",1139,0,\"mul\",[\"f64:18444492273895866368\",\"f64:9223372036854775808\"]],[\"invoke\",1149,0,\"mul\",[\"f64:18444492273895866368\",\"f64:1\"]],[\"invoke\",1159,0,\"mul\",[\"f64:9221120237041090560\",\"f64:9227875636482146304\"]],[\"invoke\",1169,0,\"mul\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",1179,0,\"mul\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",1191,0,\"mul\",[\"f64:9221120237041090560\",\"f64:18442240474082181119\"]],[\"invoke\",1201,0,\"mul\",[\"f64:9221120237041090560\",\"f64:9218868437227405312\"]],[\"invoke\",1219,0,\"div\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",1224,0,\"div\",[\"f64:9223372036854775808\",\"f64:1\"]],[\"invoke\",1229,0,\"div\",[\"f64:0\",\"f64:9227875636482146304\"]],[\"invoke\",1234,0,\"div\",[\"f64:0\",\"f64:4602678819172646912\"]],[\"invoke\",1239,0,\"div\",[\"f64:9223372036854775808\",\"f64:13842132293034192152\"]],[\"invoke\",1244,0,\"div\",[\"f64:9223372036854775808\",\"f64:9218868437227405311\"]],[\"invoke\",1249,0,\"div\",[\"f64:0\",\"f64:18442240474082181120\"]],[\"invoke\",1257,0,\"div\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",1263,0,\"div\",[\"f64:9223372036854775809\",\"f64:9223372036854775809\"]],[\"invoke\",1269,0,\"div\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",1274,0,\"div\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",1279,0,\"div\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",1284,0,\"div\",[\"f64:9223372036854775809\",\"f64:9218868437227405311\"]],[\"invoke\",1289,0,\"div\",[\"f64:1\",\"f64:18442240474082181120\"]],[\"invoke\",1297,0,\"div\",[\"f64:1\",\"f64:9221120237041090560\"]],[\"invoke\",1303,0,\"div\",[\"f64:9227875636482146304\",\"f64:9223372036854775809\"]],[\"invoke\",1308,0,\"div\",[\"f64:9227875636482146304\",\"f64:4503599627370496\"]],[\"invoke\",1313,0,\"div\",[\"f64:4503599627370496\",\"f64:13826050856027422720\"]],[\"invoke\",1318,0,\"div\",[\"f64:4503599627370496\",\"f64:4607182418800017408\"]],[\"invoke\",1323,0,\"div\",[\"f64:9227875636482146304\",\"f64:18442240474082181119\"]],[\"invoke\",1328,0,\"div\",[\"f64:9227875636482146304\",\"f64:9218868437227405312\"]],[\"invoke\",1337,0,\"div\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",1343,0,\"div\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",1348,0,\"div\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",1353,0,\"div\",[\"f64:4602678819172646912\",\"f64:13826050856027422720\"]],[\"invoke\",1358,0,\"div\",[\"f64:4602678819172646912\",\"f64:4607182418800017408\"]],[\"invoke\",1363,0,\"div\",[\"f64:13826050856027422720\",\"f64:18442240474082181119\"]],[\"invoke\",1368,0,\"div\",[\"f64:13826050856027422720\",\"f64:9218868437227405312\"]],[\"invoke\",1375,0,\"div\",[\"f64:4602678819172646912\",\"f64:18444492273895866368\"]],[\"invoke\",1382,0,\"div\",[\"f64:4607182418800017408\",\"f64:0\"]],[\"invoke\",1387,0,\"div\",[\"f64:13830554455654793216\",\"f64:9227875636482146304\"]],[\"invoke\",1392,0,\"div\",[\"f64:13830554455654793216\",\"f64:4602678819172646912\"]],[\"invoke\",1397,0,\"div\",[\"f64:4607182418800017408\",\"f64:13830554455654793216\"]],[\"invoke\",1403,0,\"div\",[\"f64:13830554455654793216\",\"f64:18442240474082181119\"]],[\"invoke\",1408,0,\"div\",[\"f64:13830554455654793216\",\"f64:9218868437227405312\"]],[\"invoke\",1415,0,\"div\",[\"f64:4607182418800017408\",\"f64:18444492273895866368\"]],[\"invoke\",1422,0,\"div\",[\"f64:4618760256179416344\",\"f64:0\"]],[\"invoke\",1427,0,\"div\",[\"f64:13842132293034192152\",\"f64:9227875636482146304\"]],[\"invoke\",1432,0,\"div\",[\"f64:13842132293034192152\",\"f64:4602678819172646912\"]],[\"invoke\",1437,0,\"div\",[\"f64:4618760256179416344\",\"f64:13830554455654793216\"]],[\"invoke\",1442,0,\"div\",[\"f64:4618760256179416344\",\"f64:4618760256179416344\"]],[\"invoke\",1447,0,\"div\",[\"f64:13842132293034192152\",\"f64:18442240474082181120\"]],[\"invoke\",1453,0,\"div\",[\"f64:13842132293034192152\",\"f64:9221120237041090560\"]],[\"invoke\",1461,0,\"div\",[\"f64:9218868437227405311\",\"f64:9223372036854775808\"]],[\"invoke\",1466,0,\"div\",[\"f64:9218868437227405311\",\"f64:1\"]],[\"invoke\",1472,0,\"div\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",1477,0,\"div\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",1482,0,\"div\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",1487,0,\"div\",[\"f64:18442240474082181119\",\"f64:18442240474082181120\"]],[\"invoke\",1493,0,\"div\",[\"f64:18442240474082181119\",\"f64:9221120237041090560\"]],[\"invoke\",1501,0,\"div\",[\"f64:9218868437227405312\",\"f64:9223372036854775808\"]],[\"invoke\",1506,0,\"div\",[\"f64:9218868437227405312\",\"f64:1\"]],[\"invoke\",1511,0,\"div\",[\"f64:18442240474082181120\",\"f64:13826050856027422720\"]],[\"invoke\",1516,0,\"div\",[\"f64:18442240474082181120\",\"f64:4607182418800017408\"]],[\"invoke\",1521,0,\"div\",[\"f64:9218868437227405312\",\"f64:13842132293034192152\"]],[\"invoke\",1526,0,\"div\",[\"f64:9218868437227405312\",\"f64:9218868437227405311\"]],[\"invoke\",1531,0,\"div\",[\"f64:18442240474082181120\",\"f64:18444492273895866368\"]],[\"invoke\",1543,0,\"div\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",1553,0,\"div\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",1563,0,\"div\",[\"f64:18444492273895866368\",\"f64:13826050856027422720\"]],[\"invoke\",1573,0,\"div\",[\"f64:18444492273895866368\",\"f64:4607182418800017408\"]],[\"invoke\",1583,0,\"div\",[\"f64:9221120237041090560\",\"f64:13842132293034192152\"]],[\"invoke\",1593,0,\"div\",[\"f64:9221120237041090560\",\"f64:9218868437227405311\"]],[\"invoke\",1603,0,\"div\",[\"f64:18444492273895866368\",\"f64:18444492273895866368\"]],[\"invoke\",1620,0,\"min\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1625,0,\"min\",[\"f64:0\",\"f64:9223372036854775809\"]],[\"invoke\",1630,0,\"min\",[\"f64:0\",\"f64:4503599627370496\"]],[\"invoke\",1635,0,\"min\",[\"f64:9223372036854775808\",\"f64:13830554455654793216\"]],[\"invoke\",1641,0,\"min\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",1646,0,\"min\",[\"f64:0\",\"f64:9218868437227405311\"]],[\"invoke\",1651,0,\"min\",[\"f64:9223372036854775808\",\"f64:18444492273895866368\"]],[\"invoke\",1660,0,\"min\",[\"f64:9223372036854775809\",\"f64:0\"]],[\"invoke\",1665,0,\"min\",[\"f64:1\",\"f64:9223372036854775809\"]],[\"invoke\",1670,0,\"min\",[\"f64:1\",\"f64:4503599627370496\"]],[\"invoke\",1675,0,\"min\",[\"f64:9223372036854775809\",\"f64:13830554455654793216\"]],[\"invoke\",1680,0,\"min\",[\"f64:9223372036854775809\",\"f64:4618760256179416344\"]],[\"invoke\",1685,0,\"min\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",1690,0,\"min\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",1699,0,\"min\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",1704,0,\"min\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",1710,0,\"min\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",1715,0,\"min\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",1720,0,\"min\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",1725,0,\"min\",[\"f64:4503599627370496\",\"f64:18442240474082181119\"]],[\"invoke\",1730,0,\"min\",[\"f64:4503599627370496\",\"f64:9218868437227405312\"]],[\"invoke\",1739,0,\"min\",[\"f64:13826050856027422720\",\"f64:9223372036854775808\"]],[\"invoke\",1744,0,\"min\",[\"f64:13826050856027422720\",\"f64:1\"]],[\"invoke\",1749,0,\"min\",[\"f64:4602678819172646912\",\"f64:9227875636482146304\"]],[\"invoke\",1754,0,\"min\",[\"f64:4602678819172646912\",\"f64:4602678819172646912\"]],[\"invoke\",1759,0,\"min\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",1764,0,\"min\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",1769,0,\"min\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",1779,0,\"min\",[\"f64:13830554455654793216\",\"f64:9223372036854775808\"]],[\"invoke\",1784,0,\"min\",[\"f64:13830554455654793216\",\"f64:1\"]],[\"invoke\",1789,0,\"min\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",1794,0,\"min\",[\"f64:4607182418800017408\",\"f64:4602678819172646912\"]],[\"invoke\",1799,0,\"min\",[\"f64:13830554455654793216\",\"f64:13842132293034192152\"]],[\"invoke\",1804,0,\"min\",[\"f64:13830554455654793216\",\"f64:9218868437227405311\"]],[\"invoke\",1809,0,\"min\",[\"f64:4607182418800017408\",\"f64:18442240474082181120\"]],[\"invoke\",1817,0,\"min\",[\"f64:4607182418800017408\",\"f64:9221120237041090560\"]],[\"invoke\",1823,0,\"min\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",1828,0,\"min\",[\"f64:13842132293034192152\",\"f64:4503599627370496\"]],[\"invoke\",1833,0,\"min\",[\"f64:4618760256179416344\",\"f64:13826050856027422720\"]],[\"invoke\",1838,0,\"min\",[\"f64:4618760256179416344\",\"f64:4607182418800017408\"]],[\"invoke\",1844,0,\"min\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",1849,0,\"min\",[\"f64:4618760256179416344\",\"f64:18442240474082181120\"]],[\"invoke\",1857,0,\"min\",[\"f64:4618760256179416344\",\"f64:9221120237041090560\"]],[\"invoke\",1863,0,\"min\",[\"f64:18442240474082181119\",\"f64:9223372036854775809\"]],[\"invoke\",1868,0,\"min\",[\"f64:18442240474082181119\",\"f64:4503599627370496\"]],[\"invoke\",1873,0,\"min\",[\"f64:9218868437227405311\",\"f64:13826050856027422720\"]],[\"invoke\",1878,0,\"min\",[\"f64:9218868437227405311\",\"f64:4607182418800017408\"]],[\"invoke\",1883,0,\"min\",[\"f64:18442240474082181119\",\"f64:18442240474082181119\"]],[\"invoke\",1888,0,\"min\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",1895,0,\"min\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",1902,0,\"min\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",1908,0,\"min\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",1913,0,\"min\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",1918,0,\"min\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",1923,0,\"min\",[\"f64:18442240474082181120\",\"f64:18442240474082181119\"]],[\"invoke\",1928,0,\"min\",[\"f64:18442240474082181120\",\"f64:9218868437227405312\"]],[\"invoke\",1935,0,\"min\",[\"f64:9218868437227405312\",\"f64:18444492273895866368\"]],[\"invoke\",1945,0,\"min\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1955,0,\"min\",[\"f64:18444492273895866368\",\"f64:9227875636482146304\"]],[\"invoke\",1965,0,\"min\",[\"f64:18444492273895866368\",\"f64:4602678819172646912\"]],[\"invoke\",1975,0,\"min\",[\"f64:9221120237041090560\",\"f64:13830554455654793216\"]],[\"invoke\",1985,0,\"min\",[\"f64:9221120237041090560\",\"f64:4618760256179416344\"]],[\"invoke\",1995,0,\"min\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",2011,0,\"min\",[\"f64:9221120237041090560\",\"f64:18444492273895866368\"]],[\"invoke\",2022,0,\"max\",[\"f64:0\",\"f64:0\"]],[\"invoke\",2027,0,\"max\",[\"f64:9223372036854775808\",\"f64:9227875636482146304\"]],[\"invoke\",2032,0,\"max\",[\"f64:9223372036854775808\",\"f64:4602678819172646912\"]],[\"invoke\",2037,0,\"max\",[\"f64:0\",\"f64:13830554455654793216\"]],[\"invoke\",2042,0,\"max\",[\"f64:0\",\"f64:4618760256179416344\"]],[\"invoke\",2047,0,\"max\",[\"f64:9223372036854775808\",\"f64:18442240474082181120\"]],[\"invoke\",2053,0,\"max\",[\"f64:9223372036854775808\",\"f64:9221120237041090560\"]],[\"invoke\",2061,0,\"max\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",2066,0,\"max\",[\"f64:1\",\"f64:1\"]],[\"invoke\",2071,0,\"max\",[\"f64:9223372036854775809\",\"f64:13826050856027422720\"]],[\"invoke\",2076,0,\"max\",[\"f64:9223372036854775809\",\"f64:4607182418800017408\"]],[\"invoke\",2082,0,\"max\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",2087,0,\"max\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",2093,0,\"max\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",2101,0,\"max\",[\"f64:4503599627370496\",\"f64:9223372036854775808\"]],[\"invoke\",2106,0,\"max\",[\"f64:4503599627370496\",\"f64:1\"]],[\"invoke\",2111,0,\"max\",[\"f64:9227875636482146304\",\"f64:13826050856027422720\"]],[\"invoke\",2116,0,\"max\",[\"f64:9227875636482146304\",\"f64:4607182418800017408\"]],[\"invoke\",2121,0,\"max\",[\"f64:4503599627370496\",\"f64:13842132293034192152\"]],[\"invoke\",2126,0,\"max\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",2131,0,\"max\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",2140,0,\"max\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",2145,0,\"max\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",2151,0,\"max\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",2156,0,\"max\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",2161,0,\"max\",[\"f64:4602678819172646912\",\"f64:13842132293034192152\"]],[\"invoke\",2166,0,\"max\",[\"f64:4602678819172646912\",\"f64:9218868437227405311\"]],[\"invoke\",2171,0,\"max\",[\"f64:13826050856027422720\",\"f64:18444492273895866368\"]],[\"invoke\",2180,0,\"max\",[\"f64:13830554455654793216\",\"f64:0\"]],[\"invoke\",2185,0,\"max\",[\"f64:4607182418800017408\",\"f64:9223372036854775809\"]],[\"invoke\",2190,0,\"max\",[\"f64:4607182418800017408\",\"f64:4503599627370496\"]],[\"invoke\",2195,0,\"max\",[\"f64:13830554455654793216\",\"f64:13830554455654793216\"]],[\"invoke\",2200,0,\"max\",[\"f64:13830554455654793216\",\"f64:4618760256179416344\"]],[\"invoke\",2205,0,\"max\",[\"f64:4607182418800017408\",\"f64:18442240474082181119\"]],[\"invoke\",2211,0,\"max\",[\"f64:13830554455654793216\",\"f64:18444492273895866368\"]],[\"invoke\",2220,0,\"max\",[\"f64:13842132293034192152\",\"f64:0\"]],[\"invoke\",2225,0,\"max\",[\"f64:4618760256179416344\",\"f64:9223372036854775809\"]],[\"invoke\",2230,0,\"max\",[\"f64:4618760256179416344\",\"f64:4503599627370496\"]],[\"invoke\",2235,0,\"max\",[\"f64:13842132293034192152\",\"f64:13830554455654793216\"]],[\"invoke\",2240,0,\"max\",[\"f64:13842132293034192152\",\"f64:4618760256179416344\"]],[\"invoke\",2245,0,\"max\",[\"f64:4618760256179416344\",\"f64:18442240474082181119\"]],[\"invoke\",2250,0,\"max\",[\"f64:4618760256179416344\",\"f64:9218868437227405312\"]],[\"invoke\",2259,0,\"max\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",2264,0,\"max\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",2269,0,\"max\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",2274,0,\"max\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",2280,0,\"max\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",2285,0,\"max\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",2290,0,\"max\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",2299,0,\"max\",[\"f64:18442240474082181120\",\"f64:9223372036854775808\"]],[\"invoke\",2304,0,\"max\",[\"f64:18442240474082181120\",\"f64:1\"]],[\"invoke\",2309,0,\"max\",[\"f64:9218868437227405312\",\"f64:9227875636482146304\"]],[\"invoke\",2314,0,\"max\",[\"f64:9218868437227405312\",\"f64:4602678819172646912\"]],[\"invoke\",2319,0,\"max\",[\"f64:18442240474082181120\",\"f64:13842132293034192152\"]],[\"invoke\",2324,0,\"max\",[\"f64:18442240474082181120\",\"f64:9218868437227405311\"]],[\"invoke\",2329,0,\"max\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",2337,0,\"max\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",2347,0,\"max\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",2359,0,\"max\",[\"f64:9221120237041090560\",\"f64:9227875636482146304\"]],[\"invoke\",2369,0,\"max\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",2379,0,\"max\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",2389,0,\"max\",[\"f64:18444492273895866368\",\"f64:9218868437227405311\"]],[\"invoke\",2399,0,\"max\",[\"f64:9221120237041090560\",\"f64:18442240474082181120\"]],[\"invoke\",2415,0,\"max\",[\"f64:9221120237041090560\",\"f64:9221120237041090560\"]],[\"invoke\",2423,0,\"sqrt\",[\"f64:9227875636482146304\"]],[\"invoke\",2428,0,\"sqrt\",[\"f64:4607182418800017408\"]],[\"invoke\",2433,0,\"sqrt\",[\"f64:18442240474082181120\"]],[\"invoke\",2440,0,\"floor\",[\"f64:0\"]],[\"invoke\",2445,0,\"floor\",[\"f64:13826050856027422720\"]],[\"invoke\",2450,0,\"floor\",[\"f64:4618760256179416344\"]],[\"invoke\",2457,0,\"floor\",[\"f64:9221120237041090560\"]],[\"invoke\",2463,0,\"ceil\",[\"f64:9227875636482146304\"]],[\"invoke\",2468,0,\"ceil\",[\"f64:4607182418800017408\"]],[\"invoke\",2473,0,\"ceil\",[\"f64:18442240474082181120\"]],[\"invoke\",2480,0,\"trunc\",[\"f64:0\"]],[\"invoke\",2485,0,\"trunc\",[\"f64:13826050856027422720\"]],[\"invoke\",2490,0,\"trunc\",[\"f64:4618760256179416344\"]],[\"invoke\",2495,0,\"trunc\",[\"f64:18444492273895866368\"]],[\"invoke\",2502,0,\"nearest\",[\"f64:1\"]],[\"invoke\",2507,0,\"nearest\",[\"f64:13830554455654793216\"]],[\"invoke\",2512,0,\"nearest\",[\"f64:9218868437227405311\"]],[\"file\",\"f32_cmp.wast\"],[\"module\",4,2],[\"invoke\",13,0,\"eq\",[\"f32:2147483648\",\"f32:2147483648\"]],[\"invoke\",18,0,\"eq\",[\"f32:2147483648\",\"f32:1\"]],[\"invoke\",23,0,\"eq\",[\"f32:0\",\"f32:2155872256\"]],[\"invoke\",28,0,\"eq\",[\"f32:0\",\"f32:1056964608\"]],[\"invoke\",33,0,\"eq\",[\"f32:2147483648\",\"f32:3234402267\"]],[\"invoke\",38,0,\"eq\",[\"f32:2147483648\",\"f32:2139095039\"]],[\"invoke\",43,0,\"eq\",[\"f32:0\",\"f32:4286578688\"]],[\"invoke\",51,0,\"eq\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",56,0,\"eq\",[\"f32:1\",\"f32:0\"]],[\"invoke\",61,0,\"eq\",[\"f32:2147483649\",\"f32:2155872256\"]],[\"invoke\",66,0,\"eq\",[\"f32:2147483649\",\"f32:1056964608\"]],[\"invoke\",71,0,\"eq\",[\"f32:1\",\"f32:3212836864\"]],[\"invoke\",76,0,\"eq\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",81,0,\"eq\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",87,0,\"eq\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",94,0,\"eq\",[\"f32:2155872256\",\"f32:0\"]],[\"invoke\",99,0,\"eq\",[\"f32:8388608\",\"f32:2147483649\"]],[\"invoke\",104,0,\"eq\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",109,0,\"eq\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",114,0,\"eq\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",119,0,\"eq\",[\"f32:8388608\",\"f32:4286578687\"]],[\"invoke\",124,0,\"eq\",[\"f32:8388608\",\"f32:2139095040\"]],[\"invoke\",131,0,\"eq\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",137,0,\"eq\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",142,0,\"eq\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",147,0,\"eq\",[\"f32:1056964608\",\"f32:3204448256\"]],[\"invoke\",152,0,\"eq\",[\"f32:1056964608\",\"f32:1065353216\"]],[\"invoke\",157,0,\"eq\",[\"f32:3204448256\",\"f32:4286578687\"]],[\"invoke\",162,0,\"eq\",[\"f32:3204448256\",\"f32:2139095040\"]],[\"invoke\",167,0,\"eq\",[\"f32:3204448256\",\"f32:2143289344\"]],[\"invoke\",175,0,\"eq\",[\"f32:1065353216\",\"f32:2147483648\"]],[\"invoke\",180,0,\"eq\",[\"f32:1065353216\",\"f32:1\"]],[\"invoke\",185,0,\"eq\",[\"f32:3212836864\",\"f32:3204448256\"]],[\"invoke\",190,0,\"eq\",[\"f32:3212836864\",\"f32:1065353216\"]],[\"invoke\",195,0,\"eq\",[\"f32:1065353216\",\"f32:3234402267\"]],[\"invoke\",200,0,\"eq\",[\"f32:1065353216\",\"f32:2139095039\"]],[\"invoke\",204,0,\"eq\",[\"f32:1065353216\",\"f32:2139095040\"]],[\"invoke\",213,0,\"eq\",[\"f32:3234402267\",\"f32:2147483648\"]],[\"invoke\",218,0,\"eq\",[\"f32:3234402267\",\"f32:1\"]],[\"invoke\",223,0,\"eq\",[\"f32:1086918619\",\"f32:2155872256\"]],[\"invoke\",228,0,\"eq\",[\"f32:1086918619\",\"f32:1056964608\"]],[\"invoke\",233,0,\"eq\",[\"f32:3234402267\",\"f32:3234402267\"]],[\"invoke\",238,0,\"eq\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",242,0,\"eq\",[\"f32:3234402267\",\"f32:2139095040\"]],[\"invoke\",249,0,\"eq\",[\"f32:1086918619\",\"f32:4290772992\"]],[\"invoke\",256,0,\"eq\",[\"f32:2139095039\",\"f32:0\"]],[\"invoke\",261,0,\"eq\",[\"f32:4286578687\",\"f32:2155872256\"]],[\"invoke\",266,0,\"eq\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",271,0,\"eq\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",276,0,\"eq\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",281,0,\"eq\",[\"f32:4286578687\",\"f32:4286578688\"]],[\"invoke\",285,0,\"eq\",[\"f32:4286578687\",\"f32:4290772992\"]],[\"invoke\",294,0,\"eq\",[\"f32:4286578688\",\"f32:0\"]],[\"invoke\",299,0,\"eq\",[\"f32:2139095040\",\"f32:2147483649\"]],[\"invoke\",304,0,\"eq\",[\"f32:2139095040\",\"f32:8388608\"]],[\"invoke\",309,0,\"eq\",[\"f32:4286578688\",\"f32:3212836864\"]],[\"invoke\",314,0,\"eq\",[\"f32:4286578688\",\"f32:1086918619\"]],[\"invoke\",319,0,\"eq\",[\"f32:2139095040\",\"f32:4286578687\"]],[\"invoke\",323,0,\"eq\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",331,0,\"eq\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",341,0,\"eq\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",351,0,\"eq\",[\"f32:4290772992\",\"f32:8388608\"]],[\"invoke\",361,0,\"eq\",[\"f32:2143289344\",\"f32:3204448256\"]],[\"invoke\",371,0,\"eq\",[\"f32:2143289344\",\"f32:1065353216\"]],[\"invoke\",381,0,\"eq\",[\"f32:4290772992\",\"f32:4286578687\"]],[\"invoke\",389,0,\"eq\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",401,0,\"eq\",[\"f32:4290772992\",\"f32:2143289344\"]],[\"invoke\",415,0,\"ne\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",420,0,\"ne\",[\"f32:0\",\"f32:1\"]],[\"invoke\",425,0,\"ne\",[\"f32:2147483648\",\"f32:3204448256\"]],[\"invoke\",430,0,\"ne\",[\"f32:2147483648\",\"f32:1065353216\"]],[\"invoke\",435,0,\"ne\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",439,0,\"ne\",[\"f32:0\",\"f32:4286578687\"]],[\"invoke\",444,0,\"ne\",[\"f32:0\",\"f32:2139095040\"]],[\"invoke\",453,0,\"ne\",[\"f32:2147483649\",\"f32:2147483648\"]],[\"invoke\",458,0,\"ne\",[\"f32:2147483649\",\"f32:1\"]],[\"invoke\",463,0,\"ne\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",468,0,\"ne\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",473,0,\"ne\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",477,0,\"ne\",[\"f32:2147483649\",\"f32:4286578687\"]],[\"invoke\",482,0,\"ne\",[\"f32:2147483649\",\"f32:2139095040\"]],[\"invoke\",489,0,\"ne\",[\"f32:1\",\"f32:4290772992\"]],[\"invoke\",496,0,\"ne\",[\"f32:8388608\",\"f32:0\"]],[\"invoke\",501,0,\"ne\",[\"f32:2155872256\",\"f32:2155872256\"]],[\"invoke\",506,0,\"ne\",[\"f32:2155872256\",\"f32:1056964608\"]],[\"invoke\",511,0,\"ne\",[\"f32:8388608\",\"f32:3212836864\"]],[\"invoke\",515,0,\"ne\",[\"f32:8388608\",\"f32:3234402267\"]],[\"invoke\",520,0,\"ne\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",525,0,\"ne\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",534,0,\"ne\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",539,0,\"ne\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",544,0,\"ne\",[\"f32:1056964608\",\"f32:8388608\"]],[\"invoke\",549,0,\"ne\",[\"f32:3204448256\",\"f32:3212836864\"]],[\"invoke\",553,0,\"ne\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",558,0,\"ne\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",563,0,\"ne\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",571,0,\"ne\",[\"f32:1056964608\",\"f32:2143289344\"]],[\"invoke\",577,0,\"ne\",[\"f32:3212836864\",\"f32:2147483649\"]],[\"invoke\",582,0,\"ne\",[\"f32:3212836864\",\"f32:8388608\"]],[\"invoke\",587,0,\"ne\",[\"f32:1065353216\",\"f32:3204448256\"]],[\"invoke\",592,0,\"ne\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"invoke\",596,0,\"ne\",[\"f32:1065353216\",\"f32:1086918619\"]],[\"invoke\",601,0,\"ne\",[\"f32:3212836864\",\"f32:4286578688\"]],[\"invoke\",607,0,\"ne\",[\"f32:3212836864\",\"f32:2143289344\"]],[\"invoke\",615,0,\"ne\",[\"f32:1086918619\",\"f32:2147483648\"]],[\"invoke\",620,0,\"ne\",[\"f32:1086918619\",\"f32:1\"]],[\"invoke\",625,0,\"ne\",[\"f32:3234402267\",\"f32:3204448256\"]],[\"invoke\",630,0,\"ne\",[\"f32:3234402267\",\"f32:1065353216\"]],[\"invoke\",634,0,\"ne\",[\"f32:3234402267\",\"f32:1086918619\"]],[\"invoke\",639,0,\"ne\",[\"f32:1086918619\",\"f32:4286578687\"]],[\"invoke\",644,0,\"ne\",[\"f32:1086918619\",\"f32:2139095040\"]],[\"invoke\",653,0,\"ne\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",658,0,\"ne\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",663,0,\"ne\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",668,0,\"ne\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",672,0,\"ne\",[\"f32:2139095039\",\"f32:1065353216\"]],[\"invoke\",677,0,\"ne\",[\"f32:4286578687\",\"f32:4286578687\"]],[\"invoke\",682,0,\"ne\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",689,0,\"ne\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",696,0,\"ne\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",701,0,\"ne\",[\"f32:4286578688\",\"f32:2155872256\"]],[\"invoke\",706,0,\"ne\",[\"f32:4286578688\",\"f32:1056964608\"]],[\"invoke\",710,0,\"ne\",[\"f32:4286578688\",\"f32:1065353216\"]],[\"invoke\",715,0,\"ne\",[\"f32:2139095040\",\"f32:3234402267\"]],[\"invoke\",720,0,\"ne\",[\"f32:2139095040\",\"f32:2139095039\"]],[\"invoke\",725,0,\"ne\",[\"f32:4286578688\",\"f32:4290772992\"]],[\"invoke\",735,0,\"ne\",[\"f32:4290772992\",\"f32:0\"]],[\"invoke\",745,0,\"ne\",[\"f32:2143289344\",\"f32:2147483649\"]],[\"invoke\",755,0,\"ne\",[\"f32:2143289344\",\"f32:8388608\"]],[\"invoke\",763,0,\"ne\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",773,0,\"ne\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",783,0,\"ne\",[\"f32:4290772992\",\"f32:2139095039\"]],[\"invoke\",793,0,\"ne\",[\"f32:2143289344\",\"f32:4286578688\"]],[\"invoke\",809,0,\"ne\",[\"f32:2143289344\",\"f32:2143289344\"]],[\"invoke\",817,0,\"lt\",[\"f32:2147483648\",\"f32:2147483649\"]],[\"invoke\",822,0,\"lt\",[\"f32:2147483648\",\"f32:8388608\"]],[\"invoke\",826,0,\"lt\",[\"f32:2147483648\",\"f32:1056964608\"]],[\"invoke\",831,0,\"lt\",[\"f32:0\",\"f32:3212836864\"]],[\"invoke\",836,0,\"lt\",[\"f32:0\",\"f32:1086918619\"]],[\"invoke\",841,0,\"lt\",[\"f32:2147483648\",\"f32:4286578688\"]],[\"invoke\",847,0,\"lt\",[\"f32:2147483648\",\"f32:2143289344\"]],[\"invoke\",855,0,\"lt\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",860,0,\"lt\",[\"f32:1\",\"f32:1\"]],[\"invoke\",864,0,\"lt\",[\"f32:1\",\"f32:8388608\"]],[\"invoke\",869,0,\"lt\",[\"f32:2147483649\",\"f32:3212836864\"]],[\"invoke\",874,0,\"lt\",[\"f32:2147483649\",\"f32:1086918619\"]],[\"invoke\",879,0,\"lt\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",884,0,\"lt\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",893,0,\"lt\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",898,0,\"lt\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",903,0,\"lt\",[\"f32:8388608\",\"f32:2155872256\"]],[\"invoke\",907,0,\"lt\",[\"f32:8388608\",\"f32:3204448256\"]],[\"invoke\",912,0,\"lt\",[\"f32:8388608\",\"f32:1065353216\"]],[\"invoke\",917,0,\"lt\",[\"f32:2155872256\",\"f32:4286578687\"]],[\"invoke\",922,0,\"lt\",[\"f32:2155872256\",\"f32:2139095040\"]],[\"invoke\",929,0,\"lt\",[\"f32:8388608\",\"f32:4290772992\"]],[\"invoke\",936,0,\"lt\",[\"f32:1056964608\",\"f32:0\"]],[\"invoke\",941,0,\"lt\",[\"f32:3204448256\",\"f32:2155872256\"]],[\"invoke\",945,0,\"lt\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",950,0,\"lt\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",955,0,\"lt\",[\"f32:1056964608\",\"f32:3234402267\"]],[\"invoke\",960,0,\"lt\",[\"f32:1056964608\",\"f32:2139095039\"]],[\"invoke\",965,0,\"lt\",[\"f32:3204448256\",\"f32:4290772992\"]],[\"invoke\",974,0,\"lt\",[\"f32:3212836864\",\"f32:0\"]],[\"invoke\",979,0,\"lt\",[\"f32:1065353216\",\"f32:2147483649\"]],[\"invoke\",983,0,\"lt\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",988,0,\"lt\",[\"f32:1065353216\",\"f32:1056964608\"]],[\"invoke\",993,0,\"lt\",[\"f32:3212836864\",\"f32:3234402267\"]],[\"invoke\",998,0,\"lt\",[\"f32:3212836864\",\"f32:2139095039\"]],[\"invoke\",1003,0,\"lt\",[\"f32:1065353216\",\"f32:4286578688\"]],[\"invoke\",1011,0,\"lt\",[\"f32:1065353216\",\"f32:2143289344\"]],[\"invoke\",1017,0,\"lt\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",1021,0,\"lt\",[\"f32:3234402267\",\"f32:2155872256\"]],[\"invoke\",1026,0,\"lt\",[\"f32:3234402267\",\"f32:1056964608\"]],[\"invoke\",1031,0,\"lt\",[\"f32:1086918619\",\"f32:3212836864\"]],[\"invoke\",1036,0,\"lt\",[\"f32:1086918619\",\"f32:1086918619\"]],[\"invoke\",1041,0,\"lt\",[\"f32:3234402267\",\"f32:4286578688\"]],[\"invoke\",1047,0,\"lt\",[\"f32:3234402267\",\"f32:2143289344\"]],[\"invoke\",1055,0,\"lt\",[\"f32:2139095039\",\"f32:2147483648\"]],[\"invoke\",1059,0,\"lt\",[\"f32:2139095039\",\"f32:2147483649\"]],[\"invoke\",1064,0,\"lt\",[\"f32:2139095039\",\"f32:8388608\"]],[\"invoke\",1069,0,\"lt\",[\"f32:4286578687\",\"f32:3212836864\"]],[\"invoke\",1074,0,\"lt\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",1079,0,\"lt\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",1084,0,\"lt\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",1093,0,\"lt\",[\"f32:4286578688\",\"f32:2147483648\"]],[\"invoke\",1097,0,\"lt\",[\"f32:4286578688\",\"f32:2147483649\"]],[\"invoke\",1102,0,\"lt\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",1107,0,\"lt\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",1112,0,\"lt\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",1117,0,\"lt\",[\"f32:4286578688\",\"f32:4286578687\"]],[\"invoke\",1122,0,\"lt\",[\"f32:4286578688\",\"f32:2139095040\"]],[\"invoke\",1129,0,\"lt\",[\"f32:2139095040\",\"f32:4290772992\"]],[\"invoke\",1137,0,\"lt\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",1147,0,\"lt\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",1157,0,\"lt\",[\"f32:4290772992\",\"f32:3204448256\"]],[\"invoke\",1167,0,\"lt\",[\"f32:4290772992\",\"f32:1065353216\"]],[\"invoke\",1177,0,\"lt\",[\"f32:2143289344\",\"f32:3234402267\"]],[\"invoke\",1187,0,\"lt\",[\"f32:2143289344\",\"f32:2139095039\"]],[\"invoke\",1197,0,\"lt\",[\"f32:4290772992\",\"f32:4290772992\"]],[\"invoke\",1214,0,\"le\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",1218,0,\"le\",[\"f32:2147483648\",\"f32:1\"]],[\"invoke\",1223,0,\"le\",[\"f32:0\",\"f32:2155872256\"]],[\"invoke\",1228,0,\"le\",[\"f32:0\",\"f32:1056964608\"]],[\"invoke\",1233,0,\"le\",[\"f32:2147483648\",\"f32:3234402267\"]],[\"invoke\",1238,0,\"le\",[\"f32:2147483648\",\"f32:2139095039\"]],[\"invoke\",1243,0,\"le\",[\"f32:0\",\"f32:4286578688\"]],[\"invoke\",1251,0,\"le\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1256,0,\"le\",[\"f32:1\",\"f32:0\"]],[\"invoke\",1261,0,\"le\",[\"f32:2147483649\",\"f32:2155872256\"]],[\"invoke\",1266,0,\"le\",[\"f32:2147483649\",\"f32:1056964608\"]],[\"invoke\",1271,0,\"le\",[\"f32:1\",\"f32:3212836864\"]],[\"invoke\",1276,0,\"le\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",1281,0,\"le\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",1287,0,\"le\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",1294,0,\"le\",[\"f32:2155872256\",\"f32:0\"]],[\"invoke\",1299,0,\"le\",[\"f32:8388608\",\"f32:2147483649\"]],[\"invoke\",1304,0,\"le\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",1309,0,\"le\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",1314,0,\"le\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",1319,0,\"le\",[\"f32:8388608\",\"f32:4286578687\"]],[\"invoke\",1324,0,\"le\",[\"f32:8388608\",\"f32:2139095040\"]],[\"invoke\",1331,0,\"le\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",1337,0,\"le\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",1342,0,\"le\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",1347,0,\"le\",[\"f32:1056964608\",\"f32:3204448256\"]],[\"invoke\",1352,0,\"le\",[\"f32:1056964608\",\"f32:1065353216\"]],[\"invoke\",1357,0,\"le\",[\"f32:3204448256\",\"f32:4286578687\"]],[\"invoke\",1362,0,\"le\",[\"f32:3204448256\",\"f32:2139095040\"]],[\"invoke\",1367,0,\"le\",[\"f32:3204448256\",\"f32:2143289344\"]],[\"invoke\",1375,0,\"le\",[\"f32:1065353216\",\"f32:2147483648\"]],[\"invoke\",1380,0,\"le\",[\"f32:1065353216\",\"f32:1\"]],[\"invoke\",1385,0,\"le\",[\"f32:3212836864\",\"f32:3204448256\"]],[\"invoke\",1390,0,\"le\",[\"f32:3212836864\",\"f32:1065353216\"]],[\"invoke\",1395,0,\"le\",[\"f32:1065353216\",\"f32:3234402267\"]],[\"invoke\",1400,0,\"le\",[\"f32:1065353216\",\"f32:2139095039\"]],[\"invoke\",1404,0,\"le\",[\"f32:1065353216\",\"f32:2139095040\"]],[\"invoke\",1413,0,\"le\",[\"f32:3234402267\",\"f32:2147483648\"]],[\"invoke\",1418,0,\"le\",[\"f32:3234402267\",\"f32:1\"]],[\"invoke\",1423,0,\"le\",[\"f32:1086918619\",\"f32:2155872256\"]],[\"invoke\",1428,0,\"le\",[\"f32:1086918619\",\"f32:1056964608\"]],[\"invoke\",1433,0,\"le\",[\"f32:3234402267\",\"f32:3234402267\"]],[\"invoke\",1438,0,\"le\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",1442,0,\"le\",[\"f32:3234402267\",\"f32:2139095040\"]],[\"invoke\",1449,0,\"le\",[\"f32:1086918619\",\"f32:4290772992\"]],[\"invoke\",1456,0,\"le\",[\"f32:2139095039\",\"f32:0\"]],[\"invoke\",1461,0,\"le\",[\"f32:4286578687\",\"f32:2155872256\"]],[\"invoke\",1466,0,\"le\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",1471,0,\"le\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",1476,0,\"le\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",1480,0,\"le\",[\"f32:2139095039\",\"f32:2139095039\"]],[\"invoke\",1485,0,\"le\",[\"f32:4286578687\",\"f32:4290772992\"]],[\"invoke\",1494,0,\"le\",[\"f32:4286578688\",\"f32:0\"]],[\"invoke\",1499,0,\"le\",[\"f32:2139095040\",\"f32:2147483649\"]],[\"invoke\",1504,0,\"le\",[\"f32:2139095040\",\"f32:8388608\"]],[\"invoke\",1509,0,\"le\",[\"f32:4286578688\",\"f32:3212836864\"]],[\"invoke\",1514,0,\"le\",[\"f32:4286578688\",\"f32:1086918619\"]],[\"invoke\",1519,0,\"le\",[\"f32:2139095040\",\"f32:4286578687\"]],[\"invoke\",1523,0,\"le\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",1531,0,\"le\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",1541,0,\"le\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",1551,0,\"le\",[\"f32:4290772992\",\"f32:8388608\"]],[\"invoke\",1561,0,\"le\",[\"f32:2143289344\",\"f32:3204448256\"]],[\"invoke\",1571,0,\"le\",[\"f32:2143289344\",\"f32:1065353216\"]],[\"invoke\",1581,0,\"le\",[\"f32:4290772992\",\"f32:4286578687\"]],[\"invoke\",1589,0,\"le\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",1601,0,\"le\",[\"f32:4290772992\",\"f32:2143289344\"]],[\"invoke\",1615,0,\"gt\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1620,0,\"gt\",[\"f32:0\",\"f32:1\"]],[\"invoke\",1625,0,\"gt\",[\"f32:2147483648\",\"f32:3204448256\"]],[\"invoke\",1630,0,\"gt\",[\"f32:2147483648\",\"f32:1065353216\"]],[\"invoke\",1635,0,\"gt\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",1639,0,\"gt\",[\"f32:0\",\"f32:4286578687\"]],[\"invoke\",1644,0,\"gt\",[\"f32:0\",\"f32:2139095040\"]],[\"invoke\",1653,0,\"gt\",[\"f32:2147483649\",\"f32:2147483648\"]],[\"invoke\",1658,0,\"gt\",[\"f32:2147483649\",\"f32:1\"]],[\"invoke\",1663,0,\"gt\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",1668,0,\"gt\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",1673,0,\"gt\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",1677,0,\"gt\",[\"f32:2147483649\",\"f32:4286578687\"]],[\"invoke\",1682,0,\"gt\",[\"f32:2147483649\",\"f32:2139095040\"]],[\"invoke\",1689,0,\"gt\",[\"f32:1\",\"f32:4290772992\"]],[\"invoke\",1696,0,\"gt\",[\"f32:8388608\",\"f32:0\"]],[\"invoke\",1701,0,\"gt\",[\"f32:2155872256\",\"f32:2155872256\"]],[\"invoke\",1706,0,\"gt\",[\"f32:2155872256\",\"f32:1056964608\"]],[\"invoke\",1711,0,\"gt\",[\"f32:8388608\",\"f32:3212836864\"]],[\"invoke\",1715,0,\"gt\",[\"f32:8388608\",\"f32:3234402267\"]],[\"invoke\",1720,0,\"gt\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",1725,0,\"gt\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",1734,0,\"gt\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",1739,0,\"gt\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",1744,0,\"gt\",[\"f32:1056964608\",\"f32:8388608\"]],[\"invoke\",1749,0,\"gt\",[\"f32:3204448256\",\"f32:3212836864\"]],[\"invoke\",1753,0,\"gt\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",1758,0,\"gt\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",1763,0,\"gt\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",1771,0,\"gt\",[\"f32:1056964608\",\"f32:2143289344\"]],[\"invoke\",1777,0,\"gt\",[\"f32:3212836864\",\"f32:2147483649\"]],[\"invoke\",1782,0,\"gt\",[\"f32:3212836864\",\"f32:8388608\"]],[\"invoke\",1787,0,\"gt\",[\"f32:1065353216\",\"f32:3204448256\"]],[\"invoke\",1791,0,\"gt\",[\"f32:1065353216\",\"f32:3212836864\"]],[\"invoke\",1796,0,\"gt\",[\"f32:1065353216\",\"f32:1086918619\"]],[\"invoke\",1801,0,\"gt\",[\"f32:3212836864\",\"f32:4286578688\"]],[\"invoke\",1807,0,\"gt\",[\"f32:3212836864\",\"f32:2143289344\"]],[\"invoke\",1815,0,\"gt\",[\"f32:1086918619\",\"f32:2147483648\"]],[\"invoke\",1820,0,\"gt\",[\"f32:1086918619\",\"f32:1\"]],[\"invoke\",1825,0,\"gt\",[\"f32:3234402267\",\"f32:3204448256\"]],[\"invoke\",1830,0,\"gt\",[\"f32:3234402267\",\"f32:1065353216\"]],[\"invoke\",1834,0,\"gt\",[\"f32:3234402267\",\"f32:1086918619\"]],[\"invoke\",1839,0,\"gt\",[\"f32:1086918619\",\"f32:4286578687\"]],[\"invoke\",1844,0,\"gt\",[\"f32:1086918619\",\"f32:2139095040\"]],[\"invoke\",1853,0,\"gt\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",1858,0,\"gt\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",1863,0,\"gt\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",1868,0,\"gt\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",1872,0,\"gt\",[\"f32:2139095039\",\"f32:1065353216\"]],[\"invoke\",1877,0,\"gt\",[\"f32:4286578687\",\"f32:4286578687\"]],[\"invoke\",1882,0,\"gt\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",1889,0,\"gt\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",1896,0,\"gt\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",1901,0,\"gt\",[\"f32:4286578688\",\"f32:2155872256\"]],[\"invoke\",1906,0,\"gt\",[\"f32:4286578688\",\"f32:1056964608\"]],[\"invoke\",1910,0,\"gt\",[\"f32:4286578688\",\"f32:1065353216\"]],[\"invoke\",1915,0,\"gt\",[\"f32:2139095040\",\"f32:3234402267\"]],[\"invoke\",1920,0,\"gt\",[\"f32:2139095040\",\"f32:2139095039\"]],[\"invoke\",1925,0,\"gt\",[\"f32:4286578688\",\"f32:4290772992\"]],[\"invoke\",1935,0,\"gt\",[\"f32:4290772992\",\"f32:0\"]],[\"invoke\",1945,0,\"gt\",[\"f32:2143289344\",\"f32:2147483649\"]],[\"invoke\",1955,0,\"gt\",[\"f32:2143289344\",\"f32:8388608\"]],[\"invoke\",1963,0,\"gt\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",1973,0,\"gt\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",1983,0,\"gt\",[\"f32:4290772992\",\"f32:2139095039\"]],[\"invoke\",1993,0,\"gt\",[\"f32:2143289344\",\"f32:4286578688\"]],[\"invoke\",2009,0,\"gt\",[\"f32:2143289344\",\"f32:2143289344\"]],[\"invoke\",2017,0,\"ge\",[\"f32:2147483648\",\"f32:2147483649\"]],[\"invoke\",2022,0,\"ge\",[\"f32:2147483648\",\"f32:8388608\"]],[\"invoke\",2026,0,\"ge\",[\"f32:2147483648\",\"f32:1056964608\"]],[\"invoke\",2031,0,\"ge\",[\"f32:0\",\"f32:3212836864\"]],[\"invoke\",2036,0,\"ge\",[\"f32:0\",\"f32:1086918619\"]],[\"invoke\",2041,0,\"ge\",[\"f32:2147483648\",\"f32:4286578688\"]],[\"invoke\",2047,0,\"ge\",[\"f32:2147483648\",\"f32:2143289344\"]],[\"invoke\",2055,0,\"ge\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",2060,0,\"ge\",[\"f32:1\",\"f32:1\"]],[\"invoke\",2064,0,\"ge\",[\"f32:1\",\"f32:8388608\"]],[\"invoke\",2069,0,\"ge\",[\"f32:2147483649\",\"f32:3212836864\"]],[\"invoke\",2074,0,\"ge\",[\"f32:2147483649\",\"f32:1086918619\"]],[\"invoke\",2079,0,\"ge\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",2084,0,\"ge\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",2093,0,\"ge\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",2098,0,\"ge\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",2102,0,\"ge\",[\"f32:2155872256\",\"f32:8388608\"]],[\"invoke\",2107,0,\"ge\",[\"f32:8388608\",\"f32:3204448256\"]],[\"invoke\",2112,0,\"ge\",[\"f32:8388608\",\"f32:1065353216\"]],[\"invoke\",2117,0,\"ge\",[\"f32:2155872256\",\"f32:4286578687\"]],[\"invoke\",2122,0,\"ge\",[\"f32:2155872256\",\"f32:2139095040\"]],[\"invoke\",2129,0,\"ge\",[\"f32:8388608\",\"f32:4290772992\"]],[\"invoke\",2136,0,\"ge\",[\"f32:1056964608\",\"f32:0\"]],[\"invoke\",2141,0,\"ge\",[\"f32:3204448256\",\"f32:2155872256\"]],[\"invoke\",2145,0,\"ge\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",2150,0,\"ge\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",2155,0,\"ge\",[\"f32:1056964608\",\"f32:3234402267\"]],[\"invoke\",2160,0,\"ge\",[\"f32:1056964608\",\"f32:2139095039\"]],[\"invoke\",2165,0,\"ge\",[\"f32:3204448256\",\"f32:4290772992\"]],[\"invoke\",2174,0,\"ge\",[\"f32:3212836864\",\"f32:0\"]],[\"invoke\",2179,0,\"ge\",[\"f32:1065353216\",\"f32:2147483649\"]],[\"invoke\",2183,0,\"ge\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",2188,0,\"ge\",[\"f32:1065353216\",\"f32:1056964608\"]],[\"invoke\",2193,0,\"ge\",[\"f32:3212836864\",\"f32:3234402267\"]],[\"invoke\",2198,0,\"ge\",[\"f32:3212836864\",\"f32:2139095039\"]],[\"invoke\",2203,0,\"ge\",[\"f32:1065353216\",\"f32:4286578688\"]],[\"invoke\",2211,0,\"ge\",[\"f32:1065353216\",\"f32:2143289344\"]],[\"invoke\",2217,0,\"ge\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",2221,0,\"ge\",[\"f32:3234402267\",\"f32:2155872256\"]],[\"invoke\",2226,0,\"ge\",[\"f32:3234402267\",\"f32:1056964608\"]],[\"invoke\",2231,0,\"ge\",[\"f32:1086918619\",\"f32:3212836864\"]],[\"invoke\",2236,0,\"ge\",[\"f32:1086918619\",\"f32:1086918619\"]],[\"invoke\",2241,0,\"ge\",[\"f32:3234402267\",\"f32:4286578688\"]],[\"invoke\",2247,0,\"ge\",[\"f32:3234402267\",\"f32:2143289344\"]],[\"invoke\",2255,0,\"ge\",[\"f32:2139095039\",\"f32:2147483648\"]],[\"invoke\",2259,0,\"ge\",[\"f32:2139095039\",\"f32:2147483649\"]],[\"invoke\",2264,0,\"ge\",[\"f32:2139095039\",\"f32:8388608\"]],[\"invoke\",2269,0,\"ge\",[\"f32:4286578687\",\"f32:3212836864\"]],[\"invoke\",2274,0,\"ge\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",2279,0,\"ge\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",2284,0,\"ge\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",2293,0,\"ge\",[\"f32:4286578688\",\"f32:2147483648\"]],[\"invoke\",2297,0,\"ge\",[\"f32:4286578688\",\"f32:2147483649\"]],[\"invoke\",2302,0,\"ge\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",2307,0,\"ge\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",2312,0,\"ge\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",2317,0,\"ge\",[\"f32:4286578688\",\"f32:4286578687\"]],[\"invoke\",2322,0,\"ge\",[\"f32:4286578688\",\"f32:2139095040\"]],[\"invoke\",2329,0,\"ge\",[\"f32:2139095040\",\"f32:4290772992\"]],[\"invoke\",2337,0,\"ge\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",2347,0,\"ge\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",2357,0,\"ge\",[\"f32:4290772992\",\"f32:3204448256\"]],[\"invoke\",2367,0,\"ge\",[\"f32:4290772992\",\"f32:1065353216\"]],[\"invoke\",2377,0,\"ge\",[\"f32:2143289344\",\"f32:3234402267\"]],[\"invoke\",2387,0,\"ge\",[\"f32:2143289344\",\"f32:2139095039\"]],[\"invoke\",2397,0,\"ge\",[\"f32:4290772992\",\"f32:4290772992\"]],[\"file\",\"f64_cmp.wast\"],[\"module\",4,3],[\"invoke\",13,0,\"eq\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",18,0,\"eq\",[\"f64:9223372036854775808\",\"f64:1\"]],[\"invoke\",23,0,\"eq\",[\"f64:0\",\"f64:9227875636482146304\"]],[\"invoke\",28,0,\"eq\",[\"f64:0\",\"f64:4602678819172646912\"]],[\"invoke\",33,0,\"eq\",[\"f64:9223372036854775808\",\"f64:13842132293034192152\"]],[\"invoke\",38,0,\"eq\",[\"f64:9223372036854775808\",\"f64:9218868437227405311\"]],[\"invoke\",43,0,\"eq\",[\"f64:0\",\"f64:18442240474082181120\"]],[\"invoke\",51,0,\"eq\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",56,0,\"eq\",[\"f64:1\",\"f64:0\"]],[\"invoke\",61,0,\"eq\",[\"f64:9223372036854775809\",\"f64:9227875636482146304\"]],[\"invoke\",66,0,\"eq\",[\"f64:9223372036854775809\",\"f64:4602678819172646912\"]],[\"invoke\",71,0,\"eq\",[\"f64:1\",\"f64:13830554455654793216\"]],[\"invoke\",76,0,\"eq\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",81,0,\"eq\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",87,0,\"eq\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",94,0,\"eq\",[\"f64:9227875636482146304\",\"f64:0\"]],[\"invoke\",99,0,\"eq\",[\"f64:4503599627370496\",\"f64:9223372036854775809\"]],[\"invoke\",104,0,\"eq\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",109,0,\"eq\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",114,0,\"eq\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",119,0,\"eq\",[\"f64:4503599627370496\",\"f64:18442240474082181119\"]],[\"invoke\",124,0,\"eq\",[\"f64:4503599627370496\",\"f64:9218868437227405312\"]],[\"invoke\",131,0,\"eq\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",137,0,\"eq\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",142,0,\"eq\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",147,0,\"eq\",[\"f64:4602678819172646912\",\"f64:13826050856027422720\"]],[\"invoke\",152,0,\"eq\",[\"f64:4602678819172646912\",\"f64:4607182418800017408\"]],[\"invoke\",157,0,\"eq\",[\"f64:13826050856027422720\",\"f64:18442240474082181119\"]],[\"invoke\",162,0,\"eq\",[\"f64:13826050856027422720\",\"f64:9218868437227405312\"]],[\"invoke\",167,0,\"eq\",[\"f64:13826050856027422720\",\"f64:9221120237041090560\"]],[\"invoke\",175,0,\"eq\",[\"f64:4607182418800017408\",\"f64:9223372036854775808\"]],[\"invoke\",180,0,\"eq\",[\"f64:4607182418800017408\",\"f64:1\"]],[\"invoke\",185,0,\"eq\",[\"f64:13830554455654793216\",\"f64:13826050856027422720\"]],[\"invoke\",190,0,\"eq\",[\"f64:13830554455654793216\",\"f64:4607182418800017408\"]],[\"invoke\",195,0,\"eq\",[\"f64:4607182418800017408\",\"f64:13842132293034192152\"]],[\"invoke\",200,0,\"eq\",[\"f64:4607182418800017408\",\"f64:9218868437227405311\"]],[\"invoke\",204,0,\"eq\",[\"f64:4607182418800017408\",\"f64:9218868437227405312\"]],[\"invoke\",213,0,\"eq\",[\"f64:13842132293034192152\",\"f64:9223372036854775808\"]],[\"invoke\",218,0,\"eq\",[\"f64:13842132293034192152\",\"f64:1\"]],[\"invoke\",223,0,\"eq\",[\"f64:4618760256179416344\",\"f64:9227875636482146304\"]],[\"invoke\",228,0,\"eq\",[\"f64:4618760256179416344\",\"f64:4602678819172646912\"]],[\"invoke\",233,0,\"eq\",[\"f64:13842132293034192152\",\"f64:13842132293034192152\"]],[\"invoke\",238,0,\"eq\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",242,0,\"eq\",[\"f64:13842132293034192152\",\"f64:9218868437227405312\"]],[\"invoke\",249,0,\"eq\",[\"f64:4618760256179416344\",\"f64:18444492273895866368\"]],[\"invoke\",256,0,\"eq\",[\"f64:9218868437227405311\",\"f64:0\"]],[\"invoke\",261,0,\"eq\",[\"f64:18442240474082181119\",\"f64:9227875636482146304\"]],[\"invoke\",266,0,\"eq\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",271,0,\"eq\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",276,0,\"eq\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",281,0,\"eq\",[\"f64:18442240474082181119\",\"f64:18442240474082181120\"]],[\"invoke\",285,0,\"eq\",[\"f64:18442240474082181119\",\"f64:18444492273895866368\"]],[\"invoke\",294,0,\"eq\",[\"f64:18442240474082181120\",\"f64:0\"]],[\"invoke\",299,0,\"eq\",[\"f64:9218868437227405312\",\"f64:9223372036854775809\"]],[\"invoke\",304,0,\"eq\",[\"f64:9218868437227405312\",\"f64:4503599627370496\"]],[\"invoke\",309,0,\"eq\",[\"f64:18442240474082181120\",\"f64:13830554455654793216\"]],[\"invoke\",314,0,\"eq\",[\"f64:18442240474082181120\",\"f64:4618760256179416344\"]],[\"invoke\",319,0,\"eq\",[\"f64:9218868437227405312\",\"f64:18442240474082181119\"]],[\"invoke\",323,0,\"eq\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",331,0,\"eq\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",341,0,\"eq\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",351,0,\"eq\",[\"f64:18444492273895866368\",\"f64:4503599627370496\"]],[\"invoke\",361,0,\"eq\",[\"f64:9221120237041090560\",\"f64:13826050856027422720\"]],[\"invoke\",371,0,\"eq\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\"]],[\"invoke\",381,0,\"eq\",[\"f64:18444492273895866368\",\"f64:18442240474082181119\"]],[\"invoke\",389,0,\"eq\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",401,0,\"eq\",[\"f64:18444492273895866368\",\"f64:9221120237041090560\"]],[\"invoke\",415,0,\"ne\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",420,0,\"ne\",[\"f64:0\",\"f64:1\"]],[\"invoke\",425,0,\"ne\",[\"f64:9223372036854775808\",\"f64:13826050856027422720\"]],[\"invoke\",430,0,\"ne\",[\"f64:9223372036854775808\",\"f64:4607182418800017408\"]],[\"invoke\",435,0,\"ne\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",439,0,\"ne\",[\"f64:0\",\"f64:18442240474082181119\"]],[\"invoke\",444,0,\"ne\",[\"f64:0\",\"f64:9218868437227405312\"]],[\"invoke\",453,0,\"ne\",[\"f64:9223372036854775809\",\"f64:9223372036854775808\"]],[\"invoke\",458,0,\"ne\",[\"f64:9223372036854775809\",\"f64:1\"]],[\"invoke\",463,0,\"ne\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",468,0,\"ne\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",473,0,\"ne\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",477,0,\"ne\",[\"f64:9223372036854775809\",\"f64:18442240474082181119\"]],[\"invoke\",482,0,\"ne\",[\"f64:9223372036854775809\",\"f64:9218868437227405312\"]],[\"invoke\",489,0,\"ne\",[\"f64:1\",\"f64:18444492273895866368\"]],[\"invoke\",496,0,\"ne\",[\"f64:4503599627370496\",\"f64:0\"]],[\"invoke\",501,0,\"ne\",[\"f64:9227875636482146304\",\"f64:9227875636482146304\"]],[\"invoke\",506,0,\"ne\",[\"f64:9227875636482146304\",\"f64:4602678819172646912\"]],[\"invoke\",511,0,\"ne\",[\"f64:4503599627370496\",\"f64:13830554455654793216\"]],[\"invoke\",515,0,\"ne\",[\"f64:4503599627370496\",\"f64:13842132293034192152\"]],[\"invoke\",520,0,\"ne\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",525,0,\"ne\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",534,0,\"ne\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",539,0,\"ne\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",544,0,\"ne\",[\"f64:4602678819172646912\",\"f64:4503599627370496\"]],[\"invoke\",549,0,\"ne\",[\"f64:13826050856027422720\",\"f64:13830554455654793216\"]],[\"invoke\",553,0,\"ne\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",558,0,\"ne\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",563,0,\"ne\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",571,0,\"ne\",[\"f64:4602678819172646912\",\"f64:9221120237041090560\"]],[\"invoke\",577,0,\"ne\",[\"f64:13830554455654793216\",\"f64:9223372036854775809\"]],[\"invoke\",582,0,\"ne\",[\"f64:13830554455654793216\",\"f64:4503599627370496\"]],[\"invoke\",587,0,\"ne\",[\"f64:4607182418800017408\",\"f64:13826050856027422720\"]],[\"invoke\",592,0,\"ne\",[\"f64:4607182418800017408\",\"f64:4607182418800017408\"]],[\"invoke\",596,0,\"ne\",[\"f64:4607182418800017408\",\"f64:4618760256179416344\"]],[\"invoke\",601,0,\"ne\",[\"f64:13830554455654793216\",\"f64:18442240474082181120\"]],[\"invoke\",607,0,\"ne\",[\"f64:13830554455654793216\",\"f64:9221120237041090560\"]],[\"invoke\",615,0,\"ne\",[\"f64:4618760256179416344\",\"f64:9223372036854775808\"]],[\"invoke\",620,0,\"ne\",[\"f64:4618760256179416344\",\"f64:1\"]],[\"invoke\",625,0,\"ne\",[\"f64:13842132293034192152\",\"f64:13826050856027422720\"]],[\"invoke\",630,0,\"ne\",[\"f64:13842132293034192152\",\"f64:4607182418800017408\"]],[\"invoke\",634,0,\"ne\",[\"f64:13842132293034192152\",\"f64:4618760256179416344\"]],[\"invoke\",639,0,\"ne\",[\"f64:4618760256179416344\",\"f64:18442240474082181119\"]],[\"invoke\",644,0,\"ne\",[\"f64:4618760256179416344\",\"f64:9218868437227405312\"]],[\"invoke\",653,0,\"ne\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",658,0,\"ne\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",663,0,\"ne\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",668,0,\"ne\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",672,0,\"ne\",[\"f64:9218868437227405311\",\"f64:4607182418800017408\"]],[\"invoke\",677,0,\"ne\",[\"f64:18442240474082181119\",\"f64:18442240474082181119\"]],[\"invoke\",682,0,\"ne\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",689,0,\"ne\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",696,0,\"ne\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",701,0,\"ne\",[\"f64:18442240474082181120\",\"f64:9227875636482146304\"]],[\"invoke\",706,0,\"ne\",[\"f64:18442240474082181120\",\"f64:4602678819172646912\"]],[\"invoke\",710,0,\"ne\",[\"f64:18442240474082181120\",\"f64:4607182418800017408\"]],[\"invoke\",715,0,\"ne\",[\"f64:9218868437227405312\",\"f64:13842132293034192152\"]],[\"invoke\",720,0,\"ne\",[\"f64:9218868437227405312\",\"f64:9218868437227405311\"]],[\"invoke\",725,0,\"ne\",[\"f64:18442240474082181120\",\"f64:18444492273895866368\"]],[\"invoke\",735,0,\"ne\",[\"f64:18444492273895866368\",\"f64:0\"]],[\"invoke\",745,0,\"ne\",[\"f64:9221120237041090560\",\"f64:9223372036854775809\"]],[\"invoke\",755,0,\"ne\",[\"f64:9221120237041090560\",\"f64:4503599627370496\"]],[\"invoke\",763,0,\"ne\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",773,0,\"ne\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",783,0,\"ne\",[\"f64:18444492273895866368\",\"f64:9218868437227405311\"]],[\"invoke\",793,0,\"ne\",[\"f64:9221120237041090560\",\"f64:18442240474082181120\"]],[\"invoke\",809,0,\"ne\",[\"f64:9221120237041090560\",\"f64:9221120237041090560\"]],[\"invoke\",817,0,\"lt\",[\"f64:9223372036854775808\",\"f64:9223372036854775809\"]],[\"invoke\",822,0,\"lt\",[\"f64:9223372036854775808\",\"f64:4503599627370496\"]],[\"invoke\",826,0,\"lt\",[\"f64:9223372036854775808\",\"f64:4602678819172646912\"]],[\"invoke\",831,0,\"lt\",[\"f64:0\",\"f64:13830554455654793216\"]],[\"invoke\",836,0,\"lt\",[\"f64:0\",\"f64:4618760256179416344\"]],[\"invoke\",841,0,\"lt\",[\"f64:9223372036854775808\",\"f64:18442240474082181120\"]],[\"invoke\",847,0,\"lt\",[\"f64:9223372036854775808\",\"f64:9221120237041090560\"]],[\"invoke\",855,0,\"lt\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",860,0,\"lt\",[\"f64:1\",\"f64:1\"]],[\"invoke\",864,0,\"lt\",[\"f64:1\",\"f64:4503599627370496\"]],[\"invoke\",869,0,\"lt\",[\"f64:9223372036854775809\",\"f64:13830554455654793216\"]],[\"invoke\",874,0,\"lt\",[\"f64:9223372036854775809\",\"f64:4618760256179416344\"]],[\"invoke\",879,0,\"lt\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",884,0,\"lt\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",893,0,\"lt\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",898,0,\"lt\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",903,0,\"lt\",[\"f64:4503599627370496\",\"f64:9227875636482146304\"]],[\"invoke\",907,0,\"lt\",[\"f64:4503599627370496\",\"f64:13826050856027422720\"]],[\"invoke\",912,0,\"lt\",[\"f64:4503599627370496\",\"f64:4607182418800017408\"]],[\"invoke\",917,0,\"lt\",[\"f64:9227875636482146304\",\"f64:18442240474082181119\"]],[\"invoke\",922,0,\"lt\",[\"f64:9227875636482146304\",\"f64:9218868437227405312\"]],[\"invoke\",929,0,\"lt\",[\"f64:4503599627370496\",\"f64:18444492273895866368\"]],[\"invoke\",936,0,\"lt\",[\"f64:4602678819172646912\",\"f64:0\"]],[\"invoke\",941,0,\"lt\",[\"f64:13826050856027422720\",\"f64:9227875636482146304\"]],[\"invoke\",945,0,\"lt\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",950,0,\"lt\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",955,0,\"lt\",[\"f64:4602678819172646912\",\"f64:13842132293034192152\"]],[\"invoke\",960,0,\"lt\",[\"f64:4602678819172646912\",\"f64:9218868437227405311\"]],[\"invoke\",965,0,\"lt\",[\"f64:13826050856027422720\",\"f64:18444492273895866368\"]],[\"invoke\",974,0,\"lt\",[\"f64:13830554455654793216\",\"f64:0\"]],[\"invoke\",979,0,\"lt\",[\"f64:4607182418800017408\",\"f64:9223372036854775809\"]],[\"invoke\",983,0,\"lt\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",988,0,\"lt\",[\"f64:4607182418800017408\",\"f64:4602678819172646912\"]],[\"invoke\",993,0,\"lt\",[\"f64:13830554455654793216\",\"f64:13842132293034192152\"]],[\"invoke\",998,0,\"lt\",[\"f64:13830554455654793216\",\"f64:9218868437227405311\"]],[\"invoke\",1003,0,\"lt\",[\"f64:4607182418800017408\",\"f64:18442240474082181120\"]],[\"invoke\",1011,0,\"lt\",[\"f64:4607182418800017408\",\"f64:9221120237041090560\"]],[\"invoke\",1017,0,\"lt\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",1021,0,\"lt\",[\"f64:13842132293034192152\",\"f64:9227875636482146304\"]],[\"invoke\",1026,0,\"lt\",[\"f64:13842132293034192152\",\"f64:4602678819172646912\"]],[\"invoke\",1031,0,\"lt\",[\"f64:4618760256179416344\",\"f64:13830554455654793216\"]],[\"invoke\",1036,0,\"lt\",[\"f64:4618760256179416344\",\"f64:4618760256179416344\"]],[\"invoke\",1041,0,\"lt\",[\"f64:13842132293034192152\",\"f64:18442240474082181120\"]],[\"invoke\",1047,0,\"lt\",[\"f64:13842132293034192152\",\"f64:9221120237041090560\"]],[\"invoke\",1055,0,\"lt\",[\"f64:9218868437227405311\",\"f64:9223372036854775808\"]],[\"invoke\",1059,0,\"lt\",[\"f64:9218868437227405311\",\"f64:9223372036854775809\"]],[\"invoke\",1064,0,\"lt\",[\"f64:9218868437227405311\",\"f64:4503599627370496\"]],[\"invoke\",1069,0,\"lt\",[\"f64:18442240474082181119\",\"f64:13830554455654793216\"]],[\"invoke\",1074,0,\"lt\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",1079,0,\"lt\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",1084,0,\"lt\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",1093,0,\"lt\",[\"f64:18442240474082181120\",\"f64:9223372036854775808\"]],[\"invoke\",1097,0,\"lt\",[\"f64:18442240474082181120\",\"f64:9223372036854775809\"]],[\"invoke\",1102,0,\"lt\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",1107,0,\"lt\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",1112,0,\"lt\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",1117,0,\"lt\",[\"f64:18442240474082181120\",\"f64:18442240474082181119\"]],[\"invoke\",1122,0,\"lt\",[\"f64:18442240474082181120\",\"f64:9218868437227405312\"]],[\"invoke\",1129,0,\"lt\",[\"f64:9218868437227405312\",\"f64:18444492273895866368\"]],[\"invoke\",1137,0,\"lt\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",1147,0,\"lt\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",1157,0,\"lt\",[\"f64:18444492273895866368\",\"f64:13826050856027422720\"]],[\"invoke\",1167,0,\"lt\",[\"f64:18444492273895866368\",\"f64:4607182418800017408\"]],[\"invoke\",1177,0,\"lt\",[\"f64:9221120237041090560\",\"f64:13842132293034192152\"]],[\"invoke\",1187,0,\"lt\",[\"f64:9221120237041090560\",\"f64:9218868437227405311\"]],[\"invoke\",1197,0,\"lt\",[\"f64:18444492273895866368\",\"f64:18444492273895866368\"]],[\"invoke\",1214,0,\"le\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1218,0,\"le\",[\"f64:9223372036854775808\",\"f64:1\"]],[\"invoke\",1223,0,\"le\",[\"f64:0\",\"f64:9227875636482146304\"]],[\"invoke\",1228,0,\"le\",[\"f64:0\",\"f64:4602678819172646912\"]],[\"invoke\",1233,0,\"le\",[\"f64:9223372036854775808\",\"f64:13842132293034192152\"]],[\"invoke\",1238,0,\"le\",[\"f64:9223372036854775808\",\"f64:9218868437227405311\"]],[\"invoke\",1243,0,\"le\",[\"f64:0\",\"f64:18442240474082181120\"]],[\"invoke\",1251,0,\"le\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",1256,0,\"le\",[\"f64:1\",\"f64:0\"]],[\"invoke\",1261,0,\"le\",[\"f64:9223372036854775809\",\"f64:9227875636482146304\"]],[\"invoke\",1266,0,\"le\",[\"f64:9223372036854775809\",\"f64:4602678819172646912\"]],[\"invoke\",1271,0,\"le\",[\"f64:1\",\"f64:13830554455654793216\"]],[\"invoke\",1276,0,\"le\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",1281,0,\"le\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",1287,0,\"le\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",1294,0,\"le\",[\"f64:9227875636482146304\",\"f64:0\"]],[\"invoke\",1299,0,\"le\",[\"f64:4503599627370496\",\"f64:9223372036854775809\"]],[\"invoke\",1304,0,\"le\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",1309,0,\"le\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",1314,0,\"le\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",1319,0,\"le\",[\"f64:4503599627370496\",\"f64:18442240474082181119\"]],[\"invoke\",1324,0,\"le\",[\"f64:4503599627370496\",\"f64:9218868437227405312\"]],[\"invoke\",1331,0,\"le\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",1337,0,\"le\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",1342,0,\"le\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",1347,0,\"le\",[\"f64:4602678819172646912\",\"f64:13826050856027422720\"]],[\"invoke\",1352,0,\"le\",[\"f64:4602678819172646912\",\"f64:4607182418800017408\"]],[\"invoke\",1357,0,\"le\",[\"f64:13826050856027422720\",\"f64:18442240474082181119\"]],[\"invoke\",1362,0,\"le\",[\"f64:13826050856027422720\",\"f64:9218868437227405312\"]],[\"invoke\",1367,0,\"le\",[\"f64:13826050856027422720\",\"f64:9221120237041090560\"]],[\"invoke\",1375,0,\"le\",[\"f64:4607182418800017408\",\"f64:9223372036854775808\"]],[\"invoke\",1380,0,\"le\",[\"f64:4607182418800017408\",\"f64:1\"]],[\"invoke\",1385,0,\"le\",[\"f64:13830554455654793216\",\"f64:13826050856027422720\"]],[\"invoke\",1390,0,\"le\",[\"f64:13830554455654793216\",\"f64:4607182418800017408\"]],[\"invoke\",1395,0,\"le\",[\"f64:4607182418800017408\",\"f64:13842132293034192152\"]],[\"invoke\",1400,0,\"le\",[\"f64:4607182418800017408\",\"f64:9218868437227405311\"]],[\"invoke\",1404,0,\"le\",[\"f64:4607182418800017408\",\"f64:9218868437227405312\"]],[\"invoke\",1413,0,\"le\",[\"f64:13842132293034192152\",\"f64:9223372036854775808\"]],[\"invoke\",1418,0,\"le\",[\"f64:13842132293034192152\",\"f64:1\"]],[\"invoke\",1423,0,\"le\",[\"f64:4618760256179416344\",\"f64:9227875636482146304\"]],[\"invoke\",1428,0,\"le\",[\"f64:4618760256179416344\",\"f64:4602678819172646912\"]],[\"invoke\",1433,0,\"le\",[\"f64:13842132293034192152\",\"f64:13842132293034192152\"]],[\"invoke\",1438,0,\"le\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",1442,0,\"le\",[\"f64:13842132293034192152\",\"f64:9218868437227405312\"]],[\"invoke\",1449,0,\"le\",[\"f64:4618760256179416344\",\"f64:18444492273895866368\"]],[\"invoke\",1456,0,\"le\",[\"f64:9218868437227405311\",\"f64:0\"]],[\"invoke\",1461,0,\"le\",[\"f64:18442240474082181119\",\"f64:9227875636482146304\"]],[\"invoke\",1466,0,\"le\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",1471,0,\"le\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",1476,0,\"le\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",1480,0,\"le\",[\"f64:9218868437227405311\",\"f64:9218868437227405311\"]],[\"invoke\",1485,0,\"le\",[\"f64:18442240474082181119\",\"f64:18444492273895866368\"]],[\"invoke\",1494,0,\"le\",[\"f64:18442240474082181120\",\"f64:0\"]],[\"invoke\",1499,0,\"le\",[\"f64:9218868437227405312\",\"f64:9223372036854775809\"]],[\"invoke\",1504,0,\"le\",[\"f64:9218868437227405312\",\"f64:4503599627370496\"]],[\"invoke\",1509,0,\"le\",[\"f64:18442240474082181120\",\"f64:13830554455654793216\"]],[\"invoke\",1514,0,\"le\",[\"f64:18442240474082181120\",\"f64:4618760256179416344\"]],[\"invoke\",1519,0,\"le\",[\"f64:9218868437227405312\",\"f64:18442240474082181119\"]],[\"invoke\",1523,0,\"le\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",1531,0,\"le\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",1541,0,\"le\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",1551,0,\"le\",[\"f64:18444492273895866368\",\"f64:4503599627370496\"]],[\"invoke\",1561,0,\"le\",[\"f64:9221120237041090560\",\"f64:13826050856027422720\"]],[\"invoke\",1571,0,\"le\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\"]],[\"invoke\",1581,0,\"le\",[\"f64:18444492273895866368\",\"f64:18442240474082181119\"]],[\"invoke\",1589,0,\"le\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",1601,0,\"le\",[\"f64:18444492273895866368\",\"f64:9221120237041090560\"]],[\"invoke\",1615,0,\"gt\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",1620,0,\"gt\",[\"f64:0\",\"f64:1\"]],[\"invoke\",1625,0,\"gt\",[\"f64:9223372036854775808\",\"f64:13826050856027422720\"]],[\"invoke\",1630,0,\"gt\",[\"f64:9223372036854775808\",\"f64:4607182418800017408\"]],[\"invoke\",1635,0,\"gt\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",1639,0,\"gt\",[\"f64:0\",\"f64:18442240474082181119\"]],[\"invoke\",1644,0,\"gt\",[\"f64:0\",\"f64:9218868437227405312\"]],[\"invoke\",1653,0,\"gt\",[\"f64:9223372036854775809\",\"f64:9223372036854775808\"]],[\"invoke\",1658,0,\"gt\",[\"f64:9223372036854775809\",\"f64:1\"]],[\"invoke\",1663,0,\"gt\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",1668,0,\"gt\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",1673,0,\"gt\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",1677,0,\"gt\",[\"f64:9223372036854775809\",\"f64:18442240474082181119\"]],[\"invoke\",1682,0,\"gt\",[\"f64:9223372036854775809\",\"f64:9218868437227405312\"]],[\"invoke\",1689,0,\"gt\",[\"f64:1\",\"f64:18444492273895866368\"]],[\"invoke\",1696,0,\"gt\",[\"f64:4503599627370496\",\"f64:0\"]],[\"invoke\",1701,0,\"gt\",[\"f64:9227875636482146304\",\"f64:9227875636482146304\"]],[\"invoke\",1706,0,\"gt\",[\"f64:9227875636482146304\",\"f64:4602678819172646912\"]],[\"invoke\",1711,0,\"gt\",[\"f64:4503599627370496\",\"f64:13830554455654793216\"]],[\"invoke\",1715,0,\"gt\",[\"f64:4503599627370496\",\"f64:13842132293034192152\"]],[\"invoke\",1720,0,\"gt\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",1725,0,\"gt\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",1734,0,\"gt\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",1739,0,\"gt\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",1744,0,\"gt\",[\"f64:4602678819172646912\",\"f64:4503599627370496\"]],[\"invoke\",1749,0,\"gt\",[\"f64:13826050856027422720\",\"f64:13830554455654793216\"]],[\"invoke\",1753,0,\"gt\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",1758,0,\"gt\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",1763,0,\"gt\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",1771,0,\"gt\",[\"f64:4602678819172646912\",\"f64:9221120237041090560\"]],[\"invoke\",1777,0,\"gt\",[\"f64:13830554455654793216\",\"f64:9223372036854775809\"]],[\"invoke\",1782,0,\"gt\",[\"f64:13830554455654793216\",\"f64:4503599627370496\"]],[\"invoke\",1787,0,\"gt\",[\"f64:4607182418800017408\",\"f64:13826050856027422720\"]],[\"invoke\",1791,0,\"gt\",[\"f64:4607182418800017408\",\"f64:13830554455654793216\"]],[\"invoke\",1796,0,\"gt\",[\"f64:4607182418800017408\",\"f64:4618760256179416344\"]],[\"invoke\",1801,0,\"gt\",[\"f64:13830554455654793216\",\"f64:18442240474082181120\"]],[\"invoke\",1807,0,\"gt\",[\"f64:13830554455654793216\",\"f64:9221120237041090560\"]],[\"invoke\",1815,0,\"gt\",[\"f64:4618760256179416344\",\"f64:9223372036854775808\"]],[\"invoke\",1820,0,\"gt\",[\"f64:4618760256179416344\",\"f64:1\"]],[\"invoke\",1825,0,\"gt\",[\"f64:13842132293034192152\",\"f64:13826050856027422720\"]],[\"invoke\",1830,0,\"gt\",[\"f64:13842132293034192152\",\"f64:4607182418800017408\"]],[\"invoke\",1834,0,\"gt\",[\"f64:13842132293034192152\",\"f64:4618760256179416344\"]],[\"invoke\",1839,0,\"gt\",[\"f64:4618760256179416344\",\"f64:18442240474082181119\"]],[\"invoke\",1844,0,\"gt\",[\"f64:4618760256179416344\",\"f64:9218868437227405312\"]],[\"invoke\",1853,0,\"gt\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",1858,0,\"gt\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",1863,0,\"gt\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",1868,0,\"gt\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",1872,0,\"gt\",[\"f64:9218868437227405311\",\"f64:4607182418800017408\"]],[\"invoke\",1877,0,\"gt\",[\"f64:18442240474082181119\",\"f64:18442240474082181119\"]],[\"invoke\",1882,0,\"gt\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",1889,0,\"gt\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",1896,0,\"gt\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",1901,0,\"gt\",[\"f64:18442240474082181120\",\"f64:9227875636482146304\"]],[\"invoke\",1906,0,\"gt\",[\"f64:18442240474082181120\",\"f64:4602678819172646912\"]],[\"invoke\",1910,0,\"gt\",[\"f64:18442240474082181120\",\"f64:4607182418800017408\"]],[\"invoke\",1915,0,\"gt\",[\"f64:9218868437227405312\",\"f64:13842132293034192152\"]],[\"invoke\",1920,0,\"gt\",[\"f64:9218868437227405312\",\"f64:9218868437227405311\"]],[\"invoke\",1925,0,\"gt\",[\"f64:18442240474082181120\",\"f64:18444492273895866368\"]],[\"invoke\",1935,0,\"gt\",[\"f64:18444492273895866368\",\"f64:0\"]],[\"invoke\",1945,0,\"gt\",[\"f64:9221120237041090560\",\"f64:9223372036854775809\"]],[\"invoke\",1955,0,\"gt\",[\"f64:9221120237041090560\",\"f64:4503599627370496\"]],[\"invoke\",1963,0,\"gt\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",1973,0,\"gt\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",1983,0,\"gt\",[\"f64:18444492273895866368\",\"f64:9218868437227405311\"]],[\"invoke\",1993,0,\"gt\",[\"f64:9221120237041090560\",\"f64:18442240474082181120\"]],[\"invoke\",2009,0,\"gt\",[\"f64:9221120237041090560\",\"f64:9221120237041090560\"]],[\"invoke\",2017,0,\"ge\",[\"f64:9223372036854775808\",\"f64:9223372036854775809\"]],[\"invoke\",2022,0,\"ge\",[\"f64:9223372036854775808\",\"f64:4503599627370496\"]],[\"invoke\",2026,0,\"ge\",[\"f64:9223372036854775808\",\"f64:4602678819172646912\"]],[\"invoke\",2031,0,\"ge\",[\"f64:0\",\"f64:13830554455654793216\"]],[\"invoke\",2036,0,\"ge\",[\"f64:0\",\"f64:4618760256179416344\"]],[\"invoke\",2041,0,\"ge\",[\"f64:9223372036854775808\",\"f64:18442240474082181120\"]],[\"invoke\",2047,0,\"ge\",[\"f64:9223372036854775808\",\"f64:9221120237041090560\"]],[\"invoke\",2055,0,\"ge\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",2060,0,\"ge\",[\"f64:1\",\"f64:1\"]],[\"invoke\",2064,0,\"ge\",[\"f64:1\",\"f64:4503599627370496\"]],[\"invoke\",2069,0,\"ge\",[\"f64:9223372036854775809\",\"f64:13830554455654793216\"]],[\"invoke\",2074,0,\"ge\",[\"f64:9223372036854775809\",\"f64:4618760256179416344\"]],[\"invoke\",2079,0,\"ge\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",2084,0,\"ge\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",2093,0,\"ge\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",2098,0,\"ge\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",2102,0,\"ge\",[\"f64:9227875636482146304\",\"f64:4503599627370496\"]],[\"invoke\",2107,0,\"ge\",[\"f64:4503599627370496\",\"f64:13826050856027422720\"]],[\"invoke\",2112,0,\"ge\",[\"f64:4503599627370496\",\"f64:4607182418800017408\"]],[\"invoke\",2117,0,\"ge\",[\"f64:9227875636482146304\",\"f64:18442240474082181119\"]],[\"invoke\",2122,0,\"ge\",[\"f64:9227875636482146304\",\"f64:9218868437227405312\"]],[\"invoke\",2129,0,\"ge\",[\"f64:4503599627370496\",\"f64:18444492273895866368\"]],[\"invoke\",2136,0,\"ge\",[\"f64:4602678819172646912\",\"f64:0\"]],[\"invoke\",2141,0,\"ge\",[\"f64:13826050856027422720\",\"f64:9227875636482146304\"]],[\"invoke\",2145,0,\"ge\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",2150,0,\"ge\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",2155,0,\"ge\",[\"f64:4602678819172646912\",\"f64:13842132293034192152\"]],[\"invoke\",2160,0,\"ge\",[\"f64:4602678819172646912\",\"f64:9218868437227405311\"]],[\"invoke\",2165,0,\"ge\",[\"f64:13826050856027422720\",\"f64:18444492273895866368\"]],[\"invoke\",2174,0,\"ge\",[\"f64:13830554455654793216\",\"f64:0\"]],[\"invoke\",2179,0,\"ge\",[\"f64:4607182418800017408\",\"f64:9223372036854775809\"]],[\"invoke\",2183,0,\"ge\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",2188,0,\"ge\",[\"f64:4607182418800017408\",\"f64:4602678819172646912\"]],[\"invoke\",2193,0,\"ge\",[\"f64:13830554455654793216\",\"f64:13842132293034192152\"]],[\"invoke\",2198,0,\"ge\",[\"f64:13830554455654793216\",\"f64:9218868437227405311\"]],[\"invoke\",2203,0,\"ge\",[\"f64:4607182418800017408\",\"f64:18442240474082181120\"]],[\"invoke\",2211,0,\"ge\",[\"f64:4607182418800017408\",\"f64:9221120237041090560\"]],[\"invoke\",2217,0,\"ge\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",2221,0,\"ge\",[\"f64:13842132293034192152\",\"f64:9227875636482146304\"]],[\"invoke\",2226,0,\"ge\",[\"f64:13842132293034192152\",\"f64:4602678819172646912\"]],[\"invoke\",2231,0,\"ge\",[\"f64:4618760256179416344\",\"f64:13830554455654793216\"]],[\"invoke\",2236,0,\"ge\",[\"f64:4618760256179416344\",\"f64:4618760256179416344\"]],[\"invoke\",2241,0,\"ge\",[\"f64:13842132293034192152\",\"f64:18442240474082181120\"]],[\"invoke\",2247,0,\"ge\",[\"f64:13842132293034192152\",\"f64:9221120237041090560\"]],[\"invoke\",2255,0,\"ge\",[\"f64:9218868437227405311\",\"f64:9223372036854775808\"]],[\"invoke\",2259,0,\"ge\",[\"f64:9218868437227405311\",\"f64:9223372036854775809\"]],[\"invoke\",2264,0,\"ge\",[\"f64:9218868437227405311\",\"f64:4503599627370496\"]],[\"invoke\",2269,0,\"ge\",[\"f64:18442240474082181119\",\"f64:13830554455654793216\"]],[\"invoke\",2274,0,\"ge\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",2279,0,\"ge\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",2284,0,\"ge\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",2293,0,\"ge\",[\"f64:18442240474082181120\",\"f64:9223372036854775808\"]],[\"invoke\",2297,0,\"ge\",[\"f64:18442240474082181120\",\"f64:9223372036854775809\"]],[\"invoke\",2302,0,\"ge\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",2307,0,\"ge\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",2312,0,\"ge\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",2317,0,\"ge\",[\"f64:18442240474082181120\",\"f64:18442240474082181119\"]],[\"invoke\",2322,0,\"ge\",[\"f64:18442240474082181120\",\"f64:9218868437227405312\"]],[\"invoke\",2329,0,\"ge\",[\"f64:9218868437227405312\",\"f64:18444492273895866368\"]],[\"invoke\",2337,0,\"ge\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",2347,0,\"ge\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",2357,0,\"ge\",[\"f64:18444492273895866368\",\"f64:13826050856027422720\"]],[\"invoke\",2367,0,\"ge\",[\"f64:18444492273895866368\",\"f64:4607182418800017408\"]],[\"invoke\",2377,0,\"ge\",[\"f64:9221120237041090560\",\"f64:13842132293034192152\"]],[\"invoke\",2387,0,\"ge\",[\"f64:9221120237041090560\",\"f64:9218868437227405311\"]],[\"invoke\",2397,0,\"ge\",[\"f64:18444492273895866368\",\"f64:18444492273895866368\"]],[\"file\",\"f32_bitwise.wast\"],[\"module\",4,4],[\"invoke\",10,0,\"copysign\",[\"f32:2147483648\",\"f32:2147483648\"]],[\"invoke\",11,0,\"copysign\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",12,0,\"copysign\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",13,0,\"copysign\",[\"f32:0\",\"f32:0\"]],[\"invoke\",14,0,\"copysign\",[\"f32:2147483648\",\"f32:2147483649\"]],[\"invoke\",15,0,\"copysign\",[\"f32:2147483648\",\"f32:1\"]],[\"invoke\",16,0,\"copysign\",[\"f32:0\",\"f32:2147483649\"]],[\"invoke\",17,0,\"copysign\",[\"f32:0\",\"f32:1\"]],[\"invoke\",18,0,\"copysign\",[\"f32:2147483648\",\"f32:2155872256\"]],[\"invoke\",19,0,\"copysign\",[\"f32:2147483648\",\"f32:8388608\"]],[\"invoke\",20,0,\"copysign\",[\"f32:0\",\"f32:2155872256\"]],[\"invoke\",21,0,\"copysign\",[\"f32:0\",\"f32:8388608\"]],[\"invoke\",22,0,\"copysign\",[\"f32:2147483648\",\"f32:3204448256\"]],[\"invoke\",23,0,\"copysign\",[\"f32:2147483648\",\"f32:1056964608\"]],[\"invoke\",24,0,\"copysign\",[\"f32:0\",\"f32:3204448256\"]],[\"invoke\",25,0,\"copysign\",[\"f32:0\",\"f32:1056964608\"]],[\"invoke\",26,0,\"copysign\",[\"f32:2147483648\",\"f32:3212836864\"]],[\"invoke\",27,0,\"copysign\",[\"f32:2147483648\",\"f32:1065353216\"]],[\"invoke\",28,0,\"copysign\",[\"f32:0\",\"f32:3212836864\"]],[\"invoke\",29,0,\"copysign\",[\"f32:0\",\"f32:1065353216\"]],[\"invoke\",30,0,\"copysign\",[\"f32:2147483648\",\"f32:3234402267\"]],[\"invoke\",31,0,\"copysign\",[\"f32:2147483648\",\"f32:1086918619\"]],[\"invoke\",32,0,\"copysign\",[\"f32:0\",\"f32:3234402267\"]],[\"invoke\",33,0,\"copysign\",[\"f32:0\",\"f32:1086918619\"]],[\"invoke\",34,0,\"copysign\",[\"f32:2147483648\",\"f32:4286578687\"]],[\"invoke\",35,0,\"copysign\",[\"f32:2147483648\",\"f32:2139095039\"]],[\"invoke\",36,0,\"copysign\",[\"f32:0\",\"f32:4286578687\"]],[\"invoke\",37,0,\"copysign\",[\"f32:0\",\"f32:2139095039\"]],[\"invoke\",38,0,\"copysign\",[\"f32:2147483648\",\"f32:4286578688\"]],[\"invoke\",39,0,\"copysign\",[\"f32:2147483648\",\"f32:2139095040\"]],[\"invoke\",40,0,\"copysign\",[\"f32:0\",\"f32:4286578688\"]],[\"invoke\",41,0,\"copysign\",[\"f32:0\",\"f32:2139095040\"]],[\"invoke\",42,0,\"copysign\",[\"f32:2147483648\",\"f32:4290772992\"]],[\"invoke\",43,0,\"copysign\",[\"f32:2147483648\",\"f32:2143289344\"]],[\"invoke\",44,0,\"copysign\",[\"f32:0\",\"f32:4290772992\"]],[\"invoke\",45,0,\"copysign\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",46,0,\"copysign\",[\"f32:2147483649\",\"f32:2147483648\"]],[\"invoke\",47,0,\"copysign\",[\"f32:2147483649\",\"f32:0\"]],[\"invoke\",48,0,\"copysign\",[\"f32:1\",\"f32:2147483648\"]],[\"invoke\",49,0,\"copysign\",[\"f32:1\",\"f32:0\"]],[\"invoke\",50,0,\"copysign\",[\"f32:2147483649\",\"f32:2147483649\"]],[\"invoke\",51,0,\"copysign\",[\"f32:2147483649\",\"f32:1\"]],[\"invoke\",52,0,\"copysign\",[\"f32:1\",\"f32:2147483649\"]],[\"invoke\",53,0,\"copysign\",[\"f32:1\",\"f32:1\"]],[\"invoke\",54,0,\"copysign\",[\"f32:2147483649\",\"f32:2155872256\"]],[\"invoke\",55,0,\"copysign\",[\"f32:2147483649\",\"f32:8388608\"]],[\"invoke\",56,0,\"copysign\",[\"f32:1\",\"f32:2155872256\"]],[\"invoke\",57,0,\"copysign\",[\"f32:1\",\"f32:8388608\"]],[\"invoke\",58,0,\"copysign\",[\"f32:2147483649\",\"f32:3204448256\"]],[\"invoke\",59,0,\"copysign\",[\"f32:2147483649\",\"f32:1056964608\"]],[\"invoke\",60,0,\"copysign\",[\"f32:1\",\"f32:3204448256\"]],[\"invoke\",61,0,\"copysign\",[\"f32:1\",\"f32:1056964608\"]],[\"invoke\",62,0,\"copysign\",[\"f32:2147483649\",\"f32:3212836864\"]],[\"invoke\",63,0,\"copysign\",[\"f32:2147483649\",\"f32:1065353216\"]],[\"invoke\",64,0,\"copysign\",[\"f32:1\",\"f32:3212836864\"]],[\"invoke\",65,0,\"copysign\",[\"f32:1\",\"f32:1065353216\"]],[\"invoke\",66,0,\"copysign\",[\"f32:2147483649\",\"f32:3234402267\"]],[\"invoke\",67,0,\"copysign\",[\"f32:2147483649\",\"f32:1086918619\"]],[\"invoke\",68,0,\"copysign\",[\"f32:1\",\"f32:3234402267\"]],[\"invoke\",69,0,\"copysign\",[\"f32:1\",\"f32:1086918619\"]],[\"invoke\",70,0,\"copysign\",[\"f32:2147483649\",\"f32:4286578687\"]],[\"invoke\",71,0,\"copysign\",[\"f32:2147483649\",\"f32:2139095039\"]],[\"invoke\",72,0,\"copysign\",[\"f32:1\",\"f32:4286578687\"]],[\"invoke\",73,0,\"copysign\",[\"f32:1\",\"f32:2139095039\"]],[\"invoke\",74,0,\"copysign\",[\"f32:2147483649\",\"f32:4286578688\"]],[\"invoke\",75,0,\"copysign\",[\"f32:2147483649\",\"f32:2139095040\"]],[\"invoke\",76,0,\"copysign\",[\"f32:1\",\"f32:4286578688\"]],[\"invoke\",77,0,\"copysign\",[\"f32:1\",\"f32:2139095040\"]],[\"invoke\",78,0,\"copysign\",[\"f32:2147483649\",\"f32:4290772992\"]],[\"invoke\",79,0,\"copysign\",[\"f32:2147483649\",\"f32:2143289344\"]],[\"invoke\",80,0,\"copysign\",[\"f32:1\",\"f32:4290772992\"]],[\"invoke\",81,0,\"copysign\",[\"f32:1\",\"f32:2143289344\"]],[\"invoke\",82,0,\"copysign\",[\"f32:2155872256\",\"f32:2147483648\"]],[\"invoke\",83,0,\"copysign\",[\"f32:2155872256\",\"f32:0\"]],[\"invoke\",84,0,\"copysign\",[\"f32:8388608\",\"f32:2147483648\"]],[\"invoke\",85,0,\"copysign\",[\"f32:8388608\",\"f32:0\"]],[\"invoke\",86,0,\"copysign\",[\"f32:2155872256\",\"f32:2147483649\"]],[\"invoke\",87,0,\"copysign\",[\"f32:2155872256\",\"f32:1\"]],[\"invoke\",88,0,\"copysign\",[\"f32:8388608\",\"f32:2147483649\"]],[\"invoke\",89,0,\"copysign\",[\"f32:8388608\",\"f32:1\"]],[\"invoke\",90,0,\"copysign\",[\"f32:2155872256\",\"f32:2155872256\"]],[\"invoke\",91,0,\"copysign\",[\"f32:2155872256\",\"f32:8388608\"]],[\"invoke\",92,0,\"copysign\",[\"f32:8388608\",\"f32:2155872256\"]],[\"invoke\",93,0,\"copysign\",[\"f32:8388608\",\"f32:8388608\"]],[\"invoke\",94,0,\"copysign\",[\"f32:2155872256\",\"f32:3204448256\"]],[\"invoke\",95,0,\"copysign\",[\"f32:2155872256\",\"f32:1056964608\"]],[\"invoke\",96,0,\"copysign\",[\"f32:8388608\",\"f32:3204448256\"]],[\"invoke\",97,0,\"copysign\",[\"f32:8388608\",\"f32:1056964608\"]],[\"invoke\",98,0,\"copysign\",[\"f32:2155872256\",\"f32:3212836864\"]],[\"invoke\",99,0,\"copysign\",[\"f32:2155872256\",\"f32:1065353216\"]],[\"invoke\",100,0,\"copysign\",[\"f32:8388608\",\"f32:3212836864\"]],[\"invoke\",101,0,\"copysign\",[\"f32:8388608\",\"f32:1065353216\"]],[\"invoke\",102,0,\"copysign\",[\"f32:2155872256\",\"f32:3234402267\"]],[\"invoke\",103,0,\"copysign\",[\"f32:2155872256\",\"f32:1086918619\"]],[\"invoke\",104,0,\"copysign\",[\"f32:8388608\",\"f32:3234402267\"]],[\"invoke\",105,0,\"copysign\",[\"f32:8388608\",\"f32:1086918619\"]],[\"invoke\",106,0,\"copysign\",[\"f32:2155872256\",\"f32:4286578687\"]],[\"invoke\",107,0,\"copysign\",[\"f32:2155872256\",\"f32:2139095039\"]],[\"invoke\",108,0,\"copysign\",[\"f32:8388608\",\"f32:4286578687\"]],[\"invoke\",109,0,\"copysign\",[\"f32:8388608\",\"f32:2139095039\"]],[\"invoke\",110,0,\"copysign\",[\"f32:2155872256\",\"f32:4286578688\"]],[\"invoke\",111,0,\"copysign\",[\"f32:2155872256\",\"f32:2139095040\"]],[\"invoke\",112,0,\"copysign\",[\"f32:8388608\",\"f32:4286578688\"]],[\"invoke\",113,0,\"copysign\",[\"f32:8388608\",\"f32:2139095040\"]],[\"invoke\",114,0,\"copysign\",[\"f32:2155872256\",\"f32:4290772992\"]],[\"invoke\",115,0,\"copysign\",[\"f32:2155872256\",\"f32:2143289344\"]],[\"invoke\",116,0,\"copysign\",[\"f32:8388608\",\"f32:4290772992\"]],[\"invoke\",117,0,\"copysign\",[\"f32:8388608\",\"f32:2143289344\"]],[\"invoke\",118,0,\"copysign\",[\"f32:3204448256\",\"f32:2147483648\"]],[\"invoke\",119,0,\"copysign\",[\"f32:3204448256\",\"f32:0\"]],[\"invoke\",120,0,\"copysign\",[\"f32:1056964608\",\"f32:2147483648\"]],[\"invoke\",121,0,\"copysign\",[\"f32:1056964608\",\"f32:0\"]],[\"invoke\",122,0,\"copysign\",[\"f32:3204448256\",\"f32:2147483649\"]],[\"invoke\",123,0,\"copysign\",[\"f32:3204448256\",\"f32:1\"]],[\"invoke\",124,0,\"copysign\",[\"f32:1056964608\",\"f32:2147483649\"]],[\"invoke\",125,0,\"copysign\",[\"f32:1056964608\",\"f32:1\"]],[\"invoke\",126,0,\"copysign\",[\"f32:3204448256\",\"f32:2155872256\"]],[\"invoke\",127,0,\"copysign\",[\"f32:3204448256\",\"f32:8388608\"]],[\"invoke\",128,0,\"copysign\",[\"f32:1056964608\",\"f32:2155872256\"]],[\"invoke\",129,0,\"copysign\",[\"f32:1056964608\",\"f32:8388608\"]],[\"invoke\",130,0,\"copysign\",[\"f32:3204448256\",\"f32:3204448256\"]],[\"invoke\",131,0,\"copysign\",[\"f32:3204448256\",\"f32:1056964608\"]],[\"invoke\",132,0,\"copysign\",[\"f32:1056964608\",\"f32:3204448256\"]],[\"invoke\",133,0,\"copysign\",[\"f32:1056964608\",\"f32:1056964608\"]],[\"invoke\",134,0,\"copysign\",[\"f32:3204448256\",\"f32:3212836864\"]],[\"invoke\",135,0,\"copysign\",[\"f32:3204448256\",\"f32:1065353216\"]],[\"invoke\",136,0,\"copysign\",[\"f32:1056964608\",\"f32:3212836864\"]],[\"invoke\",137,0,\"copysign\",[\"f32:1056964608\",\"f32:1065353216\"]],[\"invoke\",138,0,\"copysign\",[\"f32:3204448256\",\"f32:3234402267\"]],[\"invoke\",139,0,\"copysign\",[\"f32:3204448256\",\"f32:1086918619\"]],[\"invoke\",140,0,\"copysign\",[\"f32:1056964608\",\"f32:3234402267\"]],[\"invoke\",141,0,\"copysign\",[\"f32:1056964608\",\"f32:1086918619\"]],[\"invoke\",142,0,\"copysign\",[\"f32:3204448256\",\"f32:4286578687\"]],[\"invoke\",143,0,\"copysign\",[\"f32:3204448256\",\"f32:2139095039\"]],[\"invoke\",144,0,\"copysign\",[\"f32:1056964608\",\"f32:4286578687\"]],[\"invoke\",145,0,\"copysign\",[\"f32:1056964608\",\"f32:2139095039\"]],[\"invoke\",146,0,\"copysign\",[\"f32:3204448256\",\"f32:4286578688\"]],[\"invoke\",147,0,\"copysign\",[\"f32:3204448256\",\"f32:2139095040\"]],[\"invoke\",148,0,\"copysign\",[\"f32:1056964608\",\"f32:4286578688\"]],[\"invoke\",149,0,\"copysign\",[\"f32:1056964608\",\"f32:2139095040\"]],[\"invoke\",150,0,\"copysign\",[\"f32:3204448256\",\"f32:4290772992\"]],[\"invoke\",151,0,\"copysign\",[\"f32:3204448256\",\"f32:2143289344\"]],[\"invoke\",152,0,\"copysign\",[\"f32:1056964608\",\"f32:4290772992\"]],[\"invoke\",153,0,\"copysign\",[\"f32:1056964608\",\"f32:2143289344\"]],[\"invoke\",154,0,\"copysign\",[\"f32:3212836864\",\"f32:2147483648\"]],[\"invoke\",155,0,\"copysign\",[\"f32:3212836864\",\"f32:0\"]],[\"invoke\",156,0,\"copysign\",[\"f32:1065353216\",\"f32:2147483648\"]],[\"invoke\",157,0,\"copysign\",[\"f32:1065353216\",\"f32:0\"]],[\"invoke\",158,0,\"copysign\",[\"f32:3212836864\",\"f32:2147483649\"]],[\"invoke\",159,0,\"copysign\",[\"f32:3212836864\",\"f32:1\"]],[\"invoke\",160,0,\"copysign\",[\"f32:1065353216\",\"f32:2147483649\"]],[\"invoke\",161,0,\"copysign\",[\"f32:1065353216\",\"f32:1\"]],[\"invoke\",162,0,\"copysign\",[\"f32:3212836864\",\"f32:2155872256\"]],[\"invoke\",163,0,\"copysign\",[\"f32:3212836864\",\"f32:8388608\"]],[\"invoke\",164,0,\"copysign\",[\"f32:1065353216\",\"f32:2155872256\"]],[\"invoke\",165,0,\"copysign\",[\"f32:1065353216\",\"f32:8388608\"]],[\"invoke\",166,0,\"copysign\",[\"f32:3212836864\",\"f32:3204448256\"]],[\"invoke\",167,0,\"copysign\",[\"f32:3212836864\",\"f32:1056964608\"]],[\"invoke\",168,0,\"copysign\",[\"f32:1065353216\",\"f32:3204448256\"]],[\"invoke\",169,0,\"copysign\",[\"f32:1065353216\",\"f32:1056964608\"]],[\"invoke\",170,0,\"copysign\",[\"f32:3212836864\",\"f32:3212836864\"]],[\"invoke\",171,0,\"copysign\",[\"f32:3212836864\",\"f32:1065353216\"]],[\"invoke\",172,0,\"copysign\",[\"f32:1065353216\",\"f32:3212836864\"]],[\"invoke\",173,0,\"copysign\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"invoke\",174,0,\"copysign\",[\"f32:3212836864\",\"f32:3234402267\"]],[\"invoke\",175,0,\"copysign\",[\"f32:3212836864\",\"f32:1086918619\"]],[\"invoke\",176,0,\"copysign\",[\"f32:1065353216\",\"f32:3234402267\"]],[\"invoke\",177,0,\"copysign\",[\"f32:1065353216\",\"f32:1086918619\"]],[\"invoke\",178,0,\"copysign\",[\"f32:3212836864\",\"f32:4286578687\"]],[\"invoke\",179,0,\"copysign\",[\"f32:3212836864\",\"f32:2139095039\"]],[\"invoke\",180,0,\"copysign\",[\"f32:1065353216\",\"f32:4286578687\"]],[\"invoke\",181,0,\"copysign\",[\"f32:1065353216\",\"f32:2139095039\"]],[\"invoke\",182,0,\"copysign\",[\"f32:3212836864\",\"f32:4286578688\"]],[\"invoke\",183,0,\"copysign\",[\"f32:3212836864\",\"f32:2139095040\"]],[\"invoke\",184,0,\"copysign\",[\"f32:1065353216\",\"f32:4286578688\"]],[\"invoke\",185,0,\"copysign\",[\"f32:1065353216\",\"f32:2139095040\"]],[\"invoke\",186,0,\"copysign\",[\"f32:3212836864\",\"f32:4290772992\"]],[\"invoke\",187,0,\"copysign\",[\"f32:3212836864\",\"f32:2143289344\"]],[\"invoke\",188,0,\"copysign\",[\"f32:1065353216\",\"f32:4290772992\"]],[\"invoke\",189,0,\"copysign\",[\"f32:1065353216\",\"f32:2143289344\"]],[\"invoke\",190,0,\"copysign\",[\"f32:3234402267\",\"f32:2147483648\"]],[\"invoke\",191,0,\"copysign\",[\"f32:3234402267\",\"f32:0\"]],[\"invoke\",192,0,\"copysign\",[\"f32:1086918619\",\"f32:2147483648\"]],[\"invoke\",193,0,\"copysign\",[\"f32:1086918619\",\"f32:0\"]],[\"invoke\",194,0,\"copysign\",[\"f32:3234402267\",\"f32:2147483649\"]],[\"invoke\",195,0,\"copysign\",[\"f32:3234402267\",\"f32:1\"]],[\"invoke\",196,0,\"copysign\",[\"f32:1086918619\",\"f32:2147483649\"]],[\"invoke\",197,0,\"copysign\",[\"f32:1086918619\",\"f32:1\"]],[\"invoke\",198,0,\"copysign\",[\"f32:3234402267\",\"f32:2155872256\"]],[\"invoke\",199,0,\"copysign\",[\"f32:3234402267\",\"f32:8388608\"]],[\"invoke\",200,0,\"copysign\",[\"f32:1086918619\",\"f32:2155872256\"]],[\"invoke\",201,0,\"copysign\",[\"f32:1086918619\",\"f32:8388608\"]],[\"invoke\",202,0,\"copysign\",[\"f32:3234402267\",\"f32:3204448256\"]],[\"invoke\",203,0,\"copysign\",[\"f32:3234402267\",\"f32:1056964608\"]],[\"invoke\",204,0,\"copysign\",[\"f32:1086918619\",\"f32:3204448256\"]],[\"invoke\",205,0,\"copysign\",[\"f32:1086918619\",\"f32:1056964608\"]],[\"invoke\",206,0,\"copysign\",[\"f32:3234402267\",\"f32:3212836864\"]],[\"invoke\",207,0,\"copysign\",[\"f32:3234402267\",\"f32:1065353216\"]],[\"invoke\",208,0,\"copysign\",[\"f32:1086918619\",\"f32:3212836864\"]],[\"invoke\",209,0,\"copysign\",[\"f32:1086918619\",\"f32:1065353216\"]],[\"invoke\",210,0,\"copysign\",[\"f32:3234402267\",\"f32:3234402267\"]],[\"invoke\",211,0,\"copysign\",[\"f32:3234402267\",\"f32:1086918619\"]],[\"invoke\",212,0,\"copysign\",[\"f32:1086918619\",\"f32:3234402267\"]],[\"invoke\",213,0,\"copysign\",[\"f32:1086918619\",\"f32:1086918619\"]],[\"invoke\",214,0,\"copysign\",[\"f32:3234402267\",\"f32:4286578687\"]],[\"invoke\",215,0,\"copysign\",[\"f32:3234402267\",\"f32:2139095039\"]],[\"invoke\",216,0,\"copysign\",[\"f32:1086918619\",\"f32:4286578687\"]],[\"invoke\",217,0,\"copysign\",[\"f32:1086918619\",\"f32:2139095039\"]],[\"invoke\",218,0,\"copysign\",[\"f32:3234402267\",\"f32:4286578688\"]],[\"invoke\",219,0,\"copysign\",[\"f32:3234402267\",\"f32:2139095040\"]],[\"invoke\",220,0,\"copysign\",[\"f32:1086918619\",\"f32:4286578688\"]],[\"invoke\",221,0,\"copysign\",[\"f32:1086918619\",\"f32:2139095040\"]],[\"invoke\",222,0,\"copysign\",[\"f32:3234402267\",\"f32:4290772992\"]],[\"invoke\",223,0,\"copysign\",[\"f32:3234402267\",\"f32:2143289344\"]],[\"invoke\",224,0,\"copysign\",[\"f32:1086918619\",\"f32:4290772992\"]],[\"invoke\",225,0,\"copysign\",[\"f32:1086918619\",\"f32:2143289344\"]],[\"invoke\",226,0,\"copysign\",[\"f32:4286578687\",\"f32:2147483648\"]],[\"invoke\",227,0,\"copysign\",[\"f32:4286578687\",\"f32:0\"]],[\"invoke\",228,0,\"copysign\",[\"f32:2139095039\",\"f32:2147483648\"]],[\"invoke\",229,0,\"copysign\",[\"f32:2139095039\",\"f32:0\"]],[\"invoke\",230,0,\"copysign\",[\"f32:4286578687\",\"f32:2147483649\"]],[\"invoke\",231,0,\"copysign\",[\"f32:4286578687\",\"f32:1\"]],[\"invoke\",232,0,\"copysign\",[\"f32:2139095039\",\"f32:2147483649\"]],[\"invoke\",233,0,\"copysign\",[\"f32:2139095039\",\"f32:1\"]],[\"invoke\",234,0,\"copysign\",[\"f32:4286578687\",\"f32:2155872256\"]],[\"invoke\",235,0,\"copysign\",[\"f32:4286578687\",\"f32:8388608\"]],[\"invoke\",236,0,\"copysign\",[\"f32:2139095039\",\"f32:2155872256\"]],[\"invoke\",237,0,\"copysign\",[\"f32:2139095039\",\"f32:8388608\"]],[\"invoke\",238,0,\"copysign\",[\"f32:4286578687\",\"f32:3204448256\"]],[\"invoke\",239,0,\"copysign\",[\"f32:4286578687\",\"f32:1056964608\"]],[\"invoke\",240,0,\"copysign\",[\"f32:2139095039\",\"f32:3204448256\"]],[\"invoke\",241,0,\"copysign\",[\"f32:2139095039\",\"f32:1056964608\"]],[\"invoke\",242,0,\"copysign\",[\"f32:4286578687\",\"f32:3212836864\"]],[\"invoke\",243,0,\"copysign\",[\"f32:4286578687\",\"f32:1065353216\"]],[\"invoke\",244,0,\"copysign\",[\"f32:2139095039\",\"f32:3212836864\"]],[\"invoke\",245,0,\"copysign\",[\"f32:2139095039\",\"f32:1065353216\"]],[\"invoke\",246,0,\"copysign\",[\"f32:4286578687\",\"f32:3234402267\"]],[\"invoke\",247,0,\"copysign\",[\"f32:4286578687\",\"f32:1086918619\"]],[\"invoke\",248,0,\"copysign\",[\"f32:2139095039\",\"f32:3234402267\"]],[\"invoke\",249,0,\"copysign\",[\"f32:2139095039\",\"f32:1086918619\"]],[\"invoke\",250,0,\"copysign\",[\"f32:4286578687\",\"f32:4286578687\"]],[\"invoke\",251,0,\"copysign\",[\"f32:4286578687\",\"f32:2139095039\"]],[\"invoke\",252,0,\"copysign\",[\"f32:2139095039\",\"f32:4286578687\"]],[\"invoke\",253,0,\"copysign\",[\"f32:2139095039\",\"f32:2139095039\"]],[\"invoke\",254,0,\"copysign\",[\"f32:4286578687\",\"f32:4286578688\"]],[\"invoke\",255,0,\"copysign\",[\"f32:4286578687\",\"f32:2139095040\"]],[\"invoke\",256,0,\"copysign\",[\"f32:2139095039\",\"f32:4286578688\"]],[\"invoke\",257,0,\"copysign\",[\"f32:2139095039\",\"f32:2139095040\"]],[\"invoke\",258,0,\"copysign\",[\"f32:4286578687\",\"f32:4290772992\"]],[\"invoke\",259,0,\"copysign\",[\"f32:4286578687\",\"f32:2143289344\"]],[\"invoke\",260,0,\"copysign\",[\"f32:2139095039\",\"f32:4290772992\"]],[\"invoke\",261,0,\"copysign\",[\"f32:2139095039\",\"f32:2143289344\"]],[\"invoke\",262,0,\"copysign\",[\"f32:4286578688\",\"f32:2147483648\"]],[\"invoke\",263,0,\"copysign\",[\"f32:4286578688\",\"f32:0\"]],[\"invoke\",264,0,\"copysign\",[\"f32:2139095040\",\"f32:2147483648\"]],[\"invoke\",265,0,\"copysign\",[\"f32:2139095040\",\"f32:0\"]],[\"invoke\",266,0,\"copysign\",[\"f32:4286578688\",\"f32:2147483649\"]],[\"invoke\",267,0,\"copysign\",[\"f32:4286578688\",\"f32:1\"]],[\"invoke\",268,0,\"copysign\",[\"f32:2139095040\",\"f32:2147483649\"]],[\"invoke\",269,0,\"copysign\",[\"f32:2139095040\",\"f32:1\"]],[\"invoke\",270,0,\"copysign\",[\"f32:4286578688\",\"f32:2155872256\"]],[\"invoke\",271,0,\"copysign\",[\"f32:4286578688\",\"f32:8388608\"]],[\"invoke\",272,0,\"copysign\",[\"f32:2139095040\",\"f32:2155872256\"]],[\"invoke\",273,0,\"copysign\",[\"f32:2139095040\",\"f32:8388608\"]],[\"invoke\",274,0,\"copysign\",[\"f32:4286578688\",\"f32:3204448256\"]],[\"invoke\",275,0,\"copysign\",[\"f32:4286578688\",\"f32:1056964608\"]],[\"invoke\",276,0,\"copysign\",[\"f32:2139095040\",\"f32:3204448256\"]],[\"invoke\",277,0,\"copysign\",[\"f32:2139095040\",\"f32:1056964608\"]],[\"invoke\",278,0,\"copysign\",[\"f32:4286578688\",\"f32:3212836864\"]],[\"invoke\",279,0,\"copysign\",[\"f32:4286578688\",\"f32:1065353216\"]],[\"invoke\",280,0,\"copysign\",[\"f32:2139095040\",\"f32:3212836864\"]],[\"invoke\",281,0,\"copysign\",[\"f32:2139095040\",\"f32:1065353216\"]],[\"invoke\",282,0,\"copysign\",[\"f32:4286578688\",\"f32:3234402267\"]],[\"invoke\",283,0,\"copysign\",[\"f32:4286578688\",\"f32:1086918619\"]],[\"invoke\",284,0,\"copysign\",[\"f32:2139095040\",\"f32:3234402267\"]],[\"invoke\",285,0,\"copysign\",[\"f32:2139095040\",\"f32:1086918619\"]],[\"invoke\",286,0,\"copysign\",[\"f32:4286578688\",\"f32:4286578687\"]],[\"invoke\",287,0,\"copysign\",[\"f32:4286578688\",\"f32:2139095039\"]],[\"invoke\",288,0,\"copysign\",[\"f32:2139095040\",\"f32:4286578687\"]],[\"invoke\",289,0,\"copysign\",[\"f32:2139095040\",\"f32:2139095039\"]],[\"invoke\",290,0,\"copysign\",[\"f32:4286578688\",\"f32:4286578688\"]],[\"invoke\",291,0,\"copysign\",[\"f32:4286578688\",\"f32:2139095040\"]],[\"invoke\",292,0,\"copysign\",[\"f32:2139095040\",\"f32:4286578688\"]],[\"invoke\",293,0,\"copysign\",[\"f32:2139095040\",\"f32:2139095040\"]],[\"invoke\",294,0,\"copysign\",[\"f32:4286578688\",\"f32:4290772992\"]],[\"invoke\",295,0,\"copysign\",[\"f32:4286578688\",\"f32:2143289344\"]],[\"invoke\",296,0,\"copysign\",[\"f32:2139095040\",\"f32:4290772992\"]],[\"invoke\",297,0,\"copysign\",[\"f32:2139095040\",\"f32:2143289344\"]],[\"invoke\",298,0,\"copysign\",[\"f32:4290772992\",\"f32:2147483648\"]],[\"invoke\",299,0,\"copysign\",[\"f32:4290772992\",\"f32:0\"]],[\"invoke\",300,0,\"copysign\",[\"f32:2143289344\",\"f32:2147483648\"]],[\"invoke\",301,0,\"copysign\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",302,0,\"copysign\",[\"f32:4290772992\",\"f32:2147483649\"]],[\"invoke\",303,0,\"copysign\",[\"f32:4290772992\",\"f32:1\"]],[\"invoke\",304,0,\"copysign\",[\"f32:2143289344\",\"f32:2147483649\"]],[\"invoke\",305,0,\"copysign\",[\"f32:2143289344\",\"f32:1\"]],[\"invoke\",306,0,\"copysign\",[\"f32:4290772992\",\"f32:2155872256\"]],[\"invoke\",307,0,\"copysign\",[\"f32:4290772992\",\"f32:8388608\"]],[\"invoke\",308,0,\"copysign\",[\"f32:2143289344\",\"f32:2155872256\"]],[\"invoke\",309,0,\"copysign\",[\"f32:2143289344\",\"f32:8388608\"]],[\"invoke\",310,0,\"copysign\",[\"f32:4290772992\",\"f32:3204448256\"]],[\"invoke\",311,0,\"copysign\",[\"f32:4290772992\",\"f32:1056964608\"]],[\"invoke\",312,0,\"copysign\",[\"f32:2143289344\",\"f32:3204448256\"]],[\"invoke\",313,0,\"copysign\",[\"f32:2143289344\",\"f32:1056964608\"]],[\"invoke\",314,0,\"copysign\",[\"f32:4290772992\",\"f32:3212836864\"]],[\"invoke\",315,0,\"copysign\",[\"f32:4290772992\",\"f32:1065353216\"]],[\"invoke\",316,0,\"copysign\",[\"f32:2143289344\",\"f32:3212836864\"]],[\"invoke\",317,0,\"copysign\",[\"f32:2143289344\",\"f32:1065353216\"]],[\"invoke\",318,0,\"copysign\",[\"f32:4290772992\",\"f32:3234402267\"]],[\"invoke\",319,0,\"copysign\",[\"f32:4290772992\",\"f32:1086918619\"]],[\"invoke\",320,0,\"copysign\",[\"f32:2143289344\",\"f32:3234402267\"]],[\"invoke\",321,0,\"copysign\",[\"f32:2143289344\",\"f32:1086918619\"]],[\"invoke\",322,0,\"copysign\",[\"f32:4290772992\",\"f32:4286578687\"]],[\"invoke\",323,0,\"copysign\",[\"f32:4290772992\",\"f32:2139095039\"]],[\"invoke\",324,0,\"copysign\",[\"f32:2143289344\",\"f32:4286578687\"]],[\"invoke\",325,0,\"copysign\",[\"f32:2143289344\",\"f32:2139095039\"]],[\"invoke\",326,0,\"copysign\",[\"f32:4290772992\",\"f32:4286578688\"]],[\"invoke\",327,0,\"copysign\",[\"f32:4290772992\",\"f32:2139095040\"]],[\"invoke\",328,0,\"copysign\",[\"f32:2143289344\",\"f32:4286578688\"]],[\"invoke\",329,0,\"copysign\",[\"f32:2143289344\",\"f32:2139095040\"]],[\"invoke\",330,0,\"copysign\",[\"f32:4290772992\",\"f32:4290772992\"]],[\"invoke\",331,0,\"copysign\",[\"f32:4290772992\",\"f32:2143289344\"]],[\"invoke\",332,0,\"copysign\",[\"f32:2143289344\",\"f32:4290772992\"]],[\"invoke\",333,0,\"copysign\",[\"f32:2143289344\",\"f32:2143289344\"]],[\"invoke\",334,0,\"abs\",[\"f32:2147483648\"]],[\"invoke\",335,0,\"abs\",[\"f32:0\"]],[\"invoke\",336,0,\"abs\",[\"f32:2147483649\"]],[\"invoke\",337,0,\"abs\",[\"f32:1\"]],[\"invoke\",338,0,\"abs\",[\"f32:2155872256\"]],[\"invoke\",339,0,\"abs\",[\"f32:8388608\"]],[\"invoke\",340,0,\"abs\",[\"f32:3204448256\"]],[\"invoke\",341,0,\"abs\",[\"f32:1056964608\"]],[\"invoke\",342,0,\"abs\",[\"f32:3212836864\"]],[\"invoke\",343,0,\"abs\",[\"f32:1065353216\"]],[\"invoke\",344,0,\"abs\",[\"f32:3234402267\"]],[\"invoke\",345,0,\"abs\",[\"f32:1086918619\"]],[\"invoke\",346,0,\"abs\",[\"f32:4286578687\"]],[\"invoke\",347,0,\"abs\",[\"f32:2139095039\"]],[\"invoke\",348,0,\"abs\",[\"f32:4286578688\"]],[\"invoke\",349,0,\"abs\",[\"f32:2139095040\"]],[\"invoke\",350,0,\"abs\",[\"f32:4290772992\"]],[\"invoke\",351,0,\"abs\",[\"f32:2143289344\"]],[\"invoke\",352,0,\"neg\",[\"f32:2147483648\"]],[\"invoke\",353,0,\"neg\",[\"f32:0\"]],[\"invoke\",354,0,\"neg\",[\"f32:2147483649\"]],[\"invoke\",355,0,\"neg\",[\"f32:1\"]],[\"invoke\",356,0,\"neg\",[\"f32:2155872256\"]],[\"invoke\",357,0,\"neg\",[\"f32:8388608\"]],[\"invoke\",358,0,\"neg\",[\"f32:3204448256\"]],[\"invoke\",359,0,\"neg\",[\"f32:1056964608\"]],[\"invoke\",360,0,\"neg\",[\"f32:3212836864\"]],[\"invoke\",361,0,\"neg\",[\"f32:1065353216\"]],[\"invoke\",362,0,\"neg\",[\"f32:3234402267\"]],[\"invoke\",363,0,\"neg\",[\"f32:1086918619\"]],[\"invoke\",364,0,\"neg\",[\"f32:4286578687\"]],[\"invoke\",365,0,\"neg\",[\"f32:2139095039\"]],[\"invoke\",366,0,\"neg\",[\"f32:4286578688\"]],[\"invoke\",367,0,\"neg\",[\"f32:2139095040\"]],[\"invoke\",368,0,\"neg\",[\"f32:4290772992\"]],[\"invoke\",369,0,\"neg\",[\"f32:2143289344\"]],[\"file\",\"f64_bitwise.wast\"],[\"module\",4,5],[\"invoke\",10,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",11,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",12,0,\"copysign\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",13,0,\"copysign\",[\"f64:0\",\"f64:0\"]],[\"invoke\",14,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9223372036854775809\"]],[\"invoke\",15,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:1\"]],[\"invoke\",16,0,\"copysign\",[\"f64:0\",\"f64:9223372036854775809\"]],[\"invoke\",17,0,\"copysign\",[\"f64:0\",\"f64:1\"]],[\"invoke\",18,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9227875636482146304\"]],[\"invoke\",19,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:4503599627370496\"]],[\"invoke\",20,0,\"copysign\",[\"f64:0\",\"f64:9227875636482146304\"]],[\"invoke\",21,0,\"copysign\",[\"f64:0\",\"f64:4503599627370496\"]],[\"invoke\",22,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:13826050856027422720\"]],[\"invoke\",23,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:4602678819172646912\"]],[\"invoke\",24,0,\"copysign\",[\"f64:0\",\"f64:13826050856027422720\"]],[\"invoke\",25,0,\"copysign\",[\"f64:0\",\"f64:4602678819172646912\"]],[\"invoke\",26,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:13830554455654793216\"]],[\"invoke\",27,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:4607182418800017408\"]],[\"invoke\",28,0,\"copysign\",[\"f64:0\",\"f64:13830554455654793216\"]],[\"invoke\",29,0,\"copysign\",[\"f64:0\",\"f64:4607182418800017408\"]],[\"invoke\",30,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:13842132293034192152\"]],[\"invoke\",31,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:4618760256179416344\"]],[\"invoke\",32,0,\"copysign\",[\"f64:0\",\"f64:13842132293034192152\"]],[\"invoke\",33,0,\"copysign\",[\"f64:0\",\"f64:4618760256179416344\"]],[\"invoke\",34,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:18442240474082181119\"]],[\"invoke\",35,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9218868437227405311\"]],[\"invoke\",36,0,\"copysign\",[\"f64:0\",\"f64:18442240474082181119\"]],[\"invoke\",37,0,\"copysign\",[\"f64:0\",\"f64:9218868437227405311\"]],[\"invoke\",38,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:18442240474082181120\"]],[\"invoke\",39,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9218868437227405312\"]],[\"invoke\",40,0,\"copysign\",[\"f64:0\",\"f64:18442240474082181120\"]],[\"invoke\",41,0,\"copysign\",[\"f64:0\",\"f64:9218868437227405312\"]],[\"invoke\",42,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:18444492273895866368\"]],[\"invoke\",43,0,\"copysign\",[\"f64:9223372036854775808\",\"f64:9221120237041090560\"]],[\"invoke\",44,0,\"copysign\",[\"f64:0\",\"f64:18444492273895866368\"]],[\"invoke\",45,0,\"copysign\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",46,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9223372036854775808\"]],[\"invoke\",47,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:0\"]],[\"invoke\",48,0,\"copysign\",[\"f64:1\",\"f64:9223372036854775808\"]],[\"invoke\",49,0,\"copysign\",[\"f64:1\",\"f64:0\"]],[\"invoke\",50,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9223372036854775809\"]],[\"invoke\",51,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:1\"]],[\"invoke\",52,0,\"copysign\",[\"f64:1\",\"f64:9223372036854775809\"]],[\"invoke\",53,0,\"copysign\",[\"f64:1\",\"f64:1\"]],[\"invoke\",54,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9227875636482146304\"]],[\"invoke\",55,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:4503599627370496\"]],[\"invoke\",56,0,\"copysign\",[\"f64:1\",\"f64:9227875636482146304\"]],[\"invoke\",57,0,\"copysign\",[\"f64:1\",\"f64:4503599627370496\"]],[\"invoke\",58,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:13826050856027422720\"]],[\"invoke\",59,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:4602678819172646912\"]],[\"invoke\",60,0,\"copysign\",[\"f64:1\",\"f64:13826050856027422720\"]],[\"invoke\",61,0,\"copysign\",[\"f64:1\",\"f64:4602678819172646912\"]],[\"invoke\",62,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:13830554455654793216\"]],[\"invoke\",63,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:4607182418800017408\"]],[\"invoke\",64,0,\"copysign\",[\"f64:1\",\"f64:13830554455654793216\"]],[\"invoke\",65,0,\"copysign\",[\"f64:1\",\"f64:4607182418800017408\"]],[\"invoke\",66,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:13842132293034192152\"]],[\"invoke\",67,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:4618760256179416344\"]],[\"invoke\",68,0,\"copysign\",[\"f64:1\",\"f64:13842132293034192152\"]],[\"invoke\",69,0,\"copysign\",[\"f64:1\",\"f64:4618760256179416344\"]],[\"invoke\",70,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:18442240474082181119\"]],[\"invoke\",71,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9218868437227405311\"]],[\"invoke\",72,0,\"copysign\",[\"f64:1\",\"f64:18442240474082181119\"]],[\"invoke\",73,0,\"copysign\",[\"f64:1\",\"f64:9218868437227405311\"]],[\"invoke\",74,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:18442240474082181120\"]],[\"invoke\",75,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9218868437227405312\"]],[\"invoke\",76,0,\"copysign\",[\"f64:1\",\"f64:18442240474082181120\"]],[\"invoke\",77,0,\"copysign\",[\"f64:1\",\"f64:9218868437227405312\"]],[\"invoke\",78,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:18444492273895866368\"]],[\"invoke\",79,0,\"copysign\",[\"f64:9223372036854775809\",\"f64:9221120237041090560\"]],[\"invoke\",80,0,\"copysign\",[\"f64:1\",\"f64:18444492273895866368\"]],[\"invoke\",81,0,\"copysign\",[\"f64:1\",\"f64:9221120237041090560\"]],[\"invoke\",82,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9223372036854775808\"]],[\"invoke\",83,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:0\"]],[\"invoke\",84,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9223372036854775808\"]],[\"invoke\",85,0,\"copysign\",[\"f64:4503599627370496\",\"f64:0\"]],[\"invoke\",86,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9223372036854775809\"]],[\"invoke\",87,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:1\"]],[\"invoke\",88,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9223372036854775809\"]],[\"invoke\",89,0,\"copysign\",[\"f64:4503599627370496\",\"f64:1\"]],[\"invoke\",90,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9227875636482146304\"]],[\"invoke\",91,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:4503599627370496\"]],[\"invoke\",92,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9227875636482146304\"]],[\"invoke\",93,0,\"copysign\",[\"f64:4503599627370496\",\"f64:4503599627370496\"]],[\"invoke\",94,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:13826050856027422720\"]],[\"invoke\",95,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:4602678819172646912\"]],[\"invoke\",96,0,\"copysign\",[\"f64:4503599627370496\",\"f64:13826050856027422720\"]],[\"invoke\",97,0,\"copysign\",[\"f64:4503599627370496\",\"f64:4602678819172646912\"]],[\"invoke\",98,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:13830554455654793216\"]],[\"invoke\",99,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:4607182418800017408\"]],[\"invoke\",100,0,\"copysign\",[\"f64:4503599627370496\",\"f64:13830554455654793216\"]],[\"invoke\",101,0,\"copysign\",[\"f64:4503599627370496\",\"f64:4607182418800017408\"]],[\"invoke\",102,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:13842132293034192152\"]],[\"invoke\",103,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:4618760256179416344\"]],[\"invoke\",104,0,\"copysign\",[\"f64:4503599627370496\",\"f64:13842132293034192152\"]],[\"invoke\",105,0,\"copysign\",[\"f64:4503599627370496\",\"f64:4618760256179416344\"]],[\"invoke\",106,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:18442240474082181119\"]],[\"invoke\",107,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9218868437227405311\"]],[\"invoke\",108,0,\"copysign\",[\"f64:4503599627370496\",\"f64:18442240474082181119\"]],[\"invoke\",109,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9218868437227405311\"]],[\"invoke\",110,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:18442240474082181120\"]],[\"invoke\",111,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9218868437227405312\"]],[\"invoke\",112,0,\"copysign\",[\"f64:4503599627370496\",\"f64:18442240474082181120\"]],[\"invoke\",113,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9218868437227405312\"]],[\"invoke\",114,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:18444492273895866368\"]],[\"invoke\",115,0,\"copysign\",[\"f64:9227875636482146304\",\"f64:9221120237041090560\"]],[\"invoke\",116,0,\"copysign\",[\"f64:4503599627370496\",\"f64:18444492273895866368\"]],[\"invoke\",117,0,\"copysign\",[\"f64:4503599627370496\",\"f64:9221120237041090560\"]],[\"invoke\",118,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9223372036854775808\"]],[\"invoke\",119,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:0\"]],[\"invoke\",120,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9223372036854775808\"]],[\"invoke\",121,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:0\"]],[\"invoke\",122,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9223372036854775809\"]],[\"invoke\",123,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:1\"]],[\"invoke\",124,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9223372036854775809\"]],[\"invoke\",125,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:1\"]],[\"invoke\",126,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9227875636482146304\"]],[\"invoke\",127,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:4503599627370496\"]],[\"invoke\",128,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9227875636482146304\"]],[\"invoke\",129,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:4503599627370496\"]],[\"invoke\",130,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:13826050856027422720\"]],[\"invoke\",131,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:4602678819172646912\"]],[\"invoke\",132,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:13826050856027422720\"]],[\"invoke\",133,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:4602678819172646912\"]],[\"invoke\",134,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:13830554455654793216\"]],[\"invoke\",135,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:4607182418800017408\"]],[\"invoke\",136,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:13830554455654793216\"]],[\"invoke\",137,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:4607182418800017408\"]],[\"invoke\",138,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:13842132293034192152\"]],[\"invoke\",139,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:4618760256179416344\"]],[\"invoke\",140,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:13842132293034192152\"]],[\"invoke\",141,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:4618760256179416344\"]],[\"invoke\",142,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:18442240474082181119\"]],[\"invoke\",143,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9218868437227405311\"]],[\"invoke\",144,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:18442240474082181119\"]],[\"invoke\",145,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9218868437227405311\"]],[\"invoke\",146,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:18442240474082181120\"]],[\"invoke\",147,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9218868437227405312\"]],[\"invoke\",148,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:18442240474082181120\"]],[\"invoke\",149,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9218868437227405312\"]],[\"invoke\",150,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:18444492273895866368\"]],[\"invoke\",151,0,\"copysign\",[\"f64:13826050856027422720\",\"f64:9221120237041090560\"]],[\"invoke\",152,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:18444492273895866368\"]],[\"invoke\",153,0,\"copysign\",[\"f64:4602678819172646912\",\"f64:9221120237041090560\"]],[\"invoke\",154,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9223372036854775808\"]],[\"invoke\",155,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:0\"]],[\"invoke\",156,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9223372036854775808\"]],[\"invoke\",157,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:0\"]],[\"invoke\",158,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9223372036854775809\"]],[\"invoke\",159,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:1\"]],[\"invoke\",160,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9223372036854775809\"]],[\"invoke\",161,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:1\"]],[\"invoke\",162,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9227875636482146304\"]],[\"invoke\",163,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:4503599627370496\"]],[\"invoke\",164,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9227875636482146304\"]],[\"invoke\",165,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:4503599627370496\"]],[\"invoke\",166,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:13826050856027422720\"]],[\"invoke\",167,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:4602678819172646912\"]],[\"invoke\",168,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:13826050856027422720\"]],[\"invoke\",169,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:4602678819172646912\"]],[\"invoke\",170,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:13830554455654793216\"]],[\"invoke\",171,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:4607182418800017408\"]],[\"invoke\",172,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:13830554455654793216\"]],[\"invoke\",173,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:4607182418800017408\"]],[\"invoke\",174,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:13842132293034192152\"]],[\"invoke\",175,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:4618760256179416344\"]],[\"invoke\",176,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:13842132293034192152\"]],[\"invoke\",177,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:4618760256179416344\"]],[\"invoke\",178,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:18442240474082181119\"]],[\"invoke\",179,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9218868437227405311\"]],[\"invoke\",180,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:18442240474082181119\"]],[\"invoke\",181,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9218868437227405311\"]],[\"invoke\",182,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:18442240474082181120\"]],[\"invoke\",183,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9218868437227405312\"]],[\"invoke\",184,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:18442240474082181120\"]],[\"invoke\",185,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9218868437227405312\"]],[\"invoke\",186,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:18444492273895866368\"]],[\"invoke\",187,0,\"copysign\",[\"f64:13830554455654793216\",\"f64:9221120237041090560\"]],[\"invoke\",188,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:18444492273895866368\"]],[\"invoke\",189,0,\"copysign\",[\"f64:4607182418800017408\",\"f64:9221120237041090560\"]],[\"invoke\",190,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9223372036854775808\"]],[\"invoke\",191,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:0\"]],[\"invoke\",192,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9223372036854775808\"]],[\"invoke\",193,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:0\"]],[\"invoke\",194,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9223372036854775809\"]],[\"invoke\",195,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:1\"]],[\"invoke\",196,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9223372036854775809\"]],[\"invoke\",197,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:1\"]],[\"invoke\",198,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9227875636482146304\"]],[\"invoke\",199,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:4503599627370496\"]],[\"invoke\",200,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9227875636482146304\"]],[\"invoke\",201,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:4503599627370496\"]],[\"invoke\",202,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:13826050856027422720\"]],[\"invoke\",203,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:4602678819172646912\"]],[\"invoke\",204,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:13826050856027422720\"]],[\"invoke\",205,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:4602678819172646912\"]],[\"invoke\",206,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:13830554455654793216\"]],[\"invoke\",207,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:4607182418800017408\"]],[\"invoke\",208,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:13830554455654793216\"]],[\"invoke\",209,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:4607182418800017408\"]],[\"invoke\",210,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:13842132293034192152\"]],[\"invoke\",211,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:4618760256179416344\"]],[\"invoke\",212,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:13842132293034192152\"]],[\"invoke\",213,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:4618760256179416344\"]],[\"invoke\",214,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:18442240474082181119\"]],[\"invoke\",215,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9218868437227405311\"]],[\"invoke\",216,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:18442240474082181119\"]],[\"invoke\",217,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9218868437227405311\"]],[\"invoke\",218,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:18442240474082181120\"]],[\"invoke\",219,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9218868437227405312\"]],[\"invoke\",220,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:18442240474082181120\"]],[\"invoke\",221,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9218868437227405312\"]],[\"invoke\",222,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:18444492273895866368\"]],[\"invoke\",223,0,\"copysign\",[\"f64:13842132293034192152\",\"f64:9221120237041090560\"]],[\"invoke\",224,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:18444492273895866368\"]],[\"invoke\",225,0,\"copysign\",[\"f64:4618760256179416344\",\"f64:9221120237041090560\"]],[\"invoke\",226,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9223372036854775808\"]],[\"invoke\",227,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:0\"]],[\"invoke\",228,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9223372036854775808\"]],[\"invoke\",229,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:0\"]],[\"invoke\",230,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9223372036854775809\"]],[\"invoke\",231,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:1\"]],[\"invoke\",232,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9223372036854775809\"]],[\"invoke\",233,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:1\"]],[\"invoke\",234,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9227875636482146304\"]],[\"invoke\",235,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:4503599627370496\"]],[\"invoke\",236,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9227875636482146304\"]],[\"invoke\",237,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:4503599627370496\"]],[\"invoke\",238,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:13826050856027422720\"]],[\"invoke\",239,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:4602678819172646912\"]],[\"invoke\",240,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:13826050856027422720\"]],[\"invoke\",241,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:4602678819172646912\"]],[\"invoke\",242,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:13830554455654793216\"]],[\"invoke\",243,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:4607182418800017408\"]],[\"invoke\",244,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:13830554455654793216\"]],[\"invoke\",245,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:4607182418800017408\"]],[\"invoke\",246,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:13842132293034192152\"]],[\"invoke\",247,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:4618760256179416344\"]],[\"invoke\",248,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:13842132293034192152\"]],[\"invoke\",249,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:4618760256179416344\"]],[\"invoke\",250,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:18442240474082181119\"]],[\"invoke\",251,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9218868437227405311\"]],[\"invoke\",252,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:18442240474082181119\"]],[\"invoke\",253,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9218868437227405311\"]],[\"invoke\",254,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:18442240474082181120\"]],[\"invoke\",255,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9218868437227405312\"]],[\"invoke\",256,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:18442240474082181120\"]],[\"invoke\",257,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9218868437227405312\"]],[\"invoke\",258,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:18444492273895866368\"]],[\"invoke\",259,0,\"copysign\",[\"f64:18442240474082181119\",\"f64:9221120237041090560\"]],[\"invoke\",260,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:18444492273895866368\"]],[\"invoke\",261,0,\"copysign\",[\"f64:9218868437227405311\",\"f64:9221120237041090560\"]],[\"invoke\",262,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9223372036854775808\"]],[\"invoke\",263,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:0\"]],[\"invoke\",264,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9223372036854775808\"]],[\"invoke\",265,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:0\"]],[\"invoke\",266,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9223372036854775809\"]],[\"invoke\",267,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:1\"]],[\"invoke\",268,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9223372036854775809\"]],[\"invoke\",269,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:1\"]],[\"invoke\",270,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9227875636482146304\"]],[\"invoke\",271,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:4503599627370496\"]],[\"invoke\",272,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9227875636482146304\"]],[\"invoke\",273,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:4503599627370496\"]],[\"invoke\",274,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:13826050856027422720\"]],[\"invoke\",275,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:4602678819172646912\"]],[\"invoke\",276,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:13826050856027422720\"]],[\"invoke\",277,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:4602678819172646912\"]],[\"invoke\",278,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:13830554455654793216\"]],[\"invoke\",279,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:4607182418800017408\"]],[\"invoke\",280,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:13830554455654793216\"]],[\"invoke\",281,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:4607182418800017408\"]],[\"invoke\",282,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:13842132293034192152\"]],[\"invoke\",283,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:4618760256179416344\"]],[\"invoke\",284,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:13842132293034192152\"]],[\"invoke\",285,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:4618760256179416344\"]],[\"invoke\",286,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:18442240474082181119\"]],[\"invoke\",287,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9218868437227405311\"]],[\"invoke\",288,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:18442240474082181119\"]],[\"invoke\",289,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9218868437227405311\"]],[\"invoke\",290,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:18442240474082181120\"]],[\"invoke\",291,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9218868437227405312\"]],[\"invoke\",292,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:18442240474082181120\"]],[\"invoke\",293,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9218868437227405312\"]],[\"invoke\",294,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:18444492273895866368\"]],[\"invoke\",295,0,\"copysign\",[\"f64:18442240474082181120\",\"f64:9221120237041090560\"]],[\"invoke\",296,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:18444492273895866368\"]],[\"invoke\",297,0,\"copysign\",[\"f64:9218868437227405312\",\"f64:9221120237041090560\"]],[\"invoke\",298,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9223372036854775808\"]],[\"invoke\",299,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:0\"]],[\"invoke\",300,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9223372036854775808\"]],[\"invoke\",301,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",302,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9223372036854775809\"]],[\"invoke\",303,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:1\"]],[\"invoke\",304,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9223372036854775809\"]],[\"invoke\",305,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:1\"]],[\"invoke\",306,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9227875636482146304\"]],[\"invoke\",307,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:4503599627370496\"]],[\"invoke\",308,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9227875636482146304\"]],[\"invoke\",309,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:4503599627370496\"]],[\"invoke\",310,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:13826050856027422720\"]],[\"invoke\",311,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:4602678819172646912\"]],[\"invoke\",312,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:13826050856027422720\"]],[\"invoke\",313,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:4602678819172646912\"]],[\"invoke\",314,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:13830554455654793216\"]],[\"invoke\",315,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:4607182418800017408\"]],[\"invoke\",316,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:13830554455654793216\"]],[\"invoke\",317,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:4607182418800017408\"]],[\"invoke\",318,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:13842132293034192152\"]],[\"invoke\",319,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:4618760256179416344\"]],[\"invoke\",320,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:13842132293034192152\"]],[\"invoke\",321,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:4618760256179416344\"]],[\"invoke\",322,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:18442240474082181119\"]],[\"invoke\",323,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9218868437227405311\"]],[\"invoke\",324,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:18442240474082181119\"]],[\"invoke\",325,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9218868437227405311\"]],[\"invoke\",326,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:18442240474082181120\"]],[\"invoke\",327,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9218868437227405312\"]],[\"invoke\",328,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:18442240474082181120\"]],[\"invoke\",329,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9218868437227405312\"]],[\"invoke\",330,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:18444492273895866368\"]],[\"invoke\",331,0,\"copysign\",[\"f64:18444492273895866368\",\"f64:9221120237041090560\"]],[\"invoke\",332,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:18444492273895866368\"]],[\"invoke\",333,0,\"copysign\",[\"f64:9221120237041090560\",\"f64:9221120237041090560\"]],[\"invoke\",334,0,\"abs\",[\"f64:9223372036854775808\"]],[\"invoke\",335,0,\"abs\",[\"f64:0\"]],[\"invoke\",336,0,\"abs\",[\"f64:9223372036854775809\"]],[\"invoke\",337,0,\"abs\",[\"f64:1\"]],[\"invoke\",338,0,\"abs\",[\"f64:9227875636482146304\"]],[\"invoke\",339,0,\"abs\",[\"f64:4503599627370496\"]],[\"invoke\",340,0,\"abs\",[\"f64:13826050856027422720\"]],[\"invoke\",341,0,\"abs\",[\"f64:4602678819172646912\"]],[\"invoke\",342,0,\"abs\",[\"f64:13830554455654793216\"]],[\"invoke\",343,0,\"abs\",[\"f64:4607182418800017408\"]],[\"invoke\",344,0,\"abs\",[\"f64:13842132293034192152\"]],[\"invoke\",345,0,\"abs\",[\"f64:4618760256179416344\"]],[\"invoke\",346,0,\"abs\",[\"f64:18442240474082181119\"]],[\"invoke\",347,0,\"abs\",[\"f64:9218868437227405311\"]],[\"invoke\",348,0,\"abs\",[\"f64:18442240474082181120\"]],[\"invoke\",349,0,\"abs\",[\"f64:9218868437227405312\"]],[\"invoke\",350,0,\"abs\",[\"f64:18444492273895866368\"]],[\"invoke\",351,0,\"abs\",[\"f64:9221120237041090560\"]],[\"invoke\",352,0,\"neg\",[\"f64:9223372036854775808\"]],[\"invoke\",353,0,\"neg\",[\"f64:0\"]],[\"invoke\",354,0,\"neg\",[\"f64:9223372036854775809\"]],[\"invoke\",355,0,\"neg\",[\"f64:1\"]],[\"invoke\",356,0,\"neg\",[\"f64:9227875636482146304\"]],[\"invoke\",357,0,\"neg\",[\"f64:4503599627370496\"]],[\"invoke\",358,0,\"neg\",[\"f64:13826050856027422720\"]],[\"invoke\",359,0,\"neg\",[\"f64:4602678819172646912\"]],[\"invoke\",360,0,\"neg\",[\"f64:13830554455654793216\"]],[\"invoke\",361,0,\"neg\",[\"f64:4607182418800017408\"]],[\"invoke\",362,0,\"neg\",[\"f64:13842132293034192152\"]],[\"invoke\",363,0,\"neg\",[\"f64:4618760256179416344\"]],[\"invoke\",364,0,\"neg\",[\"f64:18442240474082181119\"]],[\"invoke\",365,0,\"neg\",[\"f64:9218868437227405311\"]],[\"invoke\",366,0,\"neg\",[\"f64:18442240474082181120\"]],[\"invoke\",367,0,\"neg\",[\"f64:9218868437227405312\"]],[\"invoke\",368,0,\"neg\",[\"f64:18444492273895866368\"]],[\"invoke\",369,0,\"neg\",[\"f64:9221120237041090560\"]],[\"file\",\"float_exprs.wast\"],[\"module\",6,6],[\"invoke\",11,0,\"f64.no_contraction\",[\"f64:13369472591878845359\",\"f64:7598224971858294334\",\"f64:7009968021366006149\"]],[\"invoke\",13,0,\"f64.no_contraction\",[\"f64:11159707324127586240\",\"f64:7011538096610110295\",\"f64:4140382893275160737\"]],[\"invoke\",15,0,\"f64.no_contraction\",[\"f64:9554523352352050493\",\"f64:18042841594766434431\",\"f64:4368037109959396445\"]],[\"module\",19,7],[\"invoke\",27,0,\"f32.no_fma\",[\"f32:838240978\",\"f32:2796592697\",\"f32:329493464\"]],[\"invoke\",29,0,\"f32.no_fma\",[\"f32:999635965\",\"f32:3403528619\",\"f32:3222888213\"]],[\"invoke\",31,0,\"f64.no_fma\",[\"f64:7118716943724900052\",\"f64:6546073043412611735\",\"f64:18275705786238687882\"]],[\"invoke\",33,0,\"f64.no_fma\",[\"f64:1362668175782178275\",\"f64:18385570095786966502\",\"f64:5677031731722859914\"]],[\"invoke\",35,0,\"f64.no_fma\",[\"f64:4843589256781277081\",\"f64:7695653093478086834\",\"f64:16938438850771988744\"]],[\"module\",40,8],[\"invoke\",48,0,\"f64.no_fold_add_zero\",[\"f64:9223372036854775808\"]],[\"module\",54,9],[\"invoke\",62,0,\"f64.no_fold_zero_sub\",[\"f64:0\"]],[\"module\",68,10],[\"module\",80,11],[\"invoke\",88,0,\"f32.no_fold_mul_zero\",[\"f32:3212836864\"]],[\"invoke\",91,0,\"f64.no_fold_mul_zero\",[\"f64:9223372036854775808\"]],[\"invoke\",93,0,\"f64.no_fold_mul_zero\",[\"f64:13835058055282163712\"]],[\"module\",99,12],[\"module\",111,13],[\"invoke\",118,0,\"f32.no_fold_zero_div\",[\"f32:0\"]],[\"invoke\",120,0,\"f32.no_fold_zero_div\",[\"f32:2143289344\"]],[\"invoke\",123,0,\"f64.no_fold_zero_div\",[\"f64:9223372036854775808\"]],[\"module\",129,14],[\"module\",141,15],[\"module\",153,16],[\"module\",165,17],[\"module\",177,18],[\"invoke\",184,0,\"f32.no_fold_eq_self\",[\"f32:2143289344\"]],[\"module\",189,19],[\"invoke\",196,0,\"f32.no_fold_ne_self\",[\"f32:2143289344\"]],[\"module\",201,20],[\"invoke\",208,0,\"f32.no_fold_sub_self\",[\"f32:2139095040\"]],[\"invoke\",210,0,\"f64.no_fold_sub_self\",[\"f64:9218868437227405312\"]],[\"module\",215,21],[\"invoke\",222,0,\"f32.no_fold_div_self\",[\"f32:2139095040\"]],[\"invoke\",224,0,\"f32.no_fold_div_self\",[\"f32:0\"]],[\"invoke\",226,0,\"f64.no_fold_div_self\",[\"f64:9218868437227405312\"]],[\"invoke\",228,0,\"f64.no_fold_div_self\",[\"f64:0\"]],[\"module\",233,22],[\"invoke\",240,0,\"f32.no_fold_div_3\",[\"f32:3634023955\"]],[\"invoke\",242,0,\"f32.no_fold_div_3\",[\"f32:2517965963\"]],[\"invoke\",243,0,\"f32.no_fold_div_3\",[\"f32:2173683100\"]],[\"invoke\",245,0,\"f64.no_fold_div_3\",[\"f64:16679796490173820099\"]],[\"invoke\",247,0,\"f64.no_fold_div_3\",[\"f64:674365394458900388\"]],[\"invoke\",249,0,\"f64.no_fold_div_3\",[\"f64:6476267216527259981\"]],[\"module\",253,23],[\"invoke\",261,0,\"f32.no_factor\",[\"f32:3168433147\",\"f32:1028017286\",\"f32:3141035521\"]],[\"invoke\",263,0,\"f32.no_factor\",[\"f32:2684117842\",\"f32:369386499\",\"f32:2061166438\"]],[\"invoke\",265,0,\"f64.no_factor\",[\"f64:2698691837980592503\",\"f64:2529920934327896545\",\"f64:12819783413251458936\"]],[\"invoke\",267,0,\"f64.no_factor\",[\"f64:12326480769054961745\",\"f64:12563546453737163926\",\"f64:15990519985875741037\"]],[\"invoke\",269,0,\"f64.no_factor\",[\"f64:1871759566187673434\",\"f64:2002968319587025494\",\"f64:16033202089880281080\"]],[\"module\",273,24],[\"invoke\",281,0,\"f32.no_distribute\",[\"f32:3168433147\",\"f32:1028017286\",\"f32:3141035521\"]],[\"invoke\",283,0,\"f32.no_distribute\",[\"f32:2684117842\",\"f32:369386499\",\"f32:2061166438\"]],[\"invoke\",285,0,\"f64.no_distribute\",[\"f64:2698691837980592503\",\"f64:2529920934327896545\",\"f64:12819783413251458936\"]],[\"invoke\",287,0,\"f64.no_distribute\",[\"f64:12326480769054961745\",\"f64:12563546453737163926\",\"f64:15990519985875741037\"]],[\"invoke\",288,0,\"f64.no_distribute\",[\"f64:12532477544855171977\",\"f64:3439526350000314825\",\"f64:12694541248380731909\"]],[\"module\",293,25],[\"invoke\",300,0,\"f32.no_regroup_div_mul\",[\"f32:2249624147\",\"f32:2678828342\",\"f32:95319815\"]],[\"invoke\",302,0,\"f32.no_regroup_div_mul\",[\"f32:3350590135\",\"f32:3042588643\",\"f32:2186448635\"]],[\"invoke\",304,0,\"f32.no_regroup_div_mul\",[\"f32:2011387707\",\"f32:1274956446\",\"f32:3811596788\"]],[\"invoke\",306,0,\"f64.no_regroup_div_mul\",[\"f64:6126139291059848917\",\"f64:2596039250849921421\",\"f64:17423258659719899654\"]],[\"invoke\",308,0,\"f64.no_regroup_div_mul\",[\"f64:15994259208199847538\",\"f64:16584156163346075677\",\"f64:17596923907238870430\"]],[\"module\",313,26],[\"invoke\",320,0,\"f32.no_regroup_mul_div\",[\"f32:2249624147\",\"f32:2678828342\",\"f32:95319815\"]],[\"invoke\",322,0,\"f32.no_regroup_mul_div\",[\"f32:3350590135\",\"f32:3042588643\",\"f32:2186448635\"]],[\"invoke\",324,0,\"f32.no_regroup_mul_div\",[\"f32:2011387707\",\"f32:1274956446\",\"f32:3811596788\"]],[\"invoke\",326,0,\"f64.no_regroup_mul_div\",[\"f64:6126139291059848917\",\"f64:2596039250849921421\",\"f64:17423258659719899654\"]],[\"invoke\",328,0,\"f64.no_regroup_mul_div\",[\"f64:15994259208199847538\",\"f64:16584156163346075677\",\"f64:17596923907238870430\"]],[\"module\",333,27],[\"invoke\",340,0,\"f32.no_reassociate_add\",[\"f32:3585064686\",\"f32:1354934024\",\"f32:3612934982\",\"f32:3557837641\"]],[\"invoke\",342,0,\"f32.no_reassociate_add\",[\"f32:3506363549\",\"f32:3562765939\",\"f32:1440782572\",\"f32:1388583643\"]],[\"invoke\",343,0,\"f32.no_reassociate_add\",[\"f32:1460378878\",\"f32:1481791683\",\"f32:3506843934\",\"f32:1493913729\"]],[\"invoke\",345,0,\"f64.no_reassociate_add\",[\"f64:17619937326421449126\",\"f64:8424880666975634327\",\"f64:8461713040394112626\",\"f64:17692076622886930107\"]],[\"invoke\",347,0,\"f64.no_reassociate_add\",[\"f64:15816220208145029204\",\"f64:6443786499090728432\",\"f64:15798639273395365185\",\"f64:6395820899158300605\"]],[\"invoke\",349,0,\"f64.no_reassociate_add\",[\"f64:16720963389015391005\",\"f64:16597092572968550980\",\"f64:7518944085377596897\",\"f64:16733407756820198530\"]],[\"module\",353,28],[\"invoke\",361,0,\"f32.no_reassociate_mul\",[\"f32:598526039\",\"f32:4072603010\",\"f32:2166864805\",\"f32:3802968051\"]],[\"invoke\",363,0,\"f32.no_reassociate_mul\",[\"f32:191948150\",\"f32:1717012201\",\"f32:3682645872\",\"f32:3713382507\"]],[\"invoke\",365,0,\"f64.no_reassociate_mul\",[\"f64:10830726381612138752\",\"f64:18293529276079591087\",\"f64:12137662286027993114\",\"f64:16821646709291069775\"]],[\"invoke\",367,0,\"f64.no_reassociate_mul\",[\"f64:2352911459797566465\",\"f64:17379873157362463143\",\"f64:1179129869275935356\",\"f64:14228398113747850351\"]],[\"invoke\",369,0,\"f64.no_reassociate_mul\",[\"f64:3553622953022765407\",\"f64:1044040287824900408\",\"f64:17112762794520509437\",\"f64:11134095486440145773\"]],[\"module\",373,29],[\"invoke\",381,0,\"f32.no_fold_div_0\",[\"f32:3212836864\"]],[\"invoke\",383,0,\"f32.no_fold_div_0\",[\"f32:4286578688\"]],[\"invoke\",385,0,\"f32.no_fold_div_0\",[\"f32:2147483648\"]],[\"invoke\",388,0,\"f64.no_fold_div_0\",[\"f64:4607182418800017408\"]],[\"invoke\",389,0,\"f64.no_fold_div_0\",[\"f64:13830554455654793216\"]],[\"invoke\",391,0,\"f64.no_fold_div_0\",[\"f64:18442240474082181120\"]],[\"invoke\",393,0,\"f64.no_fold_div_0\",[\"f64:9223372036854775808\"]],[\"module\",399,30],[\"invoke\",406,0,\"f32.no_fold_div_neg0\",[\"f32:1065353216\"]],[\"invoke\",408,0,\"f32.no_fold_div_neg0\",[\"f32:2139095040\"]],[\"invoke\",410,0,\"f32.no_fold_div_neg0\",[\"f32:0\"]],[\"invoke\",413,0,\"f32.no_fold_div_neg0\",[\"f32:2143289344\"]],[\"invoke\",415,0,\"f64.no_fold_div_neg0\",[\"f64:13830554455654793216\"]],[\"invoke\",417,0,\"f64.no_fold_div_neg0\",[\"f64:18442240474082181120\"]],[\"invoke\",419,0,\"f64.no_fold_div_neg0\",[\"f64:9223372036854775808\"]],[\"module\",425,31],[\"invoke\",434,0,\"f32.no_fold_to_hypot\",[\"f32:392264092\",\"f32:497028527\"]],[\"invoke\",436,0,\"f32.no_fold_to_hypot\",[\"f32:2261577829\",\"f32:2641790518\"]],[\"invoke\",438,0,\"f32.no_fold_to_hypot\",[\"f32:432505039\",\"f32:2618036612\"]],[\"invoke\",439,0,\"f64.no_fold_to_hypot\",[\"f64:1743351192697472785\",\"f64:2202602366606243153\"]],[\"invoke\",441,0,\"f64.no_fold_to_hypot\",[\"f64:2195337108264055819\",\"f64:10384237061545402288\"]],[\"invoke\",443,0,\"f64.no_fold_to_hypot\",[\"f64:1591440107418864392\",\"f64:11515806374387309036\"]],[\"module\",447,32],[\"invoke\",453,0,\"f32.no_approximate_reciprocal\",[\"f32:2138280080\"]],[\"invoke\",455,0,\"f32.no_approximate_reciprocal\",[\"f32:1705936409\"]],[\"module\",460,33],[\"invoke\",467,0,\"f32.no_approximate_reciprocal_sqrt\",[\"f32:708147349\"]],[\"invoke\",469,0,\"f32.no_approximate_reciprocal_sqrt\",[\"f32:517799246\"]],[\"invoke\",471,0,\"f32.no_approximate_reciprocal_sqrt\",[\"f32:347001813\"]],[\"invoke\",474,0,\"f64.no_fuse_reciprocal_sqrt\",[\"f64:6008428610859539631\"]],[\"invoke\",476,0,\"f64.no_fuse_reciprocal_sqrt\",[\"f64:7616219057857077123\"]],[\"module\",481,34],[\"invoke\",486,0,\"f32.no_approximate_sqrt_reciprocal\",[\"f32:1574069443\"]],[\"invoke\",488,0,\"f32.no_approximate_sqrt_reciprocal\",[\"f32:1644769121\"]],[\"invoke\",490,0,\"f32.no_approximate_sqrt_reciprocal\",[\"f32:1940205041\"]],[\"module\",494,35],[\"invoke\",505,0,\"i32.no_fold_f32_s\",[\"i32:16777216\"]],[\"invoke\",507,0,\"i32.no_fold_f32_s\",[\"i32:4026531856\"]],[\"invoke\",510,0,\"i32.no_fold_f32_u\",[\"i32:16777217\"]],[\"invoke\",513,0,\"i64.no_fold_f64_s\",[\"i64:9007199254740992\"]],[\"invoke\",515,0,\"i64.no_fold_f64_s\",[\"i64:17293822569102705664\"]],[\"invoke\",518,0,\"i64.no_fold_f64_u\",[\"i64:9007199254740993\"]],[\"module\",523,36],[\"invoke\",530,0,\"f32.no_fold_add_sub\",[\"f32:677030386\",\"f32:2998136214\"]],[\"invoke\",532,0,\"f32.no_fold_add_sub\",[\"f32:3908960888\",\"f32:4063404061\"]],[\"invoke\",534,0,\"f32.no_fold_add_sub\",[\"f32:2307650739\",\"f32:2511328013\"]],[\"invoke\",537,0,\"f64.no_fold_add_sub\",[\"f64:2152218683357821298\",\"f64:2238360073507307376\"]],[\"invoke\",539,0,\"f64.no_fold_add_sub\",[\"f64:12298280617237492384\",\"f64:3233965342858558382\"]],[\"module\",544,37],[\"invoke\",551,0,\"f32.no_fold_sub_add\",[\"f32:3291029084\",\"f32:1137280182\"]],[\"invoke\",553,0,\"f32.no_fold_sub_add\",[\"f32:1285466516\",\"f32:1361849144\"]],[\"invoke\",554,0,\"f32.no_fold_sub_add\",[\"f32:740009747\",\"f32:2989707904\"]],[\"invoke\",557,0,\"f64.no_fold_sub_add\",[\"f64:5758126085282503565\",\"f64:14997141603873875659\"]],[\"invoke\",559,0,\"f64.no_fold_sub_add\",[\"f64:5738179408840599949\",\"f64:15186085143903012996\"]],[\"invoke\",561,0,\"f64.no_fold_sub_add\",[\"f64:2955729038738127538\",\"f64:12208627806665035010\"]],[\"module\",565,38],[\"invoke\",573,0,\"f32.no_fold_mul_div\",[\"f32:2995104604\",\"f32:178524966\"]],[\"invoke\",575,0,\"f32.no_fold_mul_div\",[\"f32:1507152519\",\"f32:3723483599\"]],[\"invoke\",578,0,\"f64.no_fold_mul_div\",[\"f64:10062123074470382106\",\"f64:12910565991996555404\"]],[\"invoke\",580,0,\"f64.no_fold_mul_div\",[\"f64:14905228263410157971\",\"f64:11346251643264732732\"]],[\"invoke\",582,0,\"f64.no_fold_mul_div\",[\"f64:16807035693954817236\",\"f64:12360222454864961326\"]],[\"module\",586,39],[\"invoke\",594,0,\"f32.no_fold_div_mul\",[\"f32:2777664539\",\"f32:3062588018\"]],[\"invoke\",596,0,\"f32.no_fold_div_mul\",[\"f32:2593594703\",\"f32:3709508810\"]],[\"invoke\",599,0,\"f64.no_fold_div_mul\",[\"f64:665690489208775809\",\"f64:14660005164454413124\"]],[\"invoke\",601,0,\"f64.no_fold_div_mul\",[\"f64:13179336828827425934\",\"f64:6536345148565138764\"]],[\"invoke\",602,0,\"f64.no_fold_div_mul\",[\"f64:12582623625647949669\",\"f64:15106746174896642041\"]],[\"module\",607,40],[\"invoke\",614,0,\"f32.no_fold_div2_mul2\",[\"f32:16777215\"]],[\"module\",619,41],[\"invoke\",624,0,\"no_fold_demote_promote\",[\"f64:13235495337234861917\"]],[\"invoke\",626,0,\"no_fold_demote_promote\",[\"f64:5090364081358261697\"]],[\"invoke\",628,0,\"no_fold_demote_promote\",[\"f64:5076240020598306430\"]],[\"module\",633,42],[\"invoke\",640,0,\"no_fold_promote_demote\",[\"f32:2147483648\"]],[\"invoke\",642,0,\"no_fold_promote_demote\",[\"f32:2147483649\"]],[\"invoke\",644,0,\"no_fold_promote_demote\",[\"f32:2155872255\"]],[\"invoke\",646,0,\"no_fold_promote_demote\",[\"f32:2155872256\"]],[\"invoke\",648,0,\"no_fold_promote_demote\",[\"f32:4286578687\"]],[\"invoke\",650,0,\"no_fold_promote_demote\",[\"f32:4286578688\"]],[\"module\",655,43],[\"invoke\",663,0,\"no_demote_mixed_add\",[\"f64:4773927428111915216\",\"f32:1387972204\"]],[\"invoke\",665,0,\"no_demote_mixed_add\",[\"f64:13740716732336801211\",\"f32:822392741\"]],[\"invoke\",666,0,\"no_demote_mixed_add\",[\"f64:13742514716462174325\",\"f32:2870112826\"]],[\"invoke\",669,0,\"no_demote_mixed_add_commuted\",[\"f32:1387972204\",\"f64:4773927428111915216\"]],[\"invoke\",671,0,\"no_demote_mixed_add_commuted\",[\"f32:822392741\",\"f64:13740716732336801211\"]],[\"module\",677,44],[\"invoke\",684,0,\"no_demote_mixed_sub\",[\"f64:4979303437048015281\",\"f32:1583535740\"]],[\"invoke\",686,0,\"no_demote_mixed_sub\",[\"f64:13860263758943608426\",\"f32:969848030\"]],[\"invoke\",688,0,\"no_demote_mixed_sub\",[\"f64:4673175763235896759\",\"f32:1198952676\"]],[\"invoke\",691,0,\"no_demote_mixed_sub_commuted\",[\"f32:1214924370\",\"f64:13967600632962086462\"]],[\"invoke\",693,0,\"no_demote_mixed_sub_commuted\",[\"f32:472962692\",\"f64:4364064588997139903\"]],[\"module\",699,45],[\"invoke\",706,0,\"no_demote_mixed_mul\",[\"f64:3700298049681794165\",\"f32:2017642079\"]],[\"invoke\",708,0,\"no_demote_mixed_mul\",[\"f64:5404042108537284972\",\"f32:592745128\"]],[\"invoke\",710,0,\"no_demote_mixed_mul\",[\"f64:3845043516226476604\",\"f32:1468885281\"]],[\"invoke\",713,0,\"no_demote_mixed_mul_commuted\",[\"f32:169311251\",\"f64:5240203039857786645\"]],[\"invoke\",714,0,\"no_demote_mixed_mul_commuted\",[\"f32:592745128\",\"f64:5404042108537284972\"]],[\"invoke\",716,0,\"no_demote_mixed_mul_commuted\",[\"f32:1468885281\",\"f64:3845043516226476604\"]],[\"module\",721,46],[\"invoke\",729,0,\"no_demote_mixed_div\",[\"f64:3919260277508722072\",\"f32:379509872\"]],[\"invoke\",731,0,\"no_demote_mixed_div\",[\"f64:3987118416164120709\",\"f32:327692725\"]],[\"invoke\",734,0,\"no_demote_mixed_div_commuted\",[\"f32:451659502\",\"f64:4418811329667688930\"]],[\"invoke\",736,0,\"no_demote_mixed_div_commuted\",[\"f32:737399826\",\"f64:3994494799108309244\"]],[\"invoke\",738,0,\"no_demote_mixed_div_commuted\",[\"f32:456887038\",\"f64:3717438582804640027\"]],[\"module\",742,47],[\"invoke\",778,0,\"f32.i32.no_fold_trunc_s_convert_s\",[\"f32:3217031168\"]],[\"invoke\",780,0,\"f32.i32.no_fold_trunc_u_convert_s\",[\"f32:3204448256\"]],[\"invoke\",782,0,\"f32.i32.no_fold_trunc_s_convert_u\",[\"f32:3217031168\"]],[\"invoke\",784,0,\"f32.i32.no_fold_trunc_u_convert_u\",[\"f32:3204448256\"]],[\"invoke\",787,0,\"f64.i32.no_fold_trunc_s_convert_s\",[\"f64:13832806255468478464\"]],[\"invoke\",789,0,\"f64.i32.no_fold_trunc_u_convert_s\",[\"f64:13826050856027422720\"]],[\"invoke\",790,0,\"f64.i32.no_fold_trunc_s_convert_u\",[\"f64:4609434218613702656\"]],[\"invoke\",792,0,\"f64.i32.no_fold_trunc_u_convert_u\",[\"f64:4609434218613702656\"]],[\"invoke\",795,0,\"f32.i64.no_fold_trunc_s_convert_s\",[\"f32:1069547520\"]],[\"invoke\",797,0,\"f32.i64.no_fold_trunc_u_convert_s\",[\"f32:1069547520\"]],[\"invoke\",799,0,\"f32.i64.no_fold_trunc_s_convert_u\",[\"f32:1069547520\"]],[\"invoke\",801,0,\"f32.i64.no_fold_trunc_u_convert_u\",[\"f32:1069547520\"]],[\"invoke\",804,0,\"f64.i64.no_fold_trunc_s_convert_s\",[\"f64:4609434218613702656\"]],[\"invoke\",806,0,\"f64.i64.no_fold_trunc_u_convert_s\",[\"f64:4609434218613702656\"]],[\"invoke\",808,0,\"f64.i64.no_fold_trunc_s_convert_u\",[\"f64:4609434218613702656\"]],[\"invoke\",810,0,\"f64.i64.no_fold_trunc_u_convert_u\",[\"f64:4609434218613702656\"]],[\"module\",817,48],[\"invoke\",838,0,\"init\",[\"i32:0\",\"f32:1097963930\"]],[\"invoke\",840,0,\"init\",[\"i32:8\",\"f32:1098173645\"]],[\"invoke\",842,0,\"check\",[\"i32:0\"]],[\"invoke\",843,0,\"check\",[\"i32:4\"]],[\"invoke\",845,0,\"check\",[\"i32:12\"]],[\"invoke\",847,0,\"check\",[\"i32:0\"]],[\"invoke\",849,0,\"check\",[\"i32:8\"]],[\"module\",852,49],[\"invoke\",873,0,\"init\",[\"i32:0\",\"f64:4624690162351420211\"]],[\"invoke\",875,0,\"init\",[\"i32:16\",\"f64:4624802752342104474\"]],[\"invoke\",877,0,\"check\",[\"i32:0\"]],[\"invoke\",879,0,\"check\",[\"i32:16\"]],[\"invoke\",881,0,\"run\",[\"i32:32\",\"f64:4613937818241073152\"]],[\"invoke\",883,0,\"check\",[\"i32:8\"]],[\"invoke\",885,0,\"check\",[\"i32:24\"]],[\"module\",889,50],[\"invoke\",902,0,\"f32.ult\",[\"f32:1073741824\",\"f32:1073741824\"]],[\"invoke\",904,0,\"f32.ult\",[\"f32:1073741824\",\"f32:2143289344\"]],[\"invoke\",905,0,\"f32.ule\",[\"f32:1077936128\",\"f32:1073741824\"]],[\"invoke\",907,0,\"f32.ule\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",909,0,\"f32.ugt\",[\"f32:1077936128\",\"f32:1073741824\"]],[\"invoke\",911,0,\"f32.ugt\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",913,0,\"f32.uge\",[\"f32:1077936128\",\"f32:1073741824\"]],[\"invoke\",915,0,\"f32.uge\",[\"f32:1073741824\",\"f32:1077936128\"]],[\"invoke\",917,0,\"f64.ult\",[\"f64:4613937818241073152\",\"f64:4611686018427387904\"]],[\"invoke\",919,0,\"f64.ult\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",921,0,\"f64.ule\",[\"f64:4613937818241073152\",\"f64:4611686018427387904\"]],[\"invoke\",923,0,\"f64.ule\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",925,0,\"f64.ugt\",[\"f64:4613937818241073152\",\"f64:4611686018427387904\"]],[\"invoke\",927,0,\"f64.ugt\",[\"f64:4611686018427387904\",\"f64:4613937818241073152\"]],[\"invoke\",929,0,\"f64.uge\",[\"f64:4613937818241073152\",\"f64:4611686018427387904\"]],[\"invoke\",930,0,\"f64.uge\",[\"f64:4611686018427387904\",\"f64:4611686018427387904\"]],[\"invoke\",932,0,\"f64.uge\",[\"f64:4611686018427387904\",\"f64:9221120237041090560\"]],[\"module\",936,51],[\"invoke\",949,0,\"f32.no_fold_lt_select\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",951,0,\"f32.no_fold_lt_select\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",953,0,\"f32.no_fold_le_select\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",955,0,\"f32.no_fold_le_select\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",957,0,\"f32.no_fold_gt_select\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",959,0,\"f32.no_fold_gt_select\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",961,0,\"f32.no_fold_ge_select\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",963,0,\"f32.no_fold_ge_select\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",965,0,\"f64.no_fold_lt_select\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",967,0,\"f64.no_fold_lt_select\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",969,0,\"f64.no_fold_le_select\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",970,0,\"f64.no_fold_le_select\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",972,0,\"f64.no_fold_gt_select\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",974,0,\"f64.no_fold_gt_select\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",976,0,\"f64.no_fold_ge_select\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",978,0,\"f64.no_fold_ge_select\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"module\",983,52],[\"invoke\",1027,0,\"f32.no_fold_lt_if\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1029,0,\"f32.no_fold_lt_if\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1031,0,\"f32.no_fold_le_if\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1033,0,\"f32.no_fold_le_if\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1035,0,\"f32.no_fold_gt_if\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1037,0,\"f32.no_fold_gt_if\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1039,0,\"f32.no_fold_ge_if\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",1041,0,\"f32.no_fold_ge_if\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1042,0,\"f32.no_fold_ge_if\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",1044,0,\"f64.no_fold_lt_if\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1046,0,\"f64.no_fold_lt_if\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1048,0,\"f64.no_fold_le_if\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1050,0,\"f64.no_fold_le_if\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1052,0,\"f64.no_fold_gt_if\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1054,0,\"f64.no_fold_gt_if\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"invoke\",1056,0,\"f64.no_fold_ge_if\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",1058,0,\"f64.no_fold_ge_if\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"module\",1062,53],[\"invoke\",1076,0,\"f32.no_fold_lt_select_to_abs\",[\"f32:0\"]],[\"invoke\",1079,0,\"f32.no_fold_le_select_to_abs\",[\"f32:4290772992\"]],[\"invoke\",1081,0,\"f32.no_fold_le_select_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1084,0,\"f32.no_fold_gt_select_to_abs\",[\"f32:0\"]],[\"invoke\",1085,0,\"f32.no_fold_gt_select_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1088,0,\"f32.no_fold_ge_select_to_abs\",[\"f32:0\"]],[\"invoke\",1091,0,\"f64.no_fold_lt_select_to_abs\",[\"f64:18444492273895866368\"]],[\"invoke\",1093,0,\"f64.no_fold_lt_select_to_abs\",[\"f64:9223372036854775808\"]],[\"invoke\",1096,0,\"f64.no_fold_le_select_to_abs\",[\"f64:0\"]],[\"invoke\",1099,0,\"f64.no_fold_gt_select_to_abs\",[\"f64:18444492273895866368\"]],[\"invoke\",1101,0,\"f64.no_fold_gt_select_to_abs\",[\"f64:9223372036854775808\"]],[\"invoke\",1104,0,\"f64.no_fold_ge_select_to_abs\",[\"f64:0\"]],[\"module\",1109,54],[\"invoke\",1154,0,\"f32.no_fold_lt_if_to_abs\",[\"f32:4290772992\"]],[\"invoke\",1156,0,\"f32.no_fold_lt_if_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1159,0,\"f32.no_fold_le_if_to_abs\",[\"f32:0\"]],[\"invoke\",1162,0,\"f32.no_fold_gt_if_to_abs\",[\"f32:4290772992\"]],[\"invoke\",1164,0,\"f32.no_fold_gt_if_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1166,0,\"f32.no_fold_ge_if_to_abs\",[\"f32:4290772992\"]],[\"invoke\",1168,0,\"f32.no_fold_ge_if_to_abs\",[\"f32:2147483648\"]],[\"invoke\",1171,0,\"f64.no_fold_lt_if_to_abs\",[\"f64:0\"]],[\"invoke\",1174,0,\"f64.no_fold_le_if_to_abs\",[\"f64:18444492273895866368\"]],[\"invoke\",1176,0,\"f64.no_fold_le_if_to_abs\",[\"f64:9223372036854775808\"]],[\"invoke\",1179,0,\"f64.no_fold_gt_if_to_abs\",[\"f64:0\"]],[\"invoke\",1182,0,\"f64.no_fold_ge_if_to_abs\",[\"f64:18444492273895866368\"]],[\"invoke\",1184,0,\"f64.no_fold_ge_if_to_abs\",[\"f64:9223372036854775808\"]],[\"module\",1189,55],[\"invoke\",1199,0,\"f64.incorrect_correction\",[]],[\"module\",1204,56],[\"module\",1223,57],[\"invoke\",1240,0,\"calculate\",[]],[\"module\",1245,58],[\"module\",1256,59],[\"invoke\",1262,0,\"llvm_pr27153\",[\"i32:33554434\"]],[\"module\",1267,60],[\"module\",1284,61],[\"invoke\",1298,0,\"thepast0\",[\"f64:9007199254740992\",\"f64:4607182418800017407\",\"f64:4611686018427387904\",\"f64:4602678819172646912\"]],[\"invoke\",1300,0,\"thepast2\",[\"f32:16777216\",\"f32:1056964608\",\"f32:1065353216\"]],[\"module\",1305,62],[\"invoke\",1311,0,\"inverse\",[\"f32:1119879168\"]],[\"module\",1316,63],[\"invoke\",1327,0,\"f64_sqrt_minus_2\",[\"f64:4616189618054758400\"]],[\"module\",1331,64],[\"invoke\",1340,0,\"f32.no_fold_recip_recip\",[\"f32:426844452\"]],[\"invoke\",1342,0,\"f32.no_fold_recip_recip\",[\"f32:3253941441\"]],[\"invoke\",1345,0,\"f32.no_fold_recip_recip\",[\"f32:2147483648\"]],[\"invoke\",1347,0,\"f32.no_fold_recip_recip\",[\"f32:4286578688\"]],[\"invoke\",1350,0,\"f64.no_fold_recip_recip\",[\"f64:14500888369201570768\"]],[\"invoke\",1352,0,\"f64.no_fold_recip_recip\",[\"f64:5359183527603521526\"]],[\"invoke\",1354,0,\"f64.no_fold_recip_recip\",[\"f64:8671785631545870379\"]],[\"invoke\",1357,0,\"f64.no_fold_recip_recip\",[\"f64:0\"]],[\"invoke\",1359,0,\"f64.no_fold_recip_recip\",[\"f64:9218868437227405312\"]],[\"module\",1363,65],[\"invoke\",1374,0,\"f32.no_algebraic_factoring\",[\"f32:3415653214\",\"f32:1274676302\"]],[\"invoke\",1375,0,\"f32.no_algebraic_factoring\",[\"f32:1446924633\",\"f32:3607373982\"]],[\"invoke\",1377,0,\"f32.no_algebraic_factoring\",[\"f32:3278732464\",\"f32:3379389272\"]],[\"invoke\",1380,0,\"f64.no_algebraic_factoring\",[\"f64:2911983657790464931\",\"f64:2814431682419759911\"]],[\"invoke\",1382,0,\"f64.no_algebraic_factoring\",[\"f64:14639789466354372633\",\"f64:5456963169336729236\"]],[\"module\",1387,66],[\"invoke\",1397,0,\"f32.no_algebraic_factoring\",[\"f32:684135946\",\"f32:744319693\"]],[\"invoke\",1399,0,\"f32.no_algebraic_factoring\",[\"f32:968841772\",\"f32:3106497100\"]],[\"invoke\",1401,0,\"f32.no_algebraic_factoring\",[\"f32:1498230729\",\"f32:3650453580\"]],[\"invoke\",1404,0,\"f64.no_algebraic_factoring\",[\"f64:15222970140370015722\",\"f64:15325207139996136125\"]],[\"invoke\",1406,0,\"f64.no_algebraic_factoring\",[\"f64:14653559129294038194\",\"f64:14581996260169223461\"]],[\"module\",1412,67],[\"invoke\",1445,0,\"f32.simple_x4_sum\",[\"i32:0\",\"i32:16\",\"i32:32\"]],[\"invoke\",1447,0,\"f32.load\",[\"i32:36\"]],[\"invoke\",1449,0,\"f32.load\",[\"i32:44\"]],[\"module\",1451,68],[\"invoke\",1485,0,\"f64.load\",[\"i32:64\"]],[\"invoke\",1486,0,\"f64.load\",[\"i32:72\"]],[\"invoke\",1488,0,\"f64.load\",[\"i32:88\"]],[\"module\",1493,69],[\"invoke\",1585,0,\"f32.plain_sum\",[\"i32:0\",\"i32:256\"]],[\"module\",1587,70],[\"invoke\",1636,0,\"f64.plain_sum\",[\"i32:0\",\"i32:256\"]],[\"module\",1640,71],[\"invoke\",1649,0,\"f32.no_fold_neg_sub\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1651,0,\"f32.no_fold_neg_sub\",[\"f32:0\",\"f32:0\"]],[\"invoke\",1654,0,\"f64.no_fold_neg_sub\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",1656,0,\"f64.no_fold_neg_sub\",[\"f64:0\",\"f64:0\"]],[\"module\",1660,72],[\"invoke\",1669,0,\"f32.no_fold_neg_add\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1671,0,\"f32.no_fold_neg_add\",[\"f32:0\",\"f32:0\"]],[\"invoke\",1674,0,\"f64.no_fold_neg_add\",[\"f64:0\",\"f64:9223372036854775808\"]],[\"invoke\",1676,0,\"f64.no_fold_neg_add\",[\"f64:0\",\"f64:0\"]],[\"module\",1680,73],[\"invoke\",1689,0,\"f32.no_fold_add_neg_neg\",[\"f32:0\",\"f32:2147483648\"]],[\"invoke\",1690,0,\"f32.no_fold_add_neg_neg\",[\"f32:2147483648\",\"f32:0\"]],[\"invoke\",1693,0,\"f64.no_fold_add_neg_neg\",[\"f64:9223372036854775808\",\"f64:9223372036854775808\"]],[\"invoke\",1695,0,\"f64.no_fold_add_neg_neg\",[\"f64:9223372036854775808\",\"f64:0\"]],[\"module\",1700,74],[\"invoke\",1708,0,\"f32.no_fold_add_neg\",[\"f32:0\"]],[\"invoke\",1710,0,\"f32.no_fold_add_neg\",[\"f32:2139095040\"]],[\"invoke\",1713,0,\"f64.no_fold_add_neg\",[\"f64:0\"]],[\"invoke\",1715,0,\"f64.no_fold_add_neg\",[\"f64:9218868437227405312\"]],[\"module\",1720,75],[\"invoke\",1734,0,\"f32.no_fold_6x_via_add\",[\"f32:4046243078\"]],[\"invoke\",1736,0,\"f32.no_fold_6x_via_add\",[\"f32:419462401\"]],[\"invoke\",1738,0,\"f32.no_fold_6x_via_add\",[\"f32:3883931973\"]],[\"invoke\",1741,0,\"f64.no_fold_6x_via_add\",[\"f64:11424134044545165748\"]],[\"invoke\",1743,0,\"f64.no_fold_6x_via_add\",[\"f64:7991451501228919438\"]],[\"module\",1749,76],[\"invoke\",1757,0,\"f32.no_fold_div_div\",[\"f32:3875242260\",\"f32:3086869257\",\"f32:3301317576\"]],[\"invoke\",1758,0,\"f32.no_fold_div_div\",[\"f32:485052055\",\"f32:1996083391\",\"f32:2276616712\"]],[\"invoke\",1760,0,\"f32.no_fold_div_div\",[\"f32:3101818893\",\"f32:4258133430\",\"f32:2855958950\"]],[\"invoke\",1763,0,\"f64.no_fold_div_div\",[\"f64:6128077243319875447\",\"f64:7240092044185667120\",\"f64:10312472494987686942\"]],[\"invoke\",1765,0,\"f64.no_fold_div_div\",[\"f64:13843263185226986279\",\"f64:17796742619038211051\",\"f64:5375701731263473827\"]],[\"invoke\",1767,0,\"f64.no_fold_div_div\",[\"f64:9835707468114203513\",\"f64:1924400690116523912\",\"f64:13208934041167870811\"]],[\"module\",1773,77],[\"invoke\",1782,0,\"f32.no_fold_mul_divs\",[\"f32:4068974897\",\"f32:1276265036\",\"f32:930821438\",\"f32:1044692964\"]],[\"invoke\",1784,0,\"f32.no_fold_mul_divs\",[\"f32:4135236702\",\"f32:787270424\",\"f32:932959293\",\"f32:1724950821\"]],[\"invoke\",1787,0,\"f64.no_fold_mul_divs\",[\"f64:10143060558527560466\",\"f64:11745059379675007839\",\"f64:16295837305232663584\",\"f64:5444961058358534642\"]],[\"invoke\",1789,0,\"f64.no_fold_mul_divs\",[\"f64:15220380342429201729\",\"f64:14697937818549468616\",\"f64:13203624158275174657\",\"f64:17131104131485469546\"]],[\"invoke\",1791,0,\"f64.no_fold_mul_divs\",[\"f64:4529089342618677929\",\"f64:3361245300043094097\",\"f64:1815899012046749567\",\"f64:15418396504351552390\"]],[\"module\",1795,78],[\"invoke\",1804,0,\"f32.no_fold_add_divs\",[\"f32:2690073844\",\"f32:2809448479\",\"f32:3608905030\"]],[\"invoke\",1806,0,\"f32.no_fold_add_divs\",[\"f32:2365787800\",\"f32:245111369\",\"f32:3952003433\"]],[\"invoke\",1809,0,\"f64.no_fold_add_divs\",[\"f64:15770585325769044278\",\"f64:6564157675451289455\",\"f64:8712254759989822359\"]],[\"invoke\",1810,0,\"f64.no_fold_add_divs\",[\"f64:14069844870254671283\",\"f64:4634122757084803708\",\"f64:9524897388132352235\"]],[\"invoke\",1812,0,\"f64.no_fold_add_divs\",[\"f64:6019502660029506228\",\"f64:15316513033818836241\",\"f64:4039967192182502935\"]],[\"module\",1817,79],[\"invoke\",1825,0,\"f32.no_fold_sqrt_square\",[\"f32:2662226315\"]],[\"invoke\",1827,0,\"f32.no_fold_sqrt_square\",[\"f32:2624528574\"]],[\"invoke\",1829,0,\"f32.no_fold_sqrt_square\",[\"f32:1978715378\"]],[\"invoke\",1832,0,\"f64.no_fold_sqrt_square\",[\"f64:11517048459773840771\"]],[\"invoke\",1834,0,\"f64.no_fold_sqrt_square\",[\"f64:11056484744549647728\"]],[\"module\",1839,80],[\"invoke\",1847,0,\"f32.no_fold_mul_sqrts\",[\"f32:24047316\",\"f32:2517821717\"]],[\"invoke\",1849,0,\"f32.no_fold_mul_sqrts\",[\"f32:329708528\",\"f32:1120042892\"]],[\"invoke\",1851,0,\"f32.no_fold_mul_sqrts\",[\"f32:598482176\",\"f32:990534933\"]],[\"invoke\",1854,0,\"f64.no_fold_mul_sqrts\",[\"f64:1712959863583927241\",\"f64:2792003944717853898\"]],[\"invoke\",1856,0,\"f64.no_fold_mul_sqrts\",[\"f64:2976792199849816182\",\"f64:2030444188042608984\"]],[\"module\",1861,81],[\"invoke\",1869,0,\"f32.no_fold_div_sqrts\",[\"f32:3428799709\",\"f32:2733489079\"]],[\"invoke\",1870,0,\"f32.no_fold_div_sqrts\",[\"f32:1339867611\",\"f32:1296568207\"]],[\"invoke\",1872,0,\"f32.no_fold_div_sqrts\",[\"f32:1566143010\",\"f32:816694667\"]],[\"invoke\",1875,0,\"f64.no_fold_div_sqrts\",[\"f64:10629913473787695463\",\"f64:12991130264919696663\"]],[\"invoke\",1877,0,\"f64.no_fold_div_sqrts\",[\"f64:6965599900716272009\",\"f64:4118781927977980600\"]],[\"invoke\",1879,0,\"f64.no_fold_div_sqrts\",[\"f64:1056821405580891413\",\"f64:8865548665903786673\"]],[\"module\",1883,82],[\"invoke\",1892,0,\"f32.no_fold_mul_sqrt_div\",[\"f32:2210946958\",\"f32:256302916\"]],[\"invoke\",1894,0,\"f32.no_fold_mul_sqrt_div\",[\"f32:3576537897\",\"f32:2010442638\"]],[\"invoke\",1897,0,\"f64.no_fold_mul_sqrt_div\",[\"f64:7751219282814906463\",\"f64:8023732701704228537\"]],[\"invoke\",1899,0,\"f64.no_fold_mul_sqrt_div\",[\"f64:2695209648295623224\",\"f64:7133480874314061811\"]],[\"invoke\",1901,0,\"f64.no_fold_mul_sqrt_div\",[\"f64:5076901024782455083\",\"f64:8399438310541178654\"]],[\"module\",1906,83],[\"invoke\",1915,0,\"f64.no_flush_intermediate_subnormal\",[\"f64:4503599627370496\",\"f64:4372995238176751616\",\"f64:4841369599423283200\"]],[\"module\",1920,84],[\"invoke\",1944,0,\"f32.recoding_le\",[\"f32:4286578688\",\"f32:1077936128\"]],[\"invoke\",1947,0,\"f32.recoding_eq\",[\"f32:0\",\"f32:1065353216\"]],[\"invoke\",1948,0,\"f32.recoding_le\",[\"f32:0\",\"f32:1065353216\"]],[\"invoke\",1951,0,\"f64.recoding_eq\",[\"f64:18442240474082181120\",\"f64:4613937818241073152\"]],[\"invoke\",1953,0,\"f64.recoding_lt\",[\"f64:18442240474082181120\",\"f64:4613937818241073152\"]],[\"invoke\",1956,0,\"f64.recoding_le\",[\"f64:0\",\"f64:4607182418800017408\"]],[\"invoke\",1959,0,\"recoding_demote\",[\"f64:4014054135371399168\",\"f32:1150853120\"]],[\"module\",1964,85],[\"invoke\",1973,0,\"f64.no_extended_precision_div\",[\"f64:4613937818241073152\",\"f64:4619567317775286272\",\"f64:4601392076421969627\"]],[\"module\",1980,86],[\"invoke\",1989,0,\"f64.no_distribute_exact\",[\"f64:9223372036854775808\"]],[\"module\",1994,87],[\"invoke\",2027,0,\"f32.xkcd_sqrt_2\",[\"f32:1077936128\",\"f32:1084227584\",\"f32:1078530011\",\"f32:1088421888\"]],[\"invoke\",2029,0,\"f32.xkcd_sqrt_3\",[\"f32:1073741824\",\"f32:1076754516\",\"f32:1078530011\"]],[\"invoke\",2031,0,\"f32.xkcd_sqrt_5\",[\"f32:1073741824\",\"f32:1076754516\",\"f32:1077936128\"]],[\"invoke\",2034,0,\"f64.sqrt\",[\"f64:4611686018427387904\"]],[\"invoke\",2036,0,\"f64.sqrt\",[\"f64:4613937818241073152\"]],[\"invoke\",2038,0,\"f64.sqrt\",[\"f64:4617315517961601024\"]],[\"invoke\",2039,0,\"f64.xkcd_sqrt_5\",[\"f64:4611686018427387904\",\"f64:4613303445314885481\",\"f64:4613937818241073152\"]],[\"module\",2045,88],[\"invoke\",2123,0,\"f32.compute_radix\",[\"f32:1065353216\",\"f32:1065353216\"]],[\"module\",2129,89],[\"invoke\",2137,0,\"f32.no_fold_sub1_mul_add\",[\"f32:796917760\",\"f32:1065353216\"]],[\"module\",2143,90],[\"invoke\",2157,0,\"f32.no_fold_add_le_monotonicity\",[\"f32:0\",\"f32:0\",\"f32:2143289344\"]],[\"invoke\",2159,0,\"f64.no_fold_add_le_monotonicity\",[\"f64:0\",\"f64:0\",\"f64:9221120237041090560\"]],[\"module\",2164,91],[\"invoke\",2190,0,\"f32.not_lt\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",2192,0,\"f32.not_gt\",[\"f32:2143289344\",\"f32:0\"]],[\"invoke\",2194,0,\"f64.not_lt\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"invoke\",2196,0,\"f64.not_gt\",[\"f64:9221120237041090560\",\"f64:0\"]],[\"module\",2203,92],[\"invoke\",2211,0,\"f32.epsilon\",[]],[\"module\",2218,93],[\"invoke\",2266,0,\"f32.epsilon\",[]],[\"module\",2272,94],[\"invoke\",2292,0,\"f32.no_trichotomy_lt\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",2294,0,\"f32.no_trichotomy_gt\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",2295,0,\"f32.no_trichotomy_ge\",[\"f32:0\",\"f32:2143289344\"]],[\"invoke\",2297,0,\"f64.no_trichotomy_le\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"invoke\",2299,0,\"f64.no_trichotomy_ge\",[\"f64:0\",\"f64:9221120237041090560\"]],[\"module\",2304,95],[\"invoke\",2385,0,\"f32.canonical_nan_bitpattern\",[\"i32:2143289344\",\"i32:2143289344\"]],[\"invoke\",2387,0,\"f32.canonical_nan_bitpattern\",[\"i32:2143289344\",\"i32:4290772992\"]],[\"invoke\",2389,0,\"f32.nonarithmetic_nan_bitpattern\",[\"i32:2143302160\"]],[\"invoke\",2394,0,\"f64.canonical_nan_bitpattern\",[\"i64:0\",\"i64:0\"]],[\"invoke\",2396,0,\"f64.canonical_nan_bitpattern\",[\"i64:18444492273895866368\",\"i64:9221120237041090560\"]],[\"invoke\",2398,0,\"f64.canonical_nan_bitpattern\",[\"i64:18444492273895866368\",\"i64:18444492273895866368\"]],[\"invoke\",2400,0,\"f64.nonarithmetic_nan_bitpattern\",[\"i64:18444492273895879184\"]],[\"module\",2420,96],[\"invoke\",2447,0,\"with_binary_sum_collapse\",[\"f64:4719355144821538816\",\"f64:4607182418800017408\",\"f64:13830554455654793216\",\"f64:4725141118604279808\",\"f64:4720637518976909312\",\"f64:4607182418800017408\",\"f64:13830554455654793216\",\"f64:13938223582048944128\"]],[\"module\",2454,97],[\"invoke\",2466,0,\"f32.contract2fma\",[\"f32:1066192077\",\"f32:1066192077\"]],[\"invoke\",2468,0,\"f64.contract2fma\",[\"f64:4607182418800017408\",\"f64:4607182418800017408\"]],[\"invoke\",2469,0,\"f64.contract2fma\",[\"f64:4607632778762754458\",\"f64:4607632778762754458\"]],[\"module\",2475,98],[\"invoke\",2484,0,\"f32.division_by_small_number\",[\"f32:1289068416\",\"f32:1203982336\",\"f32:980151802\"]],[\"module\",2490,99],[\"invoke\",2497,0,\"f32.golden_ratio\",[\"f32:1056964608\",\"f32:1065353216\",\"f32:1084227584\"]],[\"module\",2503,100],[\"invoke\",2516,0,\"f32.silver_means\",[\"f32:0\"]],[\"invoke\",2518,0,\"f32.silver_means\",[\"f32:1073741824\"]],[\"invoke\",2520,0,\"f32.silver_means\",[\"f32:1082130432\"]],[\"invoke\",2522,0,\"f64.silver_means\",[\"f64:0\"]],[\"invoke\",2524,0,\"f64.silver_means\",[\"f64:4611686018427387904\"]],[\"invoke\",2526,0,\"f64.silver_means\",[\"f64:4616189618054758400\"]],[\"module\",2532,101],[\"invoke\",2537,0,\"point_four\",[\"f64:4616189618054758400\",\"f64:4621819117588971520\"]],[\"module\",2542,102],[\"invoke\",2608,0,\"tau\",[\"i32:11\"]],[\"module\",2612,103],[\"invoke\",2624,0,\"f64.no_fold_conditional_inc\",[\"f64:9223372036854775808\",\"f64:13830554455654793216\"]],[\"file\",\"float_literals.wast\"],[\"module\",3,104],[\"invoke\",121,0,\"f32.nan\",[]],[\"invoke\",122,0,\"f32.positive_nan\",[]],[\"invoke\",123,0,\"f32.negative_nan\",[]],[\"invoke\",124,0,\"f32.plain_nan\",[]],[\"invoke\",125,0,\"f32.informally_known_as_plain_snan\",[]],[\"invoke\",126,0,\"f32.all_ones_nan\",[]],[\"invoke\",127,0,\"f32.misc_nan\",[]],[\"invoke\",128,0,\"f32.misc_positive_nan\",[]],[\"invoke\",129,0,\"f32.misc_negative_nan\",[]],[\"invoke\",130,0,\"f32.infinity\",[]],[\"invoke\",131,0,\"f32.positive_infinity\",[]],[\"invoke\",132,0,\"f32.negative_infinity\",[]],[\"invoke\",133,0,\"f32.zero\",[]],[\"invoke\",134,0,\"f32.positive_zero\",[]],[\"invoke\",135,0,\"f32.negative_zero\",[]],[\"invoke\",136,0,\"f32.misc\",[]],[\"invoke\",137,0,\"f32.min_positive\",[]],[\"invoke\",138,0,\"f32.min_normal\",[]],[\"invoke\",139,0,\"f32.max_subnormal\",[]],[\"invoke\",140,0,\"f32.max_finite\",[]],[\"invoke\",141,0,\"f32.trailing_dot\",[]],[\"invoke\",142,0,\"f32.misc_int\",[]],[\"invoke\",143,0,\"f32.large_int\",[]],[\"invoke\",144,0,\"f32.min_int32\",[]],[\"invoke\",145,0,\"f32.min_int64\",[]],[\"invoke\",146,0,\"f32_dec.zero\",[]],[\"invoke\",147,0,\"f32_dec.positive_zero\",[]],[\"invoke\",148,0,\"f32_dec.negative_zero\",[]],[\"invoke\",149,0,\"f32_dec.misc\",[]],[\"invoke\",150,0,\"f32_dec.min_positive\",[]],[\"invoke\",151,0,\"f32_dec.min_normal\",[]],[\"invoke\",152,0,\"f32_dec.max_subnormal\",[]],[\"invoke\",153,0,\"f32_dec.max_finite\",[]],[\"invoke\",154,0,\"f32_dec.trailing_dot\",[]],[\"invoke\",155,0,\"f32_dec.root_beer_float\",[]],[\"invoke\",156,0,\"f32_dec.misc_int\",[]],[\"invoke\",157,0,\"f32_dec.large_int\",[]],[\"invoke\",158,0,\"f32_dec.min_int32\",[]],[\"invoke\",159,0,\"f32_dec.min_int64\",[]],[\"invoke\",161,0,\"f64.nan\",[]],[\"invoke\",162,0,\"f64.positive_nan\",[]],[\"invoke\",163,0,\"f64.negative_nan\",[]],[\"invoke\",164,0,\"f64.plain_nan\",[]],[\"invoke\",165,0,\"f64.informally_known_as_plain_snan\",[]],[\"invoke\",166,0,\"f64.all_ones_nan\",[]],[\"invoke\",167,0,\"f64.misc_nan\",[]],[\"invoke\",168,0,\"f64.misc_positive_nan\",[]],[\"invoke\",169,0,\"f64.misc_negative_nan\",[]],[\"invoke\",170,0,\"f64.infinity\",[]],[\"invoke\",171,0,\"f64.positive_infinity\",[]],[\"invoke\",172,0,\"f64.negative_infinity\",[]],[\"invoke\",173,0,\"f64.zero\",[]],[\"invoke\",174,0,\"f64.positive_zero\",[]],[\"invoke\",175,0,\"f64.negative_zero\",[]],[\"invoke\",176,0,\"f64.misc\",[]],[\"invoke\",177,0,\"f64.min_positive\",[]],[\"invoke\",178,0,\"f64.min_normal\",[]],[\"invoke\",179,0,\"f64.max_subnormal\",[]],[\"invoke\",180,0,\"f64.max_finite\",[]],[\"invoke\",181,0,\"f64.trailing_dot\",[]],[\"invoke\",182,0,\"f64.misc_int\",[]],[\"invoke\",183,0,\"f64.large_int\",[]],[\"invoke\",184,0,\"f64.min_int32\",[]],[\"invoke\",185,0,\"f64.min_int64\",[]],[\"invoke\",186,0,\"f64_dec.zero\",[]],[\"invoke\",187,0,\"f64_dec.positive_zero\",[]],[\"invoke\",188,0,\"f64_dec.negative_zero\",[]],[\"invoke\",189,0,\"f64_dec.misc\",[]],[\"invoke\",190,0,\"f64_dec.min_positive\",[]],[\"invoke\",191,0,\"f64_dec.min_normal\",[]],[\"invoke\",192,0,\"f64_dec.max_subnormal\",[]],[\"invoke\",193,0,\"f64_dec.max_finite\",[]],[\"invoke\",194,0,\"f64_dec.trailing_dot\",[]],[\"invoke\",195,0,\"f64_dec.root_beer_float\",[]],[\"invoke\",196,0,\"f64_dec.misc_int\",[]],[\"invoke\",197,0,\"f64_dec.large_int\",[]],[\"invoke\",198,0,\"f64_dec.min_int32\",[]],[\"invoke\",199,0,\"f64_dec.min_int64\",[]],[\"invoke\",201,0,\"f32-dec-sep1\",[]],[\"invoke\",202,0,\"f32-dec-sep2\",[]],[\"invoke\",203,0,\"f32-dec-sep3\",[]],[\"invoke\",204,0,\"f32-dec-sep4\",[]],[\"invoke\",205,0,\"f32-dec-sep5\",[]],[\"invoke\",206,0,\"f32-hex-sep1\",[]],[\"invoke\",207,0,\"f32-hex-sep2\",[]],[\"invoke\",208,0,\"f32-hex-sep3\",[]],[\"invoke\",209,0,\"f32-hex-sep4\",[]],[\"invoke\",210,0,\"f32-hex-sep5\",[]],[\"invoke\",212,0,\"f64-dec-sep1\",[]],[\"invoke\",213,0,\"f64-dec-sep2\",[]],[\"invoke\",214,0,\"f64-dec-sep3\",[]],[\"invoke\",215,0,\"f64-dec-sep4\",[]],[\"invoke\",216,0,\"f64-dec-sep5\",[]],[\"invoke\",217,0,\"f64-hex-sep1\",[]],[\"invoke\",218,0,\"f64-hex-sep2\",[]],[\"invoke\",219,0,\"f64-hex-sep3\",[]],[\"invoke\",220,0,\"f64-hex-sep4\",[]],[\"invoke\",221,0,\"f64-hex-sep5\",[]],[\"module\",224,105],[\"invoke\",233,0,\"4294967249\",[]],[\"file\",\"float_misc.wast\"],[\"module\",17,106],[\"invoke\",50,0,\"f32.add\",[\"f32:1066388847\",\"f32:789036054\"]],[\"invoke\",55,0,\"f32.add\",[\"f32:1065353216\",\"f32:864026624\"]],[\"invoke\",56,0,\"f32.add\",[\"f32:1065353216\",\"f32:864026625\"]],[\"invoke\",57,0,\"f64.add\",[\"f64:4607182418800017408\",\"f64:4368491638549381120\"]],[\"invoke\",58,0,\"f64.add\",[\"f64:4607182418800017408\",\"f64:4368491638549381121\"]],[\"invoke\",61,0,\"f32.add\",[\"f32:1\",\"f32:8388607\"]],[\"invoke\",62,0,\"f64.add\",[\"f64:1\",\"f64:4503599627370495\"]],[\"invoke\",67,0,\"f32.add\",[\"f32:1325400064\",\"f32:1149241344\"]],[\"invoke\",72,0,\"f64.add\",[\"f64:9290926031265333248\",\"f64:5910\"]],[\"invoke\",75,0,\"f64.add\",[\"f64:4845873199050653696\",\"f64:4607182463836013682\"]],[\"invoke\",78,0,\"f64.add\",[\"f64:4845873199050653697\",\"f64:4607182281361063936\"]],[\"invoke\",81,0,\"f32.add\",[\"f32:1258291200\",\"f32:1056964608\"]],[\"invoke\",82,0,\"f32.add\",[\"f32:1258291201\",\"f32:1056964608\"]],[\"invoke\",83,0,\"f64.add\",[\"f64:4841369599423283200\",\"f64:4602678819172646912\"]],[\"invoke\",84,0,\"f64.add\",[\"f64:4841369599423283201\",\"f64:4602678819172646912\"]],[\"invoke\",88,0,\"f32.add\",[\"f32:1630930834\",\"f32:3650472296\"]],[\"invoke\",89,0,\"f32.add\",[\"f32:376639884\",\"f32:24880479\"]],[\"invoke\",90,0,\"f32.add\",[\"f32:1393899754\",\"f32:3680827526\"]],[\"invoke\",91,0,\"f32.add\",[\"f32:947682203\",\"f32:1958603311\"]],[\"invoke\",92,0,\"f64.add\",[\"f64:2751474943031650218\",\"f64:14953834855654151696\"]],[\"invoke\",93,0,\"f64.add\",[\"f64:14174076479053295281\",\"f64:3779173703388472492\"]],[\"invoke\",94,0,\"f64.add\",[\"f64:3940735352412940081\",\"f64:13776826739676942972\"]],[\"invoke\",95,0,\"f64.add\",[\"f64:6845567588107709194\",\"f64:4904758653169279867\"]],[\"invoke\",99,0,\"f32.add\",[\"f32:1670249659\",\"f32:3384781876\"]],[\"invoke\",100,0,\"f32.add\",[\"f32:692047414\",\"f32:2564611463\"]],[\"invoke\",101,0,\"f32.add\",[\"f32:2862180574\",\"f32:2122049802\"]],[\"invoke\",102,0,\"f32.add\",[\"f32:2848402951\",\"f32:2325576998\"]],[\"invoke\",103,0,\"f32.add\",[\"f32:982116028\",\"f32:2317187467\"]],[\"invoke\",104,0,\"f64.add\",[\"f64:5417704807092288842\",\"f64:11458115339210975423\"]],[\"invoke\",105,0,\"f64.add\",[\"f64:5306888566943064716\",\"f64:13560253914302152139\"]],[\"invoke\",107,0,\"f64.add\",[\"f64:14493512931660601393\",\"f64:14459238760182946131\"]],[\"invoke\",108,0,\"f64.add\",[\"f64:13847269089137787654\",\"f64:5913400236268010570\"]],[\"invoke\",111,0,\"f32.add\",[\"f32:2231979500\",\"f32:711174153\"]],[\"invoke\",112,0,\"f32.add\",[\"f32:3084976721\",\"f32:843077785\"]],[\"invoke\",113,0,\"f32.add\",[\"f32:3522127374\",\"f32:157500525\"]],[\"invoke\",114,0,\"f32.add\",[\"f32:78702389\",\"f32:3468399689\"]],[\"invoke\",115,0,\"f32.add\",[\"f32:2208228371\",\"f32:1870536627\"]],[\"invoke\",116,0,\"f64.add\",[\"f64:1111252809067936271\",\"f64:18353849863141451174\"]],[\"invoke\",118,0,\"f64.add\",[\"f64:9478779231447112314\",\"f64:3336259491488022866\"]],[\"invoke\",119,0,\"f64.add\",[\"f64:11541137186853127278\",\"f64:735421354967021004\"]],[\"invoke\",120,0,\"f64.add\",[\"f64:18181384676953296798\",\"f64:8211214354507491487\"]],[\"invoke\",123,0,\"f64.add\",[\"f64:17455847474217352892\",\"f64:8328504330151758329\"]],[\"invoke\",124,0,\"f64.add\",[\"f64:8138029755241725240\",\"f64:7967841770626914860\"]],[\"invoke\",125,0,\"f64.add\",[\"f64:16644741111433920314\",\"f64:7647005019700459394\"]],[\"invoke\",126,0,\"f64.add\",[\"f64:6696851869641768443\",\"f64:6935821972358342665\"]],[\"invoke\",130,0,\"f64.add\",[\"f64:17221080860858566255\",\"f64:18119355194379769652\"]],[\"invoke\",131,0,\"f64.add\",[\"f64:5479469458864399218\",\"f64:15063363222154738502\"]],[\"invoke\",132,0,\"f64.add\",[\"f64:15191393426281101490\",\"f64:2705322087145917275\"]],[\"invoke\",133,0,\"f64.add\",[\"f64:12404517211283155052\",\"f64:15933096090325362723\"]],[\"invoke\",134,0,\"f64.add\",[\"f64:12941874353187635000\",\"f64:149235811938438489\"]],[\"invoke\",137,0,\"f32.add\",[\"f32:2039859408\",\"f32:2137384617\"]],[\"invoke\",138,0,\"f32.add\",[\"f32:2135652809\",\"f32:4113932278\"]],[\"invoke\",140,0,\"f32.add\",[\"f32:4220448050\",\"f32:4286022000\"]],[\"invoke\",141,0,\"f32.add\",[\"f32:4280642669\",\"f32:4269109313\"]],[\"invoke\",142,0,\"f64.add\",[\"f64:9214390167692528523\",\"f64:9025765862177526868\"]],[\"invoke\",143,0,\"f64.add\",[\"f64:9218174330906182895\",\"f64:9066655639269665468\"]],[\"invoke\",144,0,\"f64.add\",[\"f64:18440065048487386960\",\"f64:9166530475417861020\"]],[\"invoke\",145,0,\"f64.add\",[\"f64:18341378616977760125\",\"f64:9218376305633483958\"]],[\"invoke\",146,0,\"f64.add\",[\"f64:9214484525044672441\",\"f64:18297568705700624757\"]],[\"invoke\",149,0,\"f32.add\",[\"f32:5932680\",\"f32:623\"]],[\"invoke\",151,0,\"f32.add\",[\"f32:2147483656\",\"f32:3701056\"]],[\"invoke\",152,0,\"f32.add\",[\"f32:2147483668\",\"f32:44113961\"]],[\"invoke\",153,0,\"f32.add\",[\"f32:453\",\"f32:2147507390\"]],[\"invoke\",154,0,\"f64.add\",[\"f64:5760669582171681\",\"f64:9233067416817195210\"]],[\"invoke\",155,0,\"f64.add\",[\"f64:9301019945277718\",\"f64:9236877899492518055\"]],[\"invoke\",156,0,\"f64.add\",[\"f64:9225260466475370890\",\"f64:9229416573822848190\"]],[\"invoke\",157,0,\"f64.add\",[\"f64:16303139584665809\",\"f64:9240639734747285230\"]],[\"invoke\",162,0,\"f32.add\",[\"f32:2139095038\",\"f32:1937768448\"]],[\"invoke\",163,0,\"f64.add\",[\"f64:9218868437227405310\",\"f64:8980177656976769024\"]],[\"invoke\",166,0,\"f32.add\",[\"f32:1073741824\",\"f32:1073741824\"]],[\"invoke\",167,0,\"f64.add\",[\"f64:4611686018427387904\",\"f64:4611686018427387904\"]],[\"invoke\",170,0,\"f32.add\",[\"f32:2139095039\",\"f32:1929379839\"]],[\"invoke\",171,0,\"f32.add\",[\"f32:2139095039\",\"f32:1929379840\"]],[\"invoke\",172,0,\"f64.add\",[\"f64:9218868437227405311\",\"f64:8975674057349398527\"]],[\"invoke\",173,0,\"f64.add\",[\"f64:9218868437227405311\",\"f64:8975674057349398528\"]],[\"invoke\",178,0,\"f64.sub\",[\"f64:4679240012837945344\",\"f64:4440549232587309056\"]],[\"invoke\",182,0,\"f32.sub\",[\"f32:1065353216\",\"f32:855638016\"]],[\"invoke\",183,0,\"f32.sub\",[\"f32:1065353216\",\"f32:855638017\"]],[\"invoke\",184,0,\"f64.sub\",[\"f64:4607182418800017408\",\"f64:4363988038922010624\"]],[\"invoke\",185,0,\"f64.sub\",[\"f64:4607182418800017408\",\"f64:4363988038922010625\"]],[\"invoke\",188,0,\"f32.sub\",[\"f32:183964211\",\"f32:4211807167\"]],[\"invoke\",189,0,\"f32.sub\",[\"f32:4213323727\",\"f32:3575761746\"]],[\"invoke\",191,0,\"f32.sub\",[\"f32:88711906\",\"f32:431212897\"]],[\"invoke\",192,0,\"f32.sub\",[\"f32:919562194\",\"f32:2319656354\"]],[\"invoke\",193,0,\"f64.sub\",[\"f64:12332465020357998661\",\"f64:9766989582560416510\"]],[\"invoke\",194,0,\"f64.sub\",[\"f64:16318450733361321773\",\"f64:13120762196173477233\"]],[\"invoke\",195,0,\"f64.sub\",[\"f64:2534186520144737456\",\"f64:10065159679028096147\"]],[\"invoke\",196,0,\"f64.sub\",[\"f64:81253721542311597\",\"f64:14524181566355681001\"]],[\"invoke\",197,0,\"f64.sub\",[\"f64:13189545483472981053\",\"f64:11407195172005604952\"]],[\"invoke\",201,0,\"f32.sub\",[\"f32:4130431355\",\"f32:1119299749\"]],[\"invoke\",202,0,\"f32.sub\",[\"f32:2517909066\",\"f32:864524238\"]],[\"invoke\",203,0,\"f32.sub\",[\"f32:2573484334\",\"f32:3114628459\"]],[\"invoke\",204,0,\"f32.sub\",[\"f32:4044667730\",\"f32:576942556\"]],[\"invoke\",205,0,\"f64.sub\",[\"f64:14742371419310964805\",\"f64:17888404506408184249\"]],[\"invoke\",206,0,\"f64.sub\",[\"f64:2953146626865245854\",\"f64:2577681730975527916\"]],[\"invoke\",207,0,\"f64.sub\",[\"f64:12932364735331397407\",\"f64:2320782934320318207\"]],[\"invoke\",208,0,\"f64.sub\",[\"f64:9365060414983047910\",\"f64:12353777816259046974\"]],[\"invoke\",212,0,\"f32.sub\",[\"f32:1869943590\",\"f32:943887556\"]],[\"invoke\",213,0,\"f32.sub\",[\"f32:3372906046\",\"f32:2919590399\"]],[\"invoke\",214,0,\"f32.sub\",[\"f32:2912495853\",\"f32:2745492671\"]],[\"invoke\",215,0,\"f32.sub\",[\"f32:3096029999\",\"f32:957859028\"]],[\"invoke\",216,0,\"f32.sub\",[\"f32:2939165019\",\"f32:2346559691\"]],[\"invoke\",217,0,\"f64.sub\",[\"f64:13126685627069188368\",\"f64:17084005755352353256\"]],[\"invoke\",218,0,\"f64.sub\",[\"f64:17250804263238954355\",\"f64:17897322818375888829\"]],[\"invoke\",220,0,\"f64.sub\",[\"f64:11250563508203208480\",\"f64:2160870138446053709\"]],[\"invoke\",221,0,\"f64.sub\",[\"f64:11315355526970152728\",\"f64:13767999508795332779\"]],[\"invoke\",224,0,\"f64.sub\",[\"f64:4259315722107263852\",\"f64:4089445689175118070\"]],[\"invoke\",225,0,\"f64.sub\",[\"f64:188121565440651108\",\"f64:62416789530785743\"]],[\"invoke\",226,0,\"f64.sub\",[\"f64:3467770562990504390\",\"f64:3710644584044210353\"]],[\"invoke\",227,0,\"f64.sub\",[\"f64:13655868372788825570\",\"f64:13522732182992332383\"]],[\"invoke\",228,0,\"f64.sub\",[\"f64:4496158230847685281\",\"f64:4595312328816348364\"]],[\"invoke\",231,0,\"f64.sub\",[\"f64:10165048318361601487\",\"f64:10384658282813060399\"]],[\"invoke\",233,0,\"f64.sub\",[\"f64:5124509539958121241\",\"f64:1953255029772502995\"]],[\"invoke\",234,0,\"f64.sub\",[\"f64:2408125051325635052\",\"f64:10707017964771367822\"]],[\"invoke\",235,0,\"f64.sub\",[\"f64:4352135458168599028\",\"f64:1826599214642193119\"]],[\"invoke\",239,0,\"f32.sub\",[\"f32:1102651427\",\"f32:1078530011\"]],[\"invoke\",240,0,\"f64.sub\",[\"f64:4627206743602008890\",\"f64:4614256656552045848\"]],[\"invoke\",243,0,\"f32.sub\",[\"f32:1245125372\",\"f32:1245125368\"]],[\"invoke\",244,0,\"f32.sub\",[\"f32:1240736760\",\"f32:1240736728\"]],[\"invoke\",246,0,\"f32.sub\",[\"f32:1220759616\",\"f32:1220759584\"]],[\"invoke\",247,0,\"f32.sub\",[\"f32:1220759616\",\"f32:1220759552\"]],[\"invoke\",248,0,\"f64.sub\",[\"f64:4838362400168542206\",\"f64:4838362400168542204\"]],[\"invoke\",249,0,\"f64.sub\",[\"f64:4835858800541171708\",\"f64:4835858800541171692\"]],[\"invoke\",250,0,\"f64.sub\",[\"f64:4835858800541171708\",\"f64:4835858800541171684\"]],[\"invoke\",251,0,\"f64.sub\",[\"f64:4825251601286430752\",\"f64:4825251601286430736\"]],[\"invoke\",252,0,\"f64.sub\",[\"f64:4825251601286430752\",\"f64:4825251601286430720\"]],[\"invoke\",256,0,\"f64.sub\",[\"f64:4503599627370496\",\"f64:4503599627370495\"]],[\"invoke\",259,0,\"f32.sub\",[\"f32:1065353217\",\"f32:1065353215\"]],[\"invoke\",260,0,\"f32.sub\",[\"f32:1065353217\",\"f32:1065353216\"]],[\"invoke\",261,0,\"f32.sub\",[\"f32:1065353216\",\"f32:1065353215\"]],[\"invoke\",262,0,\"f64.sub\",[\"f64:4607182418800017409\",\"f64:4607182418800017407\"]],[\"invoke\",263,0,\"f64.sub\",[\"f64:4607182418800017409\",\"f64:4607182418800017408\"]],[\"invoke\",264,0,\"f64.sub\",[\"f64:4607182418800017408\",\"f64:4607182418800017407\"]],[\"invoke\",268,0,\"f32.sub\",[\"f32:2139095039\",\"f32:1929379839\"]],[\"invoke\",270,0,\"f64.sub\",[\"f64:9218868437227405311\",\"f64:8975674057349398527\"]],[\"invoke\",271,0,\"f64.sub\",[\"f64:9218868437227405311\",\"f64:8975674057349398528\"]],[\"invoke\",274,0,\"f32.mul\",[\"f32:1482907561\",\"f32:1482907561\"]],[\"invoke\",275,0,\"f32.mul\",[\"f32:1621981420\",\"f32:1621981420\"]],[\"invoke\",276,0,\"f32.mul\",[\"f32:1761892689\",\"f32:1761892689\"]],[\"invoke\",277,0,\"f64.mul\",[\"f64:4831355200913801216\",\"f64:4831355200913801216\"]],[\"invoke\",278,0,\"f64.mul\",[\"f64:4906019910204099648\",\"f64:4906019910204099648\"]],[\"invoke\",284,0,\"f32.mul\",[\"f32:1323067183\",\"f32:1351920719\"]],[\"invoke\",285,0,\"f64.mul\",[\"f64:4745541551160033280\",\"f64:4761032175258435584\"]],[\"invoke\",289,0,\"f32.mul\",[\"f32:1117401907\",\"f32:1146388480\"]],[\"invoke\",290,0,\"f64.mul\",[\"f64:4635125847112967782\",\"f64:4650687894887858176\"]],[\"invoke\",293,0,\"f32.mul\",[\"f32:3725225879\",\"f32:767181884\"]],[\"invoke\",294,0,\"f32.mul\",[\"f32:4071298289\",\"f32:2363594761\"]],[\"invoke\",295,0,\"f32.mul\",[\"f32:3990403914\",\"f32:2406636213\"]],[\"invoke\",296,0,\"f32.mul\",[\"f32:3876262739\",\"f32:2590162616\"]],[\"invoke\",298,0,\"f64.mul\",[\"f64:15745248579468343268\",\"f64:8867411559652116184\"]],[\"invoke\",299,0,\"f64.mul\",[\"f64:9393595877903301723\",\"f64:12847704090045979814\"]],[\"invoke\",300,0,\"f64.mul\",[\"f64:7339743909496900438\",\"f64:14312273837817528234\"]],[\"invoke\",301,0,\"f64.mul\",[\"f64:12422633289194844785\",\"f64:7233496047441461849\"]],[\"invoke\",302,0,\"f64.mul\",[\"f64:8188526721198436258\",\"f64:745378815681991665\"]],[\"invoke\",305,0,\"f32.mul\",[\"f32:2468326353\",\"f32:3984723636\"]],[\"invoke\",306,0,\"f32.mul\",[\"f32:1680245441\",\"f32:1228341789\"]],[\"invoke\",308,0,\"f32.mul\",[\"f32:1631446143\",\"f32:3340593865\"]],[\"invoke\",309,0,\"f32.mul\",[\"f32:1321841569\",\"f32:3771977928\"]],[\"invoke\",310,0,\"f64.mul\",[\"f64:10956034376207055635\",\"f64:3195464683520095288\"]],[\"invoke\",311,0,\"f64.mul\",[\"f64:3366372084365197849\",\"f64:10971437597834234015\"]],[\"invoke\",312,0,\"f64.mul\",[\"f64:15316253934569525376\",\"f64:17593766689751523793\"]],[\"invoke\",313,0,\"f64.mul\",[\"f64:8873430008942894499\",\"f64:2630310872370005737\"]],[\"invoke\",314,0,\"f64.mul\",[\"f64:6138390736122864607\",\"f64:14591737303103877464\"]],[\"invoke\",318,0,\"f32.mul\",[\"f32:3192278970\",\"f32:242309673\"]],[\"invoke\",319,0,\"f32.mul\",[\"f32:3060150317\",\"f32:1953896717\"]],[\"invoke\",320,0,\"f32.mul\",[\"f32:3685735869\",\"f32:265571119\"]],[\"invoke\",321,0,\"f32.mul\",[\"f32:3355295625\",\"f32:69023757\"]],[\"invoke\",322,0,\"f64.mul\",[\"f64:11598370094301102699\",\"f64:15783697741936198553\"]],[\"invoke\",323,0,\"f64.mul\",[\"f64:6760137556672200005\",\"f64:10390971429121674926\"]],[\"invoke\",324,0,\"f64.mul\",[\"f64:14608361260346079328\",\"f64:6227029768418337647\"]],[\"invoke\",325,0,\"f64.mul\",[\"f64:8069908676175581646\",\"f64:15517844864458283108\"]],[\"invoke\",329,0,\"f64.mul\",[\"f64:3075846375446710711\",\"f64:2019524319895558702\"]],[\"invoke\",330,0,\"f64.mul\",[\"f64:13792389967993824121\",\"f64:7865224186729676301\"]],[\"invoke\",331,0,\"f64.mul\",[\"f64:15483491341737357200\",\"f64:10898188329492193507\"]],[\"invoke\",332,0,\"f64.mul\",[\"f64:8100886621587996114\",\"f64:4881220253517731398\"]],[\"invoke\",333,0,\"f64.mul\",[\"f64:12589892576446671178\",\"f64:8646729507413822745\"]],[\"invoke\",336,0,\"f64.mul\",[\"f64:1913383041739811140\",\"f64:2696786534754768330\"]],[\"invoke\",337,0,\"f64.mul\",[\"f64:13576886274997223712\",\"f64:9480836654241272360\"]],[\"invoke\",339,0,\"f64.mul\",[\"f64:10357270161024523488\",\"f64:3460198093814186274\"]],[\"invoke\",340,0,\"f64.mul\",[\"f64:11506848031881565957\",\"f64:11547514064126512393\"]],[\"invoke\",343,0,\"f64.mul\",[\"f64:8446730755803745435\",\"f64:640105622936255012\"]],[\"invoke\",344,0,\"f64.mul\",[\"f64:4578331547514318947\",\"f64:3937894457345907544\"]],[\"invoke\",345,0,\"f64.mul\",[\"f64:13839181621982597923\",\"f64:4426983905639375259\"]],[\"invoke\",346,0,\"f64.mul\",[\"f64:2931365221683549507\",\"f64:3687257801017498184\"]],[\"invoke\",347,0,\"f64.mul\",[\"f64:14180592859625794472\",\"f64:3043918530412552819\"]],[\"invoke\",350,0,\"f32.mul\",[\"f32:436207616\",\"f32:436207616\"]],[\"invoke\",352,0,\"f64.mul\",[\"f64:2186111271319845836\",\"f64:2186111271319845836\"]],[\"invoke\",353,0,\"f64.mul\",[\"f64:2186111271319845837\",\"f64:2186111271319845837\"]],[\"invoke\",356,0,\"f32.mul\",[\"f32:1602224127\",\"f32:1602224127\"]],[\"invoke\",357,0,\"f32.mul\",[\"f32:1602224128\",\"f32:1602224128\"]],[\"invoke\",358,0,\"f64.mul\",[\"f64:6913025428013711359\",\"f64:6913025428013711359\"]],[\"invoke\",359,0,\"f64.mul\",[\"f64:6913025428013711360\",\"f64:6913025428013711360\"]],[\"invoke\",362,0,\"f32.mul\",[\"f32:1065353217\",\"f32:1065353217\"]],[\"invoke\",364,0,\"f64.mul\",[\"f64:4607182418800017409\",\"f64:4607182418800017409\"]],[\"invoke\",365,0,\"f64.mul\",[\"f64:4607182418800017407\",\"f64:4607182418800017407\"]],[\"invoke\",368,0,\"f32.mul\",[\"f32:1065353217\",\"f32:1065353215\"]],[\"invoke\",369,0,\"f32.mul\",[\"f32:1065353218\",\"f32:1065353214\"]],[\"invoke\",370,0,\"f64.mul\",[\"f64:4607182418800017409\",\"f64:4607182418800017407\"]],[\"invoke\",371,0,\"f64.mul\",[\"f64:4607182418800017410\",\"f64:4607182418800017406\"]],[\"invoke\",375,0,\"f32.mul\",[\"f32:8388608\",\"f32:872415232\"]],[\"invoke\",376,0,\"f64.mul\",[\"f64:4503599627370496\",\"f64:4372995238176751616\"]],[\"invoke\",382,0,\"f32.div\",[\"f32:1066388847\",\"f32:1120403456\"]],[\"invoke\",383,0,\"f32.div\",[\"f32:1258294259\",\"f32:1262485497\"]],[\"invoke\",384,0,\"f32.div\",[\"f32:1199570944\",\"f32:754974720\"]],[\"invoke\",385,0,\"f32.div\",[\"f32:1072586677\",\"f32:2139095039\"]],[\"invoke\",386,0,\"f32.div\",[\"f32:1082130432\",\"f32:1077936128\"]],[\"invoke\",387,0,\"f64.div\",[\"f64:4607738418748954166\",\"f64:4636737291354636288\"]],[\"invoke\",388,0,\"f64.div\",[\"f64:4710766852517658624\",\"f64:4713017006285127680\"]],[\"invoke\",390,0,\"f64.div\",[\"f64:4611065853604003840\",\"f64:9218868437227405311\"]],[\"invoke\",391,0,\"f64.div\",[\"f64:4616189618054758400\",\"f64:4613937818241073152\"]],[\"invoke\",395,0,\"f32.div\",[\"f32:1249905654\",\"f32:1245708284\"]],[\"invoke\",396,0,\"f64.div\",[\"f64:4706263254500900864\",\"f64:4704009808640999424\"]],[\"invoke\",399,0,\"f32.div\",[\"f32:666187309\",\"f32:2138899896\"]],[\"invoke\",400,0,\"f32.div\",[\"f32:338737113\",\"f32:1637454009\"]],[\"invoke\",401,0,\"f32.div\",[\"f32:3351999420\",\"f32:401966635\"]],[\"invoke\",403,0,\"f32.div\",[\"f32:3564182439\",\"f32:397999726\"]],[\"invoke\",404,0,\"f64.div\",[\"f64:5269602717919885505\",\"f64:5615431061534361830\"]],[\"invoke\",405,0,\"f64.div\",[\"f64:1791703022234099881\",\"f64:230587289790163684\"]],[\"invoke\",406,0,\"f64.div\",[\"f64:15176390632865983384\",\"f64:7489729865403831125\"]],[\"invoke\",407,0,\"f64.div\",[\"f64:16945752639368638170\",\"f64:5718505335991307775\"]],[\"invoke\",408,0,\"f64.div\",[\"f64:16263708435887736913\",\"f64:6648866667391375614\"]],[\"invoke\",411,0,\"f32.div\",[\"f32:3798033061\",\"f32:2491443361\"]],[\"invoke\",412,0,\"f32.div\",[\"f32:686927199\",\"f32:1440960248\"]],[\"invoke\",414,0,\"f32.div\",[\"f32:642177221\",\"f32:2685135\"]],[\"invoke\",415,0,\"f32.div\",[\"f32:1824271560\",\"f32:1797890210\"]],[\"invoke\",416,0,\"f64.div\",[\"f64:4278321332874065\",\"f64:15464375907975098426\"]],[\"invoke\",417,0,\"f64.div\",[\"f64:9385779280320516508\",\"f64:2195352144435258723\"]],[\"invoke\",418,0,\"f64.div\",[\"f64:12019785500442997560\",\"f64:14033717547286793792\"]],[\"invoke\",419,0,\"f64.div\",[\"f64:14192622858179995755\",\"f64:1048270709368415171\"]],[\"invoke\",420,0,\"f64.div\",[\"f64:14895971708944847415\",\"f64:14082502105592202184\"]],[\"invoke\",424,0,\"f32.div\",[\"f32:3861505243\",\"f32:1298875600\"]],[\"invoke\",425,0,\"f32.div\",[\"f32:1387472197\",\"f32:4021087697\"]],[\"invoke\",426,0,\"f32.div\",[\"f32:4149014653\",\"f32:3294714580\"]],[\"invoke\",427,0,\"f32.div\",[\"f32:3987554477\",\"f32:3327836421\"]],[\"invoke\",428,0,\"f64.div\",[\"f64:10882983485852580843\",\"f64:1499380901194126188\"]],[\"invoke\",429,0,\"f64.div\",[\"f64:17353632673902705731\",\"f64:11343375610449883809\"]],[\"invoke\",430,0,\"f64.div\",[\"f64:10471640091267549911\",\"f64:4317563730789156718\"]],[\"invoke\",431,0,\"f64.div\",[\"f64:15262578458239868670\",\"f64:11184694400998603321\"]],[\"invoke\",435,0,\"f64.div\",[\"f64:16467910284400317286\",\"f64:14710955777971283471\"]],[\"invoke\",436,0,\"f64.div\",[\"f64:16290965053652735591\",\"f64:15317706837775392625\"]],[\"invoke\",437,0,\"f64.div\",[\"f64:5440366292977110369\",\"f64:18177315027663270891\"]],[\"invoke\",438,0,\"f64.div\",[\"f64:5586144947709395659\",\"f64:6532436425888624877\"]],[\"invoke\",439,0,\"f64.div\",[\"f64:17550005606375580620\",\"f64:6180045224129992598\"]],[\"invoke\",442,0,\"f64.div\",[\"f64:4611065853588275309\",\"f64:9218868437227405311\"]],[\"invoke\",443,0,\"f64.div\",[\"f64:58005932411648970\",\"f64:4662638783615926141\"]],[\"invoke\",445,0,\"f64.div\",[\"f64:12062544014359965409\",\"f64:16674780236028867992\"]],[\"invoke\",446,0,\"f64.div\",[\"f64:752499975442349235\",\"f64:5356690663768240614\"]],[\"invoke\",447,0,\"f64.div\",[\"f64:83041154348962762\",\"f64:13934889252531101931\"]],[\"invoke\",450,0,\"f32.div\",[\"f32:1817629909\",\"f32:1421132838\"]],[\"invoke\",451,0,\"f32.div\",[\"f32:1824604388\",\"f32:4105258014\"]],[\"invoke\",452,0,\"f32.div\",[\"f32:1747858929\",\"f32:1063784191\"]],[\"invoke\",453,0,\"f32.div\",[\"f32:3378374953\",\"f32:3497907368\"]],[\"invoke\",455,0,\"f64.div\",[\"f64:4885037015672297625\",\"f64:9954263091431620051\"]],[\"invoke\",456,0,\"f64.div\",[\"f64:4022347395424227722\",\"f64:11486191764643320419\"]],[\"invoke\",457,0,\"f64.div\",[\"f64:7623973190248181230\",\"f64:7481900282945497881\"]],[\"invoke\",458,0,\"f64.div\",[\"f64:14650287530633645126\",\"f64:8091500841285620267\"]],[\"invoke\",459,0,\"f64.div\",[\"f64:9910334119372230161\",\"f64:10921408834055405722\"]],[\"invoke\",462,0,\"f64.div\",[\"f64:4352764235752579571\",\"f64:4610853254537913145\"]],[\"invoke\",463,0,\"f64.div\",[\"f64:4148344307949876337\",\"f64:13642564096968604240\"]],[\"invoke\",464,0,\"f64.div\",[\"f64:4425554483259831683\",\"f64:4103199251532205583\"]],[\"invoke\",466,0,\"f64.div\",[\"f64:4558988817252519457\",\"f64:14066629987516208597\"]],[\"invoke\",469,0,\"f32.div\",[\"f32:8388608\",\"f32:8388607\"]],[\"invoke\",470,0,\"f32.div\",[\"f32:8388607\",\"f32:8388608\"]],[\"invoke\",471,0,\"f64.div\",[\"f64:4503599627370496\",\"f64:4503599627370495\"]],[\"invoke\",472,0,\"f64.div\",[\"f64:4503599627370495\",\"f64:4503599627370496\"]],[\"invoke\",475,0,\"f32.div\",[\"f32:880803839\",\"f32:2139095039\"]],[\"invoke\",476,0,\"f32.div\",[\"f32:880803840\",\"f32:2139095039\"]],[\"invoke\",478,0,\"f64.div\",[\"f64:4377498837804122112\",\"f64:9218868437227405311\"]],[\"invoke\",481,0,\"f32.div\",[\"f32:1065353216\",\"f32:2097152\"]],[\"invoke\",482,0,\"f32.div\",[\"f32:1065353216\",\"f32:2097153\"]],[\"invoke\",483,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:1125899906842624\"]],[\"invoke\",484,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:1125899906842625\"]],[\"invoke\",487,0,\"f32.div\",[\"f32:1065353216\",\"f32:2122317825\"]],[\"invoke\",488,0,\"f32.div\",[\"f32:1065353216\",\"f32:2122317824\"]],[\"invoke\",489,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:9209861237972664321\"]],[\"invoke\",500,0,\"f32.div\",[\"f32:1065353216\",\"f32:1077936128\"]],[\"invoke\",501,0,\"f32.div\",[\"f32:1077936128\",\"f32:1091567616\"]],[\"invoke\",502,0,\"f32.div\",[\"f32:1091567616\",\"f32:1104674816\"]],[\"invoke\",503,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:4613937818241073152\"]],[\"invoke\",504,0,\"f64.div\",[\"f64:4613937818241073152\",\"f64:4621256167635550208\"]],[\"invoke\",505,0,\"f64.div\",[\"f64:4621256167635550208\",\"f64:4628293042053316608\"]],[\"invoke\",508,0,\"f32.div\",[\"f32:1065353217\",\"f32:1065353215\"]],[\"invoke\",510,0,\"f32.div\",[\"f32:1065353216\",\"f32:1065353215\"]],[\"invoke\",511,0,\"f32.div\",[\"f32:1065353216\",\"f32:1065353217\"]],[\"invoke\",512,0,\"f64.div\",[\"f64:4607182418800017409\",\"f64:4607182418800017407\"]],[\"invoke\",513,0,\"f64.div\",[\"f64:4607182418800017407\",\"f64:4607182418800017409\"]],[\"invoke\",514,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:4607182418800017407\"]],[\"invoke\",515,0,\"f64.div\",[\"f64:4607182418800017408\",\"f64:4607182418800017409\"]],[\"invoke\",519,0,\"f32.sqrt\",[\"f32:1126891520\"]],[\"invoke\",521,0,\"f64.sqrt\",[\"f64:4640220544191430656\"]],[\"invoke\",522,0,\"f64.sqrt\",[\"f64:4505170691092029701\"]],[\"invoke\",525,0,\"f64.sqrt\",[\"f64:3868634135368364633\"]],[\"invoke\",526,0,\"f64.sqrt\",[\"f64:6130334638238385213\"]],[\"invoke\",527,0,\"f64.sqrt\",[\"f64:6420749722515481850\"]],[\"invoke\",528,0,\"f64.sqrt\",[\"f64:4441983858611369811\"]],[\"invoke\",529,0,\"f64.sqrt\",[\"f64:2943222801950574478\"]],[\"invoke\",533,0,\"f64.sqrt\",[\"f64:4607182418800017407\"]],[\"invoke\",537,0,\"f32.sqrt\",[\"f32:1911349112\"]],[\"invoke\",538,0,\"f32.sqrt\",[\"f32:1033984731\"]],[\"invoke\",539,0,\"f32.sqrt\",[\"f32:471548520\"]],[\"invoke\",540,0,\"f32.sqrt\",[\"f32:980180519\"]],[\"invoke\",541,0,\"f64.sqrt\",[\"f64:447478826231748356\"]],[\"invoke\",542,0,\"f64.sqrt\",[\"f64:7882765141420635228\"]],[\"invoke\",543,0,\"f64.sqrt\",[\"f64:6345006020483303032\"]],[\"invoke\",545,0,\"f64.sqrt\",[\"f64:9108179429173493573\"]],[\"invoke\",548,0,\"f32.sqrt\",[\"f32:1293112087\"]],[\"invoke\",549,0,\"f32.sqrt\",[\"f32:118173697\"]],[\"invoke\",550,0,\"f32.sqrt\",[\"f32:941555112\"]],[\"invoke\",551,0,\"f32.sqrt\",[\"f32:60323222\"]],[\"invoke\",552,0,\"f32.sqrt\",[\"f32:40220718\"]],[\"invoke\",553,0,\"f64.sqrt\",[\"f64:7741365502393472048\"]],[\"invoke\",554,0,\"f64.sqrt\",[\"f64:1175297548666390979\"]],[\"invoke\",556,0,\"f64.sqrt\",[\"f64:3521792205891300764\"]],[\"invoke\",557,0,\"f64.sqrt\",[\"f64:8798159938601760820\"]],[\"invoke\",560,0,\"f32.sqrt\",[\"f32:2058533950\"]],[\"invoke\",561,0,\"f32.sqrt\",[\"f32:1194962978\"]],[\"invoke\",562,0,\"f32.sqrt\",[\"f32:1062209384\"]],[\"invoke\",563,0,\"f32.sqrt\",[\"f32:339551808\"]],[\"invoke\",564,0,\"f32.sqrt\",[\"f32:279876202\"]],[\"invoke\",566,0,\"f64.sqrt\",[\"f64:2893298197477532149\"]],[\"invoke\",567,0,\"f64.sqrt\",[\"f64:8954101765007123282\"]],[\"invoke\",568,0,\"f64.sqrt\",[\"f64:4405641787023763989\"]],[\"invoke\",569,0,\"f64.sqrt\",[\"f64:2394824404681191749\"]],[\"invoke\",572,0,\"f64.sqrt\",[\"f64:9494538992465685200\"]],[\"invoke\",573,0,\"f64.sqrt\",[\"f64:5074882158241187075\"]],[\"invoke\",574,0,\"f64.sqrt\",[\"f64:1187437724299488295\"]],[\"invoke\",576,0,\"f64.sqrt\",[\"f64:7161993531047854177\"]],[\"invoke\",579,0,\"f32.sqrt\",[\"f32:1065353217\"]],[\"invoke\",580,0,\"f32.sqrt\",[\"f32:1065353218\"]],[\"invoke\",581,0,\"f64.sqrt\",[\"f64:4607182418800017409\"]],[\"invoke\",582,0,\"f64.sqrt\",[\"f64:4607182418800017410\"]],[\"invoke\",585,0,\"f32.sqrt\",[\"f32:1065353214\"]],[\"invoke\",586,0,\"f32.sqrt\",[\"f32:1065353213\"]],[\"invoke\",587,0,\"f64.sqrt\",[\"f64:4607182418800017406\"]],[\"invoke\",591,0,\"f32.sqrt\",[\"f32:1048575999\"]],[\"invoke\",592,0,\"f32.sqrt\",[\"f32:1048576000\"]],[\"invoke\",593,0,\"f32.sqrt\",[\"f32:1048576001\"]],[\"invoke\",594,0,\"f32.sqrt\",[\"f32:1048576002\"]],[\"invoke\",595,0,\"f64.sqrt\",[\"f64:4598175219545276415\"]],[\"invoke\",596,0,\"f64.sqrt\",[\"f64:4598175219545276416\"]],[\"invoke\",597,0,\"f64.sqrt\",[\"f64:4598175219545276417\"]],[\"invoke\",602,0,\"f32.sqrt\",[\"f32:1383964906\"]],[\"invoke\",603,0,\"f64.sqrt\",[\"f64:4778235767454362395\"]],[\"invoke\",607,0,\"f32.sqrt\",[\"f32:1344155865\"]],[\"invoke\",608,0,\"f32.sqrt\",[\"f32:1496014503\"]],[\"invoke\",609,0,\"f32.sqrt\",[\"f32:1437792278\"]],[\"invoke\",610,0,\"f32.sqrt\",[\"f32:1318267910\"]],[\"invoke\",611,0,\"f64.sqrt\",[\"f64:4756863450987102208\"]],[\"invoke\",612,0,\"f64.sqrt\",[\"f64:4838391936821527880\"]],[\"invoke\",614,0,\"f64.sqrt\",[\"f64:4742964961037254656\"]],[\"invoke\",618,0,\"f32.sqrt\",[\"f32:1057229822\"]],[\"invoke\",619,0,\"f32.sqrt\",[\"f32:1057013196\"]],[\"invoke\",620,0,\"f64.sqrt\",[\"f64:4602821204854702080\"]],[\"invoke\",621,0,\"f64.sqrt\",[\"f64:4602704904656519168\"]],[\"invoke\",625,0,\"f32.sqrt\",[\"f32:1091567616\"]],[\"invoke\",626,0,\"f32.sqrt\",[\"f32:1124663296\"]],[\"invoke\",628,0,\"f32.sqrt\",[\"f32:1148846080\"]],[\"invoke\",629,0,\"f64.sqrt\",[\"f64:4621256167635550208\"]],[\"invoke\",630,0,\"f64.sqrt\",[\"f64:4639024275540410368\"]],[\"invoke\",631,0,\"f64.sqrt\",[\"f64:4610667174614947795\"]],[\"invoke\",632,0,\"f64.sqrt\",[\"f64:4652007308841189376\"]],[\"invoke\",656,0,\"f32.ceil\",[\"f32:1065353215\"]],[\"invoke\",657,0,\"f32.ceil\",[\"f32:1065353217\"]],[\"invoke\",659,0,\"f64.ceil\",[\"f64:4607182418800017409\"]],[\"invoke\",662,0,\"f32.ceil\",[\"f32:1258291199\"]],[\"invoke\",663,0,\"f32.ceil\",[\"f32:3405774847\"]],[\"invoke\",664,0,\"f64.ceil\",[\"f64:4841369599423283199\"]],[\"invoke\",665,0,\"f64.ceil\",[\"f64:14064741636278059007\"]],[\"invoke\",669,0,\"f32.ceil\",[\"f32:1266679807\"]],[\"invoke\",670,0,\"f32.ceil\",[\"f32:3414163455\"]],[\"invoke\",671,0,\"f64.ceil\",[\"f64:4845873199050653695\"]],[\"invoke\",675,0,\"f32.floor\",[\"f32:3212836863\"]],[\"invoke\",676,0,\"f32.floor\",[\"f32:3212836865\"]],[\"invoke\",677,0,\"f64.floor\",[\"f64:13830554455654793215\"]],[\"invoke\",678,0,\"f64.floor\",[\"f64:13830554455654793217\"]],[\"invoke\",681,0,\"f32.floor\",[\"f32:3405774847\"]],[\"invoke\",682,0,\"f32.floor\",[\"f32:1258291199\"]],[\"invoke\",683,0,\"f64.floor\",[\"f64:14064741636278059007\"]],[\"invoke\",688,0,\"f32.floor\",[\"f32:1202524032\"]],[\"invoke\",689,0,\"f64.floor\",[\"f64:4680825439885721600\"]],[\"invoke\",692,0,\"f32.trunc\",[\"f32:3405774847\"]],[\"invoke\",693,0,\"f32.trunc\",[\"f32:1258291199\"]],[\"invoke\",694,0,\"f64.trunc\",[\"f64:14064741636278059007\"]],[\"invoke\",695,0,\"f64.trunc\",[\"f64:4841369599423283199\"]],[\"invoke\",700,0,\"f32.nearest\",[\"f32:1258291201\"]],[\"invoke\",701,0,\"f32.nearest\",[\"f32:1258291202\"]],[\"invoke\",703,0,\"f32.nearest\",[\"f32:1468006399\"]],[\"invoke\",704,0,\"f64.nearest\",[\"f64:4841369599423283201\"]],[\"invoke\",705,0,\"f64.nearest\",[\"f64:4841369599423283202\"]],[\"invoke\",706,0,\"f64.nearest\",[\"f64:4602678819172646911\"]],[\"invoke\",707,0,\"f64.nearest\",[\"f64:5084563979301289983\"]],[\"invoke\",711,0,\"f32.nearest\",[\"f32:1083179008\"]],[\"invoke\",712,0,\"f32.nearest\",[\"f32:3230662656\"]],[\"invoke\",714,0,\"f64.nearest\",[\"f64:4616752568008179712\"]],[\"invoke\",715,0,\"f64.nearest\",[\"f64:13840124604862955520\"]],[\"invoke\",716,0,\"f64.nearest\",[\"f64:13838435755002691584\"]],[\"invoke\",719,0,\"f32.nearest\",[\"f32:3405774847\"]],[\"invoke\",720,0,\"f32.nearest\",[\"f32:1258291199\"]],[\"invoke\",721,0,\"f64.nearest\",[\"f64:14064741636278059007\"]],[\"invoke\",722,0,\"f64.nearest\",[\"f64:4841369599423283199\"]]]"; diff --git a/tests/corpus/webassembly-spec-float/main.mjs b/tests/corpus/webassembly-spec-float/main.mjs new file mode 100644 index 000000000..289dad212 --- /dev/null +++ b/tests/corpus/webassembly-spec-float/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (9 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-imports/data.mjs b/tests/corpus/webassembly-spec-imports/data.mjs new file mode 100644 index 000000000..5ecccab26 --- /dev/null +++ b/tests/corpus/webassembly-spec-imports/data.mjs @@ -0,0 +1,137 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * imports.wast, table.wast, names.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABHgdgAABgAX8AYAF9AGAAAX9gAAF9YAF/AX9gAX4BfgMIBwABAgMEBQYECAJwAApwAQoUBQMBAAIGFAN/AEE3C30AQwAAMEILfgFCwgALB60BDQRmdW5jAAAIZnVuYy1pMzIAAQhmdW5jLWYzMgACCWZ1bmMtPmkzMgADCWZ1bmMtPmYzMgAEDWZ1bmMtaTMyLT5pMzIABQ1mdW5jLWk2NC0+aTY0AAYKZ2xvYmFsLWkzMgMACmdsb2JhbC1mMzIDAQ5nbG9iYWwtbXV0LWk2NAMCDHRhYmxlLTEwLWluZgEAC3RhYmxlLTEwLTIwAQEMbWVtb3J5LTItaW5mAgAKIQcCAAsCAAsCAAsEAEEWCwcAQwAAMEELBAAgAAsEACAACw==", + "AGFzbQEAAAABJAhgAX8AYAF+AGABfQBgAXwAYAF/AGACf30AYAJ8fABgAX4BfgKDAxIIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTY0AAEIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTY0AAEIc3BlY3Rlc3QJcHJpbnRfZjMyAAIIc3BlY3Rlc3QJcHJpbnRfZjY0AAMIc3BlY3Rlc3QNcHJpbnRfaTMyX2YzMgAFCHNwZWN0ZXN0DXByaW50X2Y2NF9mNjQABghzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9mNjQAAwR0ZXN0DWZ1bmMtaTY0LT5pNjQABwhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIAAAhzcGVjdGVzdAlwcmludF9pMzIABAhzcGVjdGVzdAlwcmludF9pMzIABAMDAgABBAUBcAECAgczCAJwMQALAnAyAAwCcDMADQJwNAANAnA1AA4CcDYADwdwcmludDMyABIHcHJpbnQ2NAATCQgBAEEACwICBQpoAiwBAX0gALIhASAAEAAgAEEBakMAAChCEAYgABACIAAQCCABEAQgAEEAEQAACzkBAXwgABAKuSEBIAAQASABRAAAAAAAAPA/oEQAAAAAAIBKQBAHIAAQAyABEAUgARAJIAFBAREDAAs=", + "AGFzbQEAAAABBQFgAX8AAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAAcNAQlwcmludF9pMzIAAQoIAQYAIAAQAAs=", + "AGFzbQEAAAABCwJgAX8AYAJ/fwF/AhYBCHNwZWN0ZXN0CXByaW50X2kzMgAAAwIBAQcNAQlwcmludF9pMzIAAQoJAQcAIAAgAWoL", + "AGFzbQEAAAABBAFgAAACDQEEdGVzdARmdW5jAAA=", + "AGFzbQEAAAABBQFgAX8AAhEBBHRlc3QIZnVuYy1pMzIAAA==", + "AGFzbQEAAAABBQFgAX0AAhEBBHRlc3QIZnVuYy1mMzIAAA==", + "AGFzbQEAAAABBQFgAAF/AhIBBHRlc3QJZnVuYy0+aTMyAAA=", + "AGFzbQEAAAABBQFgAAF9AhIBBHRlc3QJZnVuYy0+ZjMyAAA=", + "AGFzbQEAAAABBgFgAX8BfwIWAQR0ZXN0DWZ1bmMtaTMyLT5pMzIAAA==", + "AGFzbQEAAAABBgFgAX4BfgIWAQR0ZXN0DWZ1bmMtaTY0LT5pNjQAAA==", + "AGFzbQEAAAABBAFgAAACEAEEdGVzdAd1bmtub3duAAA=", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QHdW5rbm93bgAA", + "AGFzbQEAAAABBQFgAX8AAg0BBHRlc3QEZnVuYwAA", + "AGFzbQEAAAABBQFgAAF/Ag0BBHRlc3QEZnVuYwAA", + "AGFzbQEAAAABBgFgAX8BfwINAQR0ZXN0BGZ1bmMAAA==", + "AGFzbQEAAAABBAFgAAACEQEEdGVzdAhmdW5jLWkzMgAA", + "AGFzbQEAAAABBQFgAAF/AhEBBHRlc3QIZnVuYy1pMzIAAA==", + "AGFzbQEAAAABBQFgAX0AAhEBBHRlc3QIZnVuYy1pMzIAAA==", + "AGFzbQEAAAABBQFgAX4AAhEBBHRlc3QIZnVuYy1pMzIAAA==", + "AGFzbQEAAAABBgFgAX8BfwIRAQR0ZXN0CGZ1bmMtaTMyAAA=", + "AGFzbQEAAAABBAFgAAACEgEEdGVzdAlmdW5jLT5pMzIAAA==", + "AGFzbQEAAAABBQFgAX8AAhIBBHRlc3QJZnVuYy0+aTMyAAA=", + "AGFzbQEAAAABBQFgAAF9AhIBBHRlc3QJZnVuYy0+aTMyAAA=", + "AGFzbQEAAAABBQFgAAF+AhIBBHRlc3QJZnVuYy0+aTMyAAA=", + "AGFzbQEAAAABBgFgAX8BfwISAQR0ZXN0CWZ1bmMtPmkzMgAA", + "AGFzbQEAAAABBAFgAAACFgEEdGVzdA1mdW5jLWkzMi0+aTMyAAA=", + "AGFzbQEAAAABBQFgAX8AAhYBBHRlc3QNZnVuYy1pMzItPmkzMgAA", + "AGFzbQEAAAABBQFgAAF/AhYBBHRlc3QNZnVuYy1pMzItPmkzMgAA", + "AGFzbQEAAAABBQFgAAF/AhMBBHRlc3QKZ2xvYmFsLWkzMgAA", + "AGFzbQEAAAABBAFgAAACFQEEdGVzdAx0YWJsZS0xMC1pbmYAAA==", + "AGFzbQEAAAABBAFgAAACFQEEdGVzdAxtZW1vcnktMi1pbmYAAA==", + "AGFzbQEAAAABBAFgAAACFwEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgAA", + "AGFzbQEAAAABBAFgAAACEgEIc3BlY3Rlc3QFdGFibGUAAA==", + "AGFzbQEAAAABBAFgAAACEwEIc3BlY3Rlc3QGbWVtb3J5AAA=", + "AGFzbQEAAAABEQRgAAF/YAABfmAAAX1gAAF8AqIBBwhzcGVjdGVzdApnbG9iYWxfaTMyA38ACHNwZWN0ZXN0Cmdsb2JhbF9pMzIDfwAIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdApnbG9iYWxfaTMyA38ACHNwZWN0ZXN0Cmdsb2JhbF9pNjQDfgAIc3BlY3Rlc3QKZ2xvYmFsX2YzMgN9AAhzcGVjdGVzdApnbG9iYWxfZjY0A3wAAwgHAAAAAAECAwc5BwVnZXQtMAAABWdldC0xAAEFZ2V0LXgAAgVnZXQteQADBWdldC00AAQFZ2V0LTUABQVnZXQtNgAGCiQHBAAjAAsEACMBCwQAIwILBAAjAwsEACMECwQAIwULBAAjBgs=", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA30A", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN+AQ==", + "AGFzbQEAAAACEQEEdGVzdAd1bmtub3duA38A", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QHdW5rbm93bgN/AA==", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA34A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA30A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA3wA", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyA38B", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA38A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA34A", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA3wA", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtZjMyA30B", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN/AQ==", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN9AQ==", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN8AQ==", + "AGFzbQEAAAACGAEEdGVzdA5nbG9iYWwtbXV0LWk2NAN+AA==", + "AGFzbQEAAAACDgEEdGVzdARmdW5jA38A", + "AGFzbQEAAAACFgEEdGVzdAx0YWJsZS0xMC1pbmYDfwA=", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYDfwA=", + "AGFzbQEAAAACFwEIc3BlY3Rlc3QJcHJpbnRfaTMyA38A", + "AGFzbQEAAAACEwEIc3BlY3Rlc3QFdGFibGUDfwA=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5A38A", + "AGFzbQEAAAABCgJgAAF/YAF/AX8CFQEIc3BlY3Rlc3QFdGFibGUBcAEKFAMEAwEAAAcIAQRjYWxsAAAJCAEAQQELAgECChMDBwAgABEAAAsEAEELCwQAQRYL", + "AGFzbQEAAAACJwIIc3BlY3Rlc3QFdGFibGUBcAAACHNwZWN0ZXN0BXRhYmxlAXAAAAQHAnAACnAACg==", + "AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAK", + "AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAF", + "AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAA", + "AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAo=", + "AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAU=", + "AGFzbQEAAAACFgEEdGVzdAt0YWJsZS0xMC0yMAFwAAA=", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoU", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQUU", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQAU", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoZ", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQUZ", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQAZ", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAK", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAF", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAA", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKFA==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEFFA==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAFA==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKGQ==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEFGQ==", + "AGFzbQEAAAACEgEEdGVzdAd1bmtub3duAXAACg==", + "AGFzbQEAAAACFgEIc3BlY3Rlc3QHdW5rbm93bgFwAAo=", + "AGFzbQEAAAACFwEEdGVzdAx0YWJsZS0xMC1pbmYBcAAM", + "AGFzbQEAAAACGAEEdGVzdAx0YWJsZS0xMC1pbmYBcAEKFA==", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQwU", + "AGFzbQEAAAACFwEEdGVzdAt0YWJsZS0xMC0yMAFwAQoS", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAM", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEKDw==", + "AGFzbQEAAAACDwEEdGVzdARmdW5jAXAACg==", + "AGFzbQEAAAACFQEEdGVzdApnbG9iYWwtaTMyAXAACg==", + "AGFzbQEAAAACFwEEdGVzdAxtZW1vcnktMi1pbmYBcAAK", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QJcHJpbnRfaTMyAXAACg==", + "AGFzbQEAAAABBgFgAX8BfwIVAQhzcGVjdGVzdAZtZW1vcnkCAQECAwIBAAcIAQRsb2FkAAAKCQEHACAAKAIACwsHAQBBCgsBEA==", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAI=", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAE=", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAA=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAB", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAA", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAg==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAg==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAw==", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAw==", + "AGFzbQEAAAACEQEEdGVzdAd1bmtub3duAgAB", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QHdW5rbm93bgIAAQ==", + "AGFzbQEAAAACFgEEdGVzdAxtZW1vcnktMi1pbmYCAAM=", + "AGFzbQEAAAACFwEEdGVzdAxtZW1vcnktMi1pbmYCAQID", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAC", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEBAQ==", + "AGFzbQEAAAACEgEEdGVzdAhmdW5jLWkzMgIAAQ==", + "AGFzbQEAAAACFAEEdGVzdApnbG9iYWwtaTMyAgAB", + "AGFzbQEAAAACFgEEdGVzdAx0YWJsZS0xMC1pbmYCAAE=", + "AGFzbQEAAAACFwEIc3BlY3Rlc3QJcHJpbnRfaTMyAgAB", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgIAAQ==", + "AGFzbQEAAAACEwEIc3BlY3Rlc3QFdGFibGUCAAE=", + "AGFzbQEAAAABBgFgAX8BfwIVAQhzcGVjdGVzdAZtZW1vcnkCAQADAwIBAAcIAQRncm93AAAKCAEGACAAQAAL", + "AGFzbQEAAAA=", + "AGFzbQEAAAABKQpgAABgAX8AYAJ/fwBgAX4AYAF9AGABfABgAAF/YAABfmAAAX1gAAF8AugCEAhub3Qgd2FzbQpvdmVybG9hZGVkAAAIbm90IHdhc20Kb3ZlcmxvYWRlZAABCG5vdCB3YXNtCm92ZXJsb2FkZWQAAghub3Qgd2FzbQpvdmVybG9hZGVkAAMIbm90IHdhc20Kb3ZlcmxvYWRlZAAECG5vdCB3YXNtCm92ZXJsb2FkZWQABQhub3Qgd2FzbQpvdmVybG9hZGVkAAYIbm90IHdhc20Kb3ZlcmxvYWRlZAAHCG5vdCB3YXNtCm92ZXJsb2FkZWQACAhub3Qgd2FzbQpvdmVybG9hZGVkAAkIbm90IHdhc20Kb3ZlcmxvYWRlZAN/AAhub3Qgd2FzbQpvdmVybG9hZGVkA34ACG5vdCB3YXNtCm92ZXJsb2FkZWQDfQAIbm90IHdhc20Kb3ZlcmxvYWRlZAN8AAhub3Qgd2FzbQpvdmVybG9hZGVkAXAAAAhub3Qgd2FzbQpvdmVybG9hZGVkAgAA", + "AGFzbQEAAAAEBAFwAAA=", + "AGFzbQEAAAAEBAFwAAE=", + "AGFzbQEAAAAEBQFwAQAA", + "AGFzbQEAAAAEBQFwAQAB", + "AGFzbQEAAAAEBgFwAQGAAg==", + "AGFzbQEAAAAEBwFwAQCAgAQ=", + "AGFzbQEAAAAECQFwAQD/////Dw==", + "AGFzbQEAAAAEBwJwAABwAAA=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAABAQBcAAA", + "AGFzbQEAAAABBQFgAAF/AwIBAAcHAQNmb28AAAoGAQQAQQAL", + "AGFzbQEAAAABBQFgAAF/AwIBAAcHAQNmb28AAAoGAQQAQQEL", + "AGFzbQEAAAABBQFgAAF/A+ED3wMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAerId8DAAAAATAAAQItMAACAV8AAwEkAAQBQAAFIX4hQCMkJV4mKigpXytgLT17fXxbXVw6IjsnPD4/LC4vIAAGA05hTgAHCEluZmluaXR5AAgCaWYACQZtYWxsb2MACgdfbWFsbG9jAAsIX19tYWxsb2MADAFhAA0BQQAOA++7vwAPAsOFABADQcyKABED4oSrABID76yDABMEZu+sgQAUA2ZmaQAVEAABAgMEBQYHCAkKCwwNDg8AFhAQERITFBUWFxgZGhscHR4fABcCIH8AGCDCgMKBwoLCg8KEwoXChsKHwojCicKKwovCjMKNwo7CjwAZIMKQwpHCksKTwpTClcKWwpfCmMKZwprCm8Kcwp3CnsKfABoY77+w77+x77+y77+z77+077+177+277+3ABsY77+477+577+677+777+877+977++77+/ABww4pCA4pCB4pCC4pCD4pCE4pCF4pCG4pCH4pCI4pCJ4pCK4pCL4pCM4pCN4pCO4pCPAB0w4pCQ4pCR4pCS4pCT4pCU4pCV4pCW4pCX4pCY4pCZ4pCa4pCb4pCc4pCd4pCe4pCfAB4G4pCg4pChAB8q77+w77+x77+y77+z77+077+177+277+377+477+577+677+777+877+9ACAD4oCNACED4oCMACICzY8AIwPigaAAJAPitb8AJQTwkYG/ACYD4aCOACcW77+v4oCLwqDCreKBoOGagOKAruKArQAoJ+KAjuKAj+KAkeKAqOKAqeKAquKAq+KArOKAr+KBpuKBp+KBqOKBqQApEuKBquKBq+KBrOKBreKBruKBrwAqDOKBoeKBouKBo+KBpAArDPCQgIDzn7+/9I+/vwAsgQJazI/NhsyGzZvNjMy0zZjNns2HzKvMpcyqzZPNiM2UzY7Ml8yezLrMr8yxzJ7MmcyxzJzMlsygYc2XzajMjsyEzIbNl8y/zaHNn82AzLbNgcylzLDMs8ytzZnMssyxzLnMnc2OzLxszYTNisyazZfNps2EzavMh82BzLbMt82JzKnMucyrzJ3Nls2FzJnMssy8zYfNms2NzK7NjsylzYXMnmfNg8yQzIXNrsyUzJDMjsyCzI/Mvs2KzI3Ni82KzafMgcyGzabNnsy2zZXNlM2azKlvzYvMlM2QzarNqcyhzY/MosynzYHMq8yZzKTMrs2WzZnNk8y6zJzMqcy8zJjMoAAtDOGFn+GFoOOFpO++oAAuA++4gAAvA++4hAAwBPOghIAAMQTzoIevADICzIgAMwEKADQD4pCkADUD4oCoADYBDQA3Ag0KADgCCg0AOQEeADoBCwA7AQwAPALChQA9A+KAqQA+A+KApgA/A+KPjgBAAsKLAEECwowAQgLCjQBDA+KGtQBEA+KGqQBFA+KMpABGA+KktgBHA+KGsgBIA+KuqABJA+KusABKA++/vQBLA++3kABMA++3kQBNA++3kgBOA++3kwBPA++3lABQA++3lQBRA++3lgBSA++3lwBTA++3mABUA++3mQBVA++3mgBWA++3mwBXA++3nABYA++3nQBZA++3ngBaA++3nwBbA++3oABcA++3oQBdA++3ogBeA++3owBfA++3pABgA++3pQBhA++3pgBiA++3pwBjA++3qABkA++3qQBlA++3qgBmA++3qwBnA++3rABoA++3rQBpA++3rgBqA++3rwBrA++/vgBsA++/vwBtBPCfv74AbgTwn7+/AG8E8K+/vgBwBPCvv78AcQTwv7++AHIE8L+/vwBzBPGPv74AdATxj7+/AHUE8Z+/vgB2BPGfv78AdwTxr7++AHgE8a+/vwB5BPG/v74AegTxv7+/AHsE8o+/vgB8BPKPv78AfQTyn7++AH4E8p+/vwB/BPKvv74AgAEE8q+/vwCBAQTyv7++AIIBBPK/v78AgwEE84+/vgCEAQTzj7+/AIUBBPOfv74AhgEE85+/vwCHAQTzr7++AIgBBPOvv78AiQEE87+/vgCKAQTzv7+/AIsBBPSPv74AjAEE9I+/vwCNAQnMiOKAvcyIzIkAjgEDYWJjAI8BBuKArWFiYwCQAQbigK5jYmEAkQEJ4oCtYWJj4oCuAJIBCeKArmNiYeKArQCTAQTwnZGoAJQBBPCdkLQAlQEE8J2YiACWAQTwnZi8AJcBBPCdkIAAmAEE8J2TkACZAQTwnZWsAJoBBPCdl5QAmwEE8J2SnACcAQTwnZSEAJ0BBPCdlLgAngEE8J2WoACfAQTwnZmwAKABA+G0gAChAQPhtKwAogED4pK2AKMBA++8oQCkAQTwn4SQAKUBBPCfhLAApgEE86CBgQCnAQZVKzAwNDEAqAEEQeKAiwCpAQLQkACqAQPqmZYAqwED4re8AKwBA+K3tgCtAQPisa8ArgEE8J+FkACvAQTwn4WwALABA+KwrQCxAQTwkJCCALIBBPCQkIgAswEE8JCSsAC0AQLDgAC1AQLDgQC2AQLDggC3AQLDgwC4AQLDhAC5AQLEgAC6AQLEggC7AQLEhAC8AQLHjQC9AQLHngC+AQLHoAC/AQLHugDAAQLIgADBAQLIggDCAQLIpgDDAQLIugDEAQLTkADFAQLTkgDGAQLfigDHAQPgoKEAyAED4KCiAMkBA+CgowDKAQPgoKQAywED4KClAMwBA+CkhADNAQPgpIUAzgED4KWyAM8BA+CmhQDQAQPgqIUA0QED4KqFANIBA+CshQDTAQPgroUA1AED4LCFANUBA+CyhQDWAQPgtIUA1wED4LiwANgBA+C6sADZAQPgvIEA2gED4L2oANsBA+C+uADcAQPhgKEA3QED4YCiAN4BA+GCnADfAQPhhaEA4AED4YqgAOEBA+GLkADiAQPhjqAA4wED4ZCKAOQBA+GWswDlAQPhmqgA5gED4ZqqAOcBA+GbhgDoAQPhnIAA6QED4ZygAOoBA+GdgADrAQPhnaAA7AED4aCgAO0BA+GihwDuAQPhpKAA7wED4aWjAPABA+GolQDxAQPhqYsA8gED4amhAPMBA+GugwD0AQPhr4AA9QED4a+BAPYBA+GwowD3AQPhuIAA+AED4bqgAPkBA+G6ogD6AQPhuqQA+wED4bqmAPwBA+G6qAD9AQPhuqoA/gED4bqsAP8BA+G6rgCAAgPhurAAgQID4bqyAIICA+G6tACDAgPhurYAhAID44GCAIUCA+OCogCGAgPjhJoAhwID44WPAIgCA+OIjgCJAgPjiI8AigID44iQAIsCA+OIkQCMAgPjiJIAjQID44iTAI4CA+OIlACPAgPjiJUAkAID44iWAJECA+OIlwCSAgPjiJgAkwID44iZAJQCA+OImgCVAgPjiJsAlgID44muAJcCA+OJrwCYAgPjibAAmQID44mxAJoCA+OJsgCbAgPjibMAnAID44m0AJ0CA+OJtQCeAgPjibYAnwID44m3AKACA+OJuAChAgPjibkAogID44m6AKMCA+OJuwCkAgPji5AApQID6oCKAKYCA+qTrgCnAgPqlYkAqAID6pqgAKkCA+qggACqAgPqoKMAqwID6qGdAKwCA+qiggCtAgPqo6oArgID6qSiAK8CA+qlhgCwAgPqpoQAsQID6qiAALICA++9sQCzAgPvv4IAtAIE8JCAgAC1AgTwkIqAALYCBPCQiqAAtwIE8JCMgAC4AgTwkI6gALkCBPCQkpYAugIE8JCUgAC7AgTwkJ2AALwCBPCQoIAAvQIE8JCkoAC+AgTwkKaAAL8CBPCQpqAAwAIE8JCogADBAgTwkKyAAMICBPCQsIAAwwIE8JCwgQDEAgTwkLKAAMUCBPCRgIUAxgIE8JGCgwDHAgTwkYSnAMgCBPCRhZAAyQIE8JGGgwDKAgTwkYiAAMsCBPCRioAAzAIE8JGKsADNAgTwkYyFAM4CBPCRjbAAzwIE8JGQgADQAgTwkZKBANECBPCRloAA0gIE8JGYgADTAgTwkZqAANQCBPCRnJIA1QIE8JGcoADWAgTwkaKhANcCBPCRq5UA2AIE8JGwgADZAgTwkbKPANoCBPCRsq8A2wIE8JKAgADcAgTwlqeVAN0CBPCWqYYA3gIE8JarpwDfAgTwlr2UAOACBPCbsYEA4QIE8JuxpADiAgTwnqCjAOMCBPCfh6YA5AID4rGtAOUCAs6bAOYCA+KxsADnAgLCqgDoAgPiiIAA6QID4oKzAOoCBPCQpIAA6wID4rKAAOwCBPCQjLAA7QICzoYA7gICzpEA7wID4byIAPACA+G8iQDxAgPhvIoA8gID4byLAPMCA+G8jAD0AgPhvI0A9QID4byOAPYCA+G8jwD3AgPhvogA+AID4b6JAPkCA+G+igD6AgPhvosA+wID4b6MAPwCA+G+jQD9AgPhvo4A/gID4b6PAP8CA+G+uACAAwPhvrkAgQMD4b66AIIDA+G+uwCDAwPhvrwAhAME8J2aqACFAwTwnZuiAIYDBPCdnJwAhwME8J2dlgCIAwTwnZ6QAIkDA+KNtgCKAwPijboAiwMD4qmcAIwDA+GXhQCNAwPhjqoAjgOcASnLusu88JSXj/CdhbTwnYW28J2FuPCdhbrigb7igo7inaninavin6/vtL/vuLbvuZrvvInvvaDzoICp4p2z4p214p+n4p+p4p+r4p+t4qaI4qaK4qaW4rij4ril77iY77i477i677i877i+77mA77mC77mE77mI77mc77me77y9772d772j86CBnfOggb3Cu+KAmeKAneKAuuKdrwCPA5wBKMu5y7vwlJeO8J2Fs/CdhbXwnYW38J2FueKBveKCjeKdqOKdquKfru+0vu+4te+5me+8iO+9n/OggKjinbLinbTin6bin6jin6rin6zipofiponippXiuKLiuKTvuJfvuLfvuLnvuLvvuL3vuL/vuYHvuYPvuYfvuZvvuZ3vvLvvvZvvvaLzoIGb86CBu8Kr4oCY4oCc4oC54p2uAJADCPCdqovwnaqkAJEDBPCdqosAkgMCwr0AkwMFMeKBhDIAlAMDMS8yAJUDA+CtswCWAwPgtbQAlwMD4rO9AJgDA+qgsQCZAwTwkIWBAJoDBPCQhbUAmwME8JCFtgCcAwTwkKa9AJ0DBPCQubsAngMD77yCAJ8DAX8AoAMBCAChAwPijKsAogMD4oymAKMDA+KQiACkAwPikKEApQMD4be7AKYDAQ8ApwMD4oaQAKgDA+KMpwCpAwPijZIAqgMD4o2UAKsDA+KNogCsAwPijasArQMBGgCuAwPikKYArwMD4pCaALADA++/vACxAwE/ALIDAsK/ALMDA+GlhQC0AwLNvgC1AwLVngC2AwLYnwC3AwPhjacAuAMD4oGHALkDA+KNsAC6AwPinZMAuwMD4p2UALwDA+KzugC9AwPis7sAvgMD4riuAL8DA+OJhADAAwPqmI8AwQMD6pu3AMIDA++4lgDDAwPvuZYAxAMD77yfAMUDBPCRhYMAxgME8J6lnwDHAwTzoIC/AMgDBPCWoYQAyQMD4q+RAMoDAsK2AMsDA+KBiwDMAwLcgADNAwPhg7sAzgMD4Y2oAM8DA+OAtwDQAwPinaEA0QMD4riPANIDA+K4kADTAwPiuJEA1AMD4riOANUDARQA1gMD4piZANcDA+K4vwDYAwPjgIcA2QMD4LmbANoDA+qZrgDbAwLPkwDcAwLPlADdAwPhupsA3gMK/BXfAwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBABBEAsEAEERCwQAQRILBABBEwsEAEEUCwQAQRULBABBFgsEAEEXCwQAQRgLBABBGQsEAEEaCwQAQRsLBABBHAsEAEEdCwQAQR4LBABBHwsEAEEgCwQAQSELBABBIgsEAEEjCwQAQSQLBABBJQsEAEEmCwQAQScLBABBKAsEAEEpCwQAQSoLBABBKwsEAEEsCwQAQS0LBABBLgsEAEEvCwQAQTALBABBMQsEAEEyCwQAQTMLBABBNAsEAEE1CwQAQTYLBABBNwsEAEE4CwQAQTkLBABBOgsEAEE7CwQAQTwLBABBPQsEAEE+CwQAQT8LBQBBwAALBQBBwQALBQBBwgALBQBBwwALBQBBxAALBQBBxQALBQBBxgALBQBBxwALBQBByAALBQBByQALBQBBygALBQBBywALBQBBzAALBQBBzQALBQBBzgALBQBBzwALBQBB0AALBQBB0QALBQBB0gALBQBB0wALBQBB1AALBQBB1QALBQBB1gALBQBB1wALBQBB2AALBQBB2QALBQBB2gALBQBB2wALBQBB3AALBQBB3QALBQBB3gALBQBB3wALBQBB4AALBQBB4QALBQBB4gALBQBB4wALBQBB5AALBQBB5QALBQBB5gALBQBB5wALBQBB6AALBQBB6QALBQBB6gALBQBB6wALBQBB7AALBQBB7QALBQBB7gALBQBB7wALBQBB8AALBQBB8QALBQBB8gALBQBB8wALBQBB9AALBQBB9QALBQBB9gALBQBB9wALBQBB+AALBQBB+QALBQBB+gALBQBB+wALBQBB/AALBQBB/QALBQBB/gALBQBB/wALBQBBgAELBQBBgQELBQBBggELBQBBgwELBQBBhAELBQBBhQELBQBBhgELBQBBhwELBQBBiAELBQBBiQELBQBBigELBQBBiwELBQBBjAELBQBBjQELBQBBjgELBQBBjwELBQBBkAELBQBBkQELBQBBkgELBQBBkwELBQBBlAELBQBBlQELBQBBlgELBQBBlwELBQBBmAELBQBBmQELBQBBmgELBQBBmwELBQBBnAELBQBBnQELBQBBngELBQBBnwELBQBBoAELBQBBoQELBQBBogELBQBBowELBQBBpAELBQBBpQELBQBBpgELBQBBpwELBQBBqAELBQBBqQELBQBBqgELBQBBqwELBQBBrAELBQBBrQELBQBBrgELBQBBrwELBQBBsAELBQBBsQELBQBBsgELBQBBswELBQBBtAELBQBBtQELBQBBtgELBQBBtwELBQBBuAELBQBBuQELBQBBugELBQBBuwELBQBBvAELBQBBvQELBQBBvgELBQBBvwELBQBBwAELBQBBwQELBQBBwgELBQBBwwELBQBBxAELBQBBxQELBQBBxgELBQBBxwELBQBByAELBQBByQELBQBBygELBQBBywELBQBBzAELBQBBzQELBQBBzgELBQBBzwELBQBB0AELBQBB0QELBQBB0gELBQBB0wELBQBB1AELBQBB1QELBQBB1gELBQBB1wELBQBB2AELBQBB2QELBQBB2gELBQBB2wELBQBB3AELBQBB3QELBQBB3gELBQBB3wELBQBB4AELBQBB4QELBQBB4gELBQBB4wELBQBB5AELBQBB5QELBQBB5gELBQBB5wELBQBB6AELBQBB6QELBQBB6gELBQBB6wELBQBB7AELBQBB7QELBQBB7gELBQBB7wELBQBB8AELBQBB8QELBQBB8gELBQBB8wELBQBB9AELBQBB9QELBQBB9gELBQBB9wELBQBB+AELBQBB+QELBQBB+gELBQBB+wELBQBB/AELBQBB/QELBQBB/gELBQBB/wELBQBBgAILBQBBgQILBQBBggILBQBBgwILBQBBhAILBQBBhQILBQBBhgILBQBBhwILBQBBiAILBQBBiQILBQBBigILBQBBiwILBQBBjAILBQBBjQILBQBBjgILBQBBjwILBQBBkAILBQBBkQILBQBBkgILBQBBkwILBQBBlAILBQBBlQILBQBBlgILBQBBlwILBQBBmAILBQBBmQILBQBBmgILBQBBmwILBQBBnAILBQBBnQILBQBBngILBQBBnwILBQBBoAILBQBBoQILBQBBogILBQBBowILBQBBpAILBQBBpQILBQBBpgILBQBBpwILBQBBqAILBQBBqQILBQBBqgILBQBBqwILBQBBrAILBQBBrQILBQBBrgILBQBBrwILBQBBsAILBQBBsQILBQBBsgILBQBBswILBQBBtAILBQBBtQILBQBBtgILBQBBtwILBQBBuAILBQBBuQILBQBBugILBQBBuwILBQBBvAILBQBBvQILBQBBvgILBQBBvwILBQBBwAILBQBBwQILBQBBwgILBQBBwwILBQBBxAILBQBBxQILBQBBxgILBQBBxwILBQBByAILBQBByQILBQBBygILBQBBywILBQBBzAILBQBBzQILBQBBzgILBQBBzwILBQBB0AILBQBB0QILBQBB0gILBQBB0wILBQBB1AILBQBB1QILBQBB1gILBQBB1wILBQBB2AILBQBB2QILBQBB2gILBQBB2wILBQBB3AILBQBB3QILBQBB3gILBQBB3wILBQBB4AILBQBB4QILBQBB4gILBQBB4wILBQBB5AILBQBB5QILBQBB5gILBQBB5wILBQBB6AILBQBB6QILBQBB6gILBQBB6wILBQBB7AILBQBB7QILBQBB7gILBQBB7wILBQBB8AILBQBB8QILBQBB8gILBQBB8wILBQBB9AILBQBB9QILBQBB9gILBQBB9wILBQBB+AILBQBB+QILBQBB+gILBQBB+wILBQBB/AILBQBB/QILBQBB/gILBQBB/wILBQBBgAMLBQBBgQMLBQBBggMLBQBBgwMLBQBBhAMLBQBBhQMLBQBBhgMLBQBBhwMLBQBBiAMLBQBBiQMLBQBBigMLBQBBiwMLBQBBjAMLBQBBjQMLBQBBjgMLBQBBjwMLBQBBkAMLBQBBkQMLBQBBkgMLBQBBkwMLBQBBlAMLBQBBlQMLBQBBlgMLBQBBlwMLBQBBmAMLBQBBmQMLBQBBmgMLBQBBmwMLBQBBnAMLBQBBnQMLBQBBngMLBQBBnwMLBQBBoAMLBQBBoQMLBQBBogMLBQBBowMLBQBBpAMLBQBBpQMLBQBBpgMLBQBBpwMLBQBBqAMLBQBBqQMLBQBBqgMLBQBBqwMLBQBBrAMLBQBBrQMLBQBBrgMLBQBBrwMLBQBBsAMLBQBBsQMLBQBBsgMLBQBBswMLBQBBtAMLBQBBtQMLBQBBtgMLBQBBtwMLBQBBuAMLBQBBuQMLBQBBugMLBQBBuwMLBQBBvAMLBQBBvQMLBQBBvgMLBQBBvwMLBQBBwAMLBQBBwQMLBQBBwgMLBQBBwwMLBQBBxAMLBQBBxQMLBQBBxgMLBQBBxwMLBQBByAMLBQBByQMLBQBBygMLBQBBywMLBQBBzAMLBQBBzQMLBQBBzgMLBQBBzwMLBQBB0AMLBQBB0QMLBQBB0gMLBQBB0wMLBQBB1AMLBQBB1QMLBQBB1gMLBQBB1wMLBQBB2AMLBQBB2QMLBQBB2gMLBQBB2wMLBQBB3AMLBQBB3QMLBQBB3gML", + "AGFzbQEAAAABCgJgAX8AYAJ/fwACKwIIc3BlY3Rlc3QJcHJpbnRfaTMyAAAIc3BlY3Rlc3QJcHJpbnRfaTMyAAADAgEBBwsBB3ByaW50MzIAAgoMAQoAIAAQACABEAEL", +]; +export const script = "[[\"file\",\"imports.wast\"],[\"module\",3,0],[\"register\",\"test\"],[\"module\",26,1],[\"invoke\",85,0,\"print32\",[\"i32:13\"]],[\"invoke\",86,0,\"print64\",[\"i64:24\"]],[\"module\",97,2],[\"invoke\",104,0,\"print_i32\",[\"i32:13\"]],[\"module\",107,3],[\"invoke\",114,0,\"print_i32\",[\"i32:5\",\"i32:11\"]],[\"module\",116,4],[\"module\",117,5],[\"module\",118,6],[\"module\",119,7],[\"module\",120,8],[\"module\",121,9],[\"module\",122,10],[\"instantiate\",125,11],[\"instantiate\",129,12],[\"instantiate\",134,13],[\"instantiate\",138,14],[\"instantiate\",142,15],[\"instantiate\",146,16],[\"instantiate\",150,17],[\"instantiate\",154,18],[\"instantiate\",158,19],[\"instantiate\",162,20],[\"instantiate\",166,21],[\"instantiate\",170,22],[\"instantiate\",174,23],[\"instantiate\",178,24],[\"instantiate\",182,25],[\"instantiate\",186,26],[\"instantiate\",190,27],[\"instantiate\",194,28],[\"instantiate\",199,29],[\"instantiate\",203,30],[\"instantiate\",207,31],[\"instantiate\",211,32],[\"instantiate\",215,33],[\"instantiate\",219,34],[\"module\",226,35],[\"invoke\",246,0,\"get-0\",[]],[\"invoke\",247,0,\"get-1\",[]],[\"invoke\",248,0,\"get-x\",[]],[\"invoke\",249,0,\"get-y\",[]],[\"invoke\",250,0,\"get-4\",[]],[\"invoke\",251,0,\"get-5\",[]],[\"invoke\",252,0,\"get-6\",[]],[\"module\",254,36],[\"module\",255,37],[\"module\",256,38],[\"instantiate\",259,39],[\"instantiate\",263,40],[\"instantiate\",268,41],[\"instantiate\",272,42],[\"instantiate\",276,43],[\"instantiate\",280,44],[\"instantiate\",284,45],[\"instantiate\",288,46],[\"instantiate\",292,47],[\"instantiate\",296,48],[\"instantiate\",300,49],[\"instantiate\",304,50],[\"instantiate\",308,51],[\"instantiate\",312,52],[\"instantiate\",317,53],[\"instantiate\",321,54],[\"instantiate\",325,55],[\"instantiate\",329,56],[\"instantiate\",333,57],[\"instantiate\",337,58],[\"module\",344,59],[\"invoke\",356,0,\"call\",[\"i32:0\"]],[\"invoke\",357,0,\"call\",[\"i32:1\"]],[\"invoke\",358,0,\"call\",[\"i32:2\"]],[\"invoke\",359,0,\"call\",[\"i32:3\"]],[\"invoke\",360,0,\"call\",[\"i32:100\"]],[\"module\",363,59],[\"invoke\",375,0,\"call\",[\"i32:0\"]],[\"invoke\",376,0,\"call\",[\"i32:1\"]],[\"invoke\",377,0,\"call\",[\"i32:2\"]],[\"invoke\",378,0,\"call\",[\"i32:3\"]],[\"invoke\",379,0,\"call\",[\"i32:100\"]],[\"module\",381,60],[\"module\",388,61],[\"module\",389,62],[\"module\",390,63],[\"module\",391,64],[\"module\",392,65],[\"module\",393,66],[\"module\",394,67],[\"module\",395,68],[\"module\",396,69],[\"module\",397,70],[\"module\",398,71],[\"module\",399,72],[\"module\",400,73],[\"module\",401,74],[\"module\",402,75],[\"module\",403,76],[\"module\",404,77],[\"module\",405,78],[\"module\",406,79],[\"module\",407,80],[\"instantiate\",410,81],[\"instantiate\",414,82],[\"instantiate\",419,83],[\"instantiate\",423,84],[\"instantiate\",427,85],[\"instantiate\",431,86],[\"instantiate\",435,87],[\"instantiate\",439,88],[\"instantiate\",444,89],[\"instantiate\",448,90],[\"instantiate\",452,91],[\"instantiate\",456,92],[\"module\",464,93],[\"invoke\",471,0,\"load\",[\"i32:0\"]],[\"invoke\",472,0,\"load\",[\"i32:10\"]],[\"invoke\",473,0,\"load\",[\"i32:8\"]],[\"invoke\",474,0,\"load\",[\"i32:1000000\"]],[\"module\",476,93],[\"invoke\",482,0,\"load\",[\"i32:0\"]],[\"invoke\",483,0,\"load\",[\"i32:10\"]],[\"invoke\",484,0,\"load\",[\"i32:8\"]],[\"invoke\",485,0,\"load\",[\"i32:1000000\"]],[\"module\",500,94],[\"module\",501,95],[\"module\",502,96],[\"module\",503,97],[\"module\",504,98],[\"module\",505,99],[\"module\",506,100],[\"module\",507,101],[\"module\",508,102],[\"instantiate\",511,103],[\"instantiate\",515,104],[\"instantiate\",520,105],[\"instantiate\",524,106],[\"instantiate\",528,107],[\"instantiate\",532,108],[\"instantiate\",537,109],[\"instantiate\",541,110],[\"instantiate\",545,111],[\"instantiate\",549,112],[\"instantiate\",553,113],[\"instantiate\",557,114],[\"instantiate\",562,107],[\"instantiate\",566,108],[\"module\",570,115],[\"invoke\",574,0,\"grow\",[\"i32:0\"]],[\"invoke\",575,0,\"grow\",[\"i32:1\"]],[\"invoke\",576,0,\"grow\",[\"i32:0\"]],[\"invoke\",577,0,\"grow\",[\"i32:1\"]],[\"invoke\",578,0,\"grow\",[\"i32:0\"]],[\"module\",654,116],[\"register\",\"not wasm\"],[\"instantiate\",657,117],[\"file\",\"table.wast\"],[\"module\",3,118],[\"module\",4,119],[\"module\",5,120],[\"module\",6,121],[\"module\",7,122],[\"module\",8,123],[\"module\",9,124],[\"module\",11,125],[\"module\",12,126],[\"file\",\"names.wast\"],[\"module\",4,127],[\"invoke\",8,0,\"foo\",[]],[\"module\",12,128],[\"module\",19,129],[\"invoke\",615,0,\"\",[]],[\"invoke\",616,0,\"0\",[]],[\"invoke\",617,0,\"-0\",[]],[\"invoke\",618,0,\"_\",[]],[\"invoke\",620,0,\"@\",[]],[\"invoke\",621,0,\"~!@#$%^&*()_+`-={}|[]\\\\:\\\";'<>?,./ \",[]],[\"invoke\",622,0,\"NaN\",[]],[\"invoke\",623,0,\"Infinity\",[]],[\"invoke\",624,0,\"if\",[]],[\"invoke\",626,0,\"_malloc\",[]],[\"invoke\",627,0,\"__malloc\",[]],[\"invoke\",628,0,\"a\",[]],[\"invoke\",629,0,\"A\",[]],[\"invoke\",630,0,\"\u{feff}\",[]],[\"invoke\",632,0,\"Å\",[]],[\"invoke\",633,0,\"Å\",[]],[\"invoke\",634,0,\"ffi\",[]],[\"invoke\",635,0,\"ffi\",[]],[\"invoke\",636,0,\"ffi\",[]],[\"invoke\",638,0,\"\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\",[]],[\"invoke\",639,0,\" \u{7f}\",[]],[\"invoke\",640,0,\"\u{80}\u{81}\u{82}\u{83}\u{84}\u{85}\u{86}\u{87}\u{88}\u{89}\u{8a}\u{8b}\u{8c}\u{8d}\u{8e}\u{8f}\",[]],[\"invoke\",641,0,\"\u{90}\u{91}\u{92}\u{93}\u{94}\u{95}\u{96}\u{97}\u{98}\u{99}\u{9a}\u{9b}\u{9c}\u{9d}\u{9e}\u{9f}\",[]],[\"invoke\",642,0,\"￰￱￲￳￴￵￶￷\",[]],[\"invoke\",644,0,\"␀␁␂␃␄␅␆␇␈␉␊␋␌␍␎␏\",[]],[\"invoke\",645,0,\"␐␑␒␓␔␕␖␗␘␙␚␛␜␝␞␟\",[]],[\"invoke\",646,0,\"␠␡\",[]],[\"invoke\",647,0,\"￰￱￲￳￴￵￶￷￸\u{fff9}\u{fffa}\u{fffb}�\",[]],[\"invoke\",648,0,\"\u{200d}\",[]],[\"invoke\",650,0,\"͏\",[]],[\"invoke\",651,0,\"\u{2060}\",[]],[\"invoke\",652,0,\"⵿\",[]],[\"invoke\",653,0,\"𑁿\",[]],[\"invoke\",654,0,\"\u{180e}\",[]],[\"invoke\",656,0,\"\u{200e}\u{200f}‑\u{2028}\u{2029}\u{202a}\u{202b}\u{202c} \u{2066}\u{2067}\u{2068}\u{2069}\",[]],[\"invoke\",657,0,\"\u{206a}\u{206b}\u{206c}\u{206d}\u{206e}\u{206f}\",[]],[\"invoke\",658,0,\"\u{2061}\u{2062}\u{2063}\u{2064}\",[]],[\"invoke\",659,0,\"𐀀󟿿􏿿\",[]],[\"invoke\",660,0,\"Z̴͇̫̥̪͓͈͔͎̗̞̺̯̱̞̙̱̜̖̠̏͆̆͛͌͘͞ḁ̶̰̳̭͙̲̱̹̝͎̼͗ͨ̎̄̆͗̿̀́͟͡l̶̷͉̩̹̫̝͖̙̲̼͇͚͍̮͎̥̞̈́͊͗ͦ̈́ͫ̇́̚ͅͅg̶͕͔͚̩̓̐̅ͮ̔̐̎̂̏̾͊̍͋͊ͧ́̆ͦ͞o̡͋̔͐ͪͩ͏̢̧̫̙̤̮͖͙͓̺̜̩̼̘̠́\",[]],[\"invoke\",662,0,\"︀\",[]],[\"invoke\",663,0,\"︄\",[]],[\"invoke\",664,0,\"󠄀\",[]],[\"invoke\",665,0,\"󠇯\",[]],[\"invoke\",667,0,\"\\n\",[]],[\"invoke\",668,0,\"␤\",[]],[\"invoke\",669,0,\"\u{2028}\",[]],[\"invoke\",670,0,\"\\r\",[]],[\"invoke\",671,0,\"\\r\\n\",[]],[\"invoke\",673,0,\"\\u001e\",[]],[\"invoke\",674,0,\"\\u000b\",[]],[\"invoke\",675,0,\"\\f\",[]],[\"invoke\",676,0,\"\u{85}\",[]],[\"invoke\",677,0,\"\u{2029}\",[]],[\"invoke\",679,0,\"⏎\",[]],[\"invoke\",680,0,\"\u{8b}\",[]],[\"invoke\",681,0,\"\u{8c}\",[]],[\"invoke\",682,0,\"\u{8d}\",[]],[\"invoke\",683,0,\"↵\",[]],[\"invoke\",685,0,\"⌤\",[]],[\"invoke\",686,0,\"⤶\",[]],[\"invoke\",687,0,\"↲\",[]],[\"invoke\",688,0,\"⮨\",[]],[\"invoke\",689,0,\"⮰\",[]],[\"invoke\",691,0,\"﷐\",[]],[\"invoke\",692,0,\"﷑\",[]],[\"invoke\",693,0,\"﷒\",[]],[\"invoke\",694,0,\"﷓\",[]],[\"invoke\",695,0,\"﷔\",[]],[\"invoke\",697,0,\"﷖\",[]],[\"invoke\",698,0,\"﷗\",[]],[\"invoke\",699,0,\"﷘\",[]],[\"invoke\",700,0,\"﷙\",[]],[\"invoke\",701,0,\"﷚\",[]],[\"invoke\",703,0,\"﷜\",[]],[\"invoke\",704,0,\"﷝\",[]],[\"invoke\",705,0,\"﷞\",[]],[\"invoke\",706,0,\"﷟\",[]],[\"invoke\",707,0,\"﷠\",[]],[\"invoke\",709,0,\"﷢\",[]],[\"invoke\",710,0,\"﷣\",[]],[\"invoke\",711,0,\"﷤\",[]],[\"invoke\",712,0,\"﷥\",[]],[\"invoke\",714,0,\"﷧\",[]],[\"invoke\",715,0,\"﷨\",[]],[\"invoke\",716,0,\"﷩\",[]],[\"invoke\",717,0,\"﷪\",[]],[\"invoke\",718,0,\"﷫\",[]],[\"invoke\",720,0,\"﷭\",[]],[\"invoke\",721,0,\"﷮\",[]],[\"invoke\",722,0,\"﷯\",[]],[\"invoke\",723,0,\"￾\",[]],[\"invoke\",724,0,\"￿\",[]],[\"invoke\",726,0,\"🿿\",[]],[\"invoke\",727,0,\"𯿾\",[]],[\"invoke\",728,0,\"𯿿\",[]],[\"invoke\",729,0,\"𿿾\",[]],[\"invoke\",730,0,\"𿿿\",[]],[\"invoke\",732,0,\"񏿿\",[]],[\"invoke\",733,0,\"񟿾\",[]],[\"invoke\",734,0,\"񟿿\",[]],[\"invoke\",735,0,\"񯿾\",[]],[\"invoke\",736,0,\"񯿿\",[]],[\"invoke\",738,0,\"񿿿\",[]],[\"invoke\",739,0,\"򏿾\",[]],[\"invoke\",740,0,\"򏿿\",[]],[\"invoke\",741,0,\"򟿾\",[]],[\"invoke\",742,0,\"򟿿\",[]],[\"invoke\",744,0,\"򯿿\",[]],[\"invoke\",745,0,\"򿿾\",[]],[\"invoke\",746,0,\"򿿿\",[]],[\"invoke\",747,0,\"󏿾\",[]],[\"invoke\",748,0,\"󏿿\",[]],[\"invoke\",750,0,\"󟿿\",[]],[\"invoke\",751,0,\"󯿾\",[]],[\"invoke\",752,0,\"󯿿\",[]],[\"invoke\",753,0,\"󿿾\",[]],[\"invoke\",754,0,\"󿿿\",[]],[\"invoke\",756,0,\"􏿿\",[]],[\"invoke\",757,0,\"̈‽̈̉\",[]],[\"invoke\",758,0,\"abc\",[]],[\"invoke\",759,0,\"\u{202d}abc\",[]],[\"invoke\",761,0,\"\u{202d}abc\u{202e}\",[]],[\"invoke\",762,0,\"\u{202e}cba\u{202d}\",[]],[\"invoke\",763,0,\"𝑨\",[]],[\"invoke\",764,0,\"𝐴\",[]],[\"invoke\",765,0,\"𝘈\",[]],[\"invoke\",767,0,\"𝐀\",[]],[\"invoke\",768,0,\"𝓐\",[]],[\"invoke\",769,0,\"𝕬\",[]],[\"invoke\",770,0,\"𝗔\",[]],[\"invoke\",771,0,\"𝒜\",[]],[\"invoke\",773,0,\"𝔸\",[]],[\"invoke\",774,0,\"𝖠\",[]],[\"invoke\",775,0,\"𝙰\",[]],[\"invoke\",776,0,\"ᴀ\",[]],[\"invoke\",777,0,\"ᴬ\",[]],[\"invoke\",779,0,\"A\",[]],[\"invoke\",780,0,\"🄐\",[]],[\"invoke\",781,0,\"🄰\",[]],[\"invoke\",782,0,\"\u{e0041}\",[]],[\"invoke\",783,0,\"U+0041\",[]],[\"invoke\",785,0,\"А\",[]],[\"invoke\",786,0,\"Ꙗ\",[]],[\"invoke\",787,0,\"ⷼ\",[]],[\"invoke\",788,0,\"ⷶ\",[]],[\"invoke\",789,0,\"Ɐ\",[]],[\"invoke\",791,0,\"🅰\",[]],[\"invoke\",792,0,\"Ⱝ\",[]],[\"invoke\",793,0,\"𐐂\",[]],[\"invoke\",794,0,\"𐐈\",[]],[\"invoke\",795,0,\"𐒰\",[]],[\"invoke\",797,0,\"Á\",[]],[\"invoke\",798,0,\"Â\",[]],[\"invoke\",799,0,\"Ã\",[]],[\"invoke\",800,0,\"Ä\",[]],[\"invoke\",801,0,\"Ā\",[]],[\"invoke\",803,0,\"Ą\",[]],[\"invoke\",804,0,\"Ǎ\",[]],[\"invoke\",805,0,\"Ǟ\",[]],[\"invoke\",806,0,\"Ǡ\",[]],[\"invoke\",808,0,\"Ȁ\",[]],[\"invoke\",809,0,\"Ȃ\",[]],[\"invoke\",810,0,\"Ȧ\",[]],[\"invoke\",811,0,\"Ⱥ\",[]],[\"invoke\",812,0,\"Ӑ\",[]],[\"invoke\",814,0,\"ߊ\",[]],[\"invoke\",815,0,\"ࠡ\",[]],[\"invoke\",816,0,\"ࠢ\",[]],[\"invoke\",817,0,\"ࠣ\",[]],[\"invoke\",818,0,\"ࠤ\",[]],[\"invoke\",820,0,\"ऄ\",[]],[\"invoke\",821,0,\"अ\",[]],[\"invoke\",822,0,\"ॲ\",[]],[\"invoke\",823,0,\"অ\",[]],[\"invoke\",824,0,\"ਅ\",[]],[\"invoke\",826,0,\"ଅ\",[]],[\"invoke\",827,0,\"அ\",[]],[\"invoke\",828,0,\"అ\",[]],[\"invoke\",829,0,\"ಅ\",[]],[\"invoke\",830,0,\"അ\",[]],[\"invoke\",832,0,\"ະ\",[]],[\"invoke\",833,0,\"༁\",[]],[\"invoke\",834,0,\"ཨ\",[]],[\"invoke\",835,0,\"ྸ\",[]],[\"invoke\",836,0,\"အ\",[]],[\"invoke\",838,0,\"ႜ\",[]],[\"invoke\",839,0,\"ᅡ\",[]],[\"invoke\",840,0,\"አ\",[]],[\"invoke\",841,0,\"ዐ\",[]],[\"invoke\",842,0,\"Ꭰ\",[]],[\"invoke\",844,0,\"ᖳ\",[]],[\"invoke\",845,0,\"ᚨ\",[]],[\"invoke\",846,0,\"ᚪ\",[]],[\"invoke\",847,0,\"ᛆ\",[]],[\"invoke\",848,0,\"ᜀ\",[]],[\"invoke\",850,0,\"ᝀ\",[]],[\"invoke\",851,0,\"ᝠ\",[]],[\"invoke\",852,0,\"ᠠ\",[]],[\"invoke\",853,0,\"ᢇ\",[]],[\"invoke\",855,0,\"ᥣ\",[]],[\"invoke\",856,0,\"ᨕ\",[]],[\"invoke\",857,0,\"ᩋ\",[]],[\"invoke\",858,0,\"ᩡ\",[]],[\"invoke\",859,0,\"ᮃ\",[]],[\"invoke\",861,0,\"ᯁ\",[]],[\"invoke\",862,0,\"ᰣ\",[]],[\"invoke\",863,0,\"Ḁ\",[]],[\"invoke\",864,0,\"Ạ\",[]],[\"invoke\",865,0,\"Ả\",[]],[\"invoke\",867,0,\"Ầ\",[]],[\"invoke\",868,0,\"Ẩ\",[]],[\"invoke\",869,0,\"Ẫ\",[]],[\"invoke\",870,0,\"Ậ\",[]],[\"invoke\",871,0,\"Ắ\",[]],[\"invoke\",873,0,\"Ẳ\",[]],[\"invoke\",874,0,\"Ẵ\",[]],[\"invoke\",875,0,\"Ặ\",[]],[\"invoke\",876,0,\"あ\",[]],[\"invoke\",877,0,\"ア\",[]],[\"invoke\",879,0,\"ㅏ\",[]],[\"invoke\",880,0,\"㈎\",[]],[\"invoke\",881,0,\"㈏\",[]],[\"invoke\",882,0,\"㈐\",[]],[\"invoke\",883,0,\"㈑\",[]],[\"invoke\",885,0,\"㈓\",[]],[\"invoke\",886,0,\"㈔\",[]],[\"invoke\",887,0,\"㈕\",[]],[\"invoke\",888,0,\"㈖\",[]],[\"invoke\",889,0,\"㈗\",[]],[\"invoke\",891,0,\"㈙\",[]],[\"invoke\",892,0,\"㈚\",[]],[\"invoke\",893,0,\"㈛\",[]],[\"invoke\",894,0,\"㉮\",[]],[\"invoke\",895,0,\"㉯\",[]],[\"invoke\",897,0,\"㉱\",[]],[\"invoke\",898,0,\"㉲\",[]],[\"invoke\",899,0,\"㉳\",[]],[\"invoke\",900,0,\"㉴\",[]],[\"invoke\",901,0,\"㉵\",[]],[\"invoke\",903,0,\"㉷\",[]],[\"invoke\",904,0,\"㉸\",[]],[\"invoke\",905,0,\"㉹\",[]],[\"invoke\",906,0,\"㉺\",[]],[\"invoke\",908,0,\"㋐\",[]],[\"invoke\",909,0,\"ꀊ\",[]],[\"invoke\",910,0,\"ꓮ\",[]],[\"invoke\",911,0,\"ꕉ\",[]],[\"invoke\",912,0,\"ꚠ\",[]],[\"invoke\",914,0,\"ꠣ\",[]],[\"invoke\",915,0,\"ꡝ\",[]],[\"invoke\",916,0,\"ꢂ\",[]],[\"invoke\",917,0,\"꣪\",[]],[\"invoke\",918,0,\"ꤢ\",[]],[\"invoke\",920,0,\"ꦄ\",[]],[\"invoke\",921,0,\"ꨀ\",[]],[\"invoke\",922,0,\"ア\",[]],[\"invoke\",923,0,\"ᅡ\",[]],[\"invoke\",924,0,\"𐀀\",[]],[\"invoke\",926,0,\"𐊠\",[]],[\"invoke\",927,0,\"𐌀\",[]],[\"invoke\",928,0,\"𐎠\",[]],[\"invoke\",929,0,\"𐒖\",[]],[\"invoke\",930,0,\"𐔀\",[]],[\"invoke\",932,0,\"𐠀\",[]],[\"invoke\",933,0,\"𐤠\",[]],[\"invoke\",934,0,\"𐦀\",[]],[\"invoke\",935,0,\"𐦠\",[]],[\"invoke\",936,0,\"𐨀\",[]],[\"invoke\",938,0,\"𐰀\",[]],[\"invoke\",939,0,\"𐰁\",[]],[\"invoke\",940,0,\"𐲀\",[]],[\"invoke\",941,0,\"𑀅\",[]],[\"invoke\",942,0,\"𑂃\",[]],[\"invoke\",944,0,\"𑅐\",[]],[\"invoke\",945,0,\"𑆃\",[]],[\"invoke\",946,0,\"𑈀\",[]],[\"invoke\",947,0,\"𑊀\",[]],[\"invoke\",948,0,\"𑊰\",[]],[\"invoke\",950,0,\"𑍰\",[]],[\"invoke\",951,0,\"𑐀\",[]],[\"invoke\",952,0,\"𑒁\",[]],[\"invoke\",953,0,\"𑖀\",[]],[\"invoke\",955,0,\"𑚀\",[]],[\"invoke\",956,0,\"𑜒\",[]],[\"invoke\",957,0,\"𑜠\",[]],[\"invoke\",958,0,\"𑢡\",[]],[\"invoke\",959,0,\"𑫕\",[]],[\"invoke\",961,0,\"𑲏\",[]],[\"invoke\",962,0,\"𑲯\",[]],[\"invoke\",963,0,\"𒀀\",[]],[\"invoke\",964,0,\"𖧕\",[]],[\"invoke\",965,0,\"𖩆\",[]],[\"invoke\",967,0,\"𖽔\",[]],[\"invoke\",968,0,\"𛱁\",[]],[\"invoke\",969,0,\"𛱤\",[]],[\"invoke\",970,0,\"𞠣\",[]],[\"invoke\",971,0,\"🇦\",[]],[\"invoke\",973,0,\"Λ\",[]],[\"invoke\",974,0,\"Ɒ\",[]],[\"invoke\",975,0,\"ª\",[]],[\"invoke\",976,0,\"∀\",[]],[\"invoke\",977,0,\"₳\",[]],[\"invoke\",979,0,\"Ⲁ\",[]],[\"invoke\",980,0,\"𐌰\",[]],[\"invoke\",981,0,\"Ά\",[]],[\"invoke\",982,0,\"Α\",[]],[\"invoke\",983,0,\"Ἀ\",[]],[\"invoke\",985,0,\"Ἂ\",[]],[\"invoke\",986,0,\"Ἃ\",[]],[\"invoke\",987,0,\"Ἄ\",[]],[\"invoke\",988,0,\"Ἅ\",[]],[\"invoke\",989,0,\"Ἆ\",[]],[\"invoke\",991,0,\"ᾈ\",[]],[\"invoke\",992,0,\"ᾉ\",[]],[\"invoke\",993,0,\"ᾊ\",[]],[\"invoke\",994,0,\"ᾋ\",[]],[\"invoke\",995,0,\"ᾌ\",[]],[\"invoke\",997,0,\"ᾎ\",[]],[\"invoke\",998,0,\"ᾏ\",[]],[\"invoke\",999,0,\"Ᾰ\",[]],[\"invoke\",1000,0,\"Ᾱ\",[]],[\"invoke\",1002,0,\"Ά\",[]],[\"invoke\",1003,0,\"ᾼ\",[]],[\"invoke\",1004,0,\"𝚨\",[]],[\"invoke\",1005,0,\"𝛢\",[]],[\"invoke\",1006,0,\"𝜜\",[]],[\"invoke\",1008,0,\"𝞐\",[]],[\"invoke\",1009,0,\"⍶\",[]],[\"invoke\",1010,0,\"⍺\",[]],[\"invoke\",1011,0,\"⩜\",[]],[\"invoke\",1012,0,\"ᗅ\",[]],[\"invoke\",1014,0,\")˺˼𔗏\u{1d174}\u{1d176}\u{1d178}\u{1d17a}⁾₎❩❫⟯﴿︶﹚)⦆\u{e0029}❳❵⟧⟩⟫⟭⦈⦊⦖⸣⸥︘︸︺︼︾﹀﹂﹄﹈﹜﹞]}」\u{e005d}\u{e007d}»’”›❯\",[]],[\"invoke\",1015,0,\"(˹˻𔗎\u{1d173}\u{1d175}\u{1d177}\u{1d179}⁽₍❨❪⟮﴾︵﹙(⦅\u{e0028}❲❴⟦⟨⟪⟬⦇⦉⦕⸢⸤︗︷︹︻︽︿﹁﹃﹇﹛﹝[{「\u{e005b}\u{e007b}«‘“‹❮\",[]],[\"invoke\",1016,0,\"𝪋𝪤\",[]],[\"invoke\",1017,0,\"𝪋\",[]],[\"invoke\",1018,0,\"½\",[]],[\"invoke\",1020,0,\"1/2\",[]],[\"invoke\",1021,0,\"୳\",[]],[\"invoke\",1022,0,\"൴\",[]],[\"invoke\",1023,0,\"⳽\",[]],[\"invoke\",1024,0,\"꠱\",[]],[\"invoke\",1026,0,\"𐅵\",[]],[\"invoke\",1027,0,\"𐅶\",[]],[\"invoke\",1028,0,\"𐦽\",[]],[\"invoke\",1029,0,\"𐹻\",[]],[\"invoke\",1030,0,\""\",[]],[\"invoke\",1032,0,\"\\b\",[]],[\"invoke\",1033,0,\"⌫\",[]],[\"invoke\",1034,0,\"⌦\",[]],[\"invoke\",1035,0,\"␈\",[]],[\"invoke\",1036,0,\"␡\",[]],[\"invoke\",1038,0,\"\\u000f\",[]],[\"invoke\",1039,0,\"←\",[]],[\"invoke\",1040,0,\"⌧\",[]],[\"invoke\",1041,0,\"⍒\",[]],[\"invoke\",1042,0,\"⍔\",[]],[\"invoke\",1044,0,\"⍫\",[]],[\"invoke\",1045,0,\"\\u001a\",[]],[\"invoke\",1046,0,\"␦\",[]],[\"invoke\",1047,0,\"␚\",[]],[\"invoke\",1049,0,\"?\",[]],[\"invoke\",1050,0,\"¿\",[]],[\"invoke\",1051,0,\"᥅\",[]],[\"invoke\",1052,0,\";\",[]],[\"invoke\",1053,0,\"՞\",[]],[\"invoke\",1055,0,\"፧\",[]],[\"invoke\",1056,0,\"⁇\",[]],[\"invoke\",1057,0,\"⍰\",[]],[\"invoke\",1058,0,\"❓\",[]],[\"invoke\",1059,0,\"❔\",[]],[\"invoke\",1061,0,\"⳻\",[]],[\"invoke\",1062,0,\"⸮\",[]],[\"invoke\",1063,0,\"㉄\",[]],[\"invoke\",1064,0,\"꘏\",[]],[\"invoke\",1065,0,\"꛷\",[]],[\"invoke\",1067,0,\"﹖\",[]],[\"invoke\",1068,0,\"?\",[]],[\"invoke\",1069,0,\"𑅃\",[]],[\"invoke\",1070,0,\"𞥟\",[]],[\"invoke\",1071,0,\"\u{e003f}\",[]],[\"invoke\",1073,0,\"⯑\",[]],[\"invoke\",1074,0,\"¶\",[]],[\"invoke\",1075,0,\"⁋\",[]],[\"invoke\",1076,0,\"܀\",[]],[\"invoke\",1077,0,\"჻\",[]],[\"invoke\",1079,0,\"〷\",[]],[\"invoke\",1080,0,\"❡\",[]],[\"invoke\",1081,0,\"⸏\",[]],[\"invoke\",1082,0,\"⸐\",[]],[\"invoke\",1083,0,\"⸑\",[]],[\"invoke\",1085,0,\"\\u0014\",[]],[\"invoke\",1086,0,\"☙\",[]],[\"invoke\",1087,0,\"⸿\",[]],[\"invoke\",1088,0,\"〇\",[]],[\"invoke\",1089,0,\"๛\",[]],[\"invoke\",1091,0,\"ϓ\",[]],[\"invoke\",1092,0,\"ϔ\",[]],[\"invoke\",1093,0,\"ẛ\",[]],[\"module\",1095,130],[\"invoke\",1107,0,\"print32\",[\"i32:42\",\"i32:123\"]]]"; diff --git a/tests/corpus/webassembly-spec-imports/main.mjs b/tests/corpus/webassembly-spec-imports/main.mjs new file mode 100644 index 000000000..681908d64 --- /dev/null +++ b/tests/corpus/webassembly-spec-imports/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (3 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-int/data.mjs b/tests/corpus/webassembly-spec-int/data.mjs new file mode 100644 index 000000000..08a72be2f --- /dev/null +++ b/tests/corpus/webassembly-spec-int/data.mjs @@ -0,0 +1,33 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * i32.wast, i64.wast, int_exprs.wast, int_literals.wast, conversions.wast, traps.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABDAJgAn9/AX9gAX8BfwMgHwAAAAAAAAAAAAAAAAAAAAEBAQEBAQAAAAAAAAAAAAAH3gEfA2FkZAAAA3N1YgABA211bAACBWRpdl9zAAMFZGl2X3UABAVyZW1fcwAFBXJlbV91AAYDYW5kAAcCb3IACAN4b3IACQNzaGwACgVzaHJfcwALBXNocl91AAwEcm90bAANBHJvdHIADgNjbHoADwNjdHoAEAZwb3BjbnQAEQlleHRlbmQ4X3MAEgpleHRlbmQxNl9zABMDZXF6ABQCZXEAFQJuZQAWBGx0X3MAFwRsdF91ABgEbGVfcwAZBGxlX3UAGgRndF9zABsEZ3RfdQAcBGdlX3MAHQRnZV91AB4K7QEfBwAgACABagsHACAAIAFrCwcAIAAgAWwLBwAgACABbQsHACAAIAFuCwcAIAAgAW8LBwAgACABcAsHACAAIAFxCwcAIAAgAXILBwAgACABcwsHACAAIAF0CwcAIAAgAXULBwAgACABdgsHACAAIAF3CwcAIAAgAXgLBQAgAGcLBQAgAGgLBQAgAGkLBQAgAMALBQAgAMELBQAgAEULBwAgACABRgsHACAAIAFHCwcAIAAgAUgLBwAgACABSQsHACAAIAFMCwcAIAAgAU0LBwAgACABSgsHACAAIAFLCwcAIAAgAU4LBwAgACABTws=", + "AGFzbQEAAAABFwRgAn5+AX5gAX4BfmABfgF/YAJ+fgF/AyEgAAAAAAAAAAAAAAAAAAAAAQEBAQEBAgMDAwMDAwMDAwMH6wEgA2FkZAAAA3N1YgABA211bAACBWRpdl9zAAMFZGl2X3UABAVyZW1fcwAFBXJlbV91AAYDYW5kAAcCb3IACAN4b3IACQNzaGwACgVzaHJfcwALBXNocl91AAwEcm90bAANBHJvdHIADgNjbHoADwNjdHoAEAZwb3BjbnQAEQlleHRlbmQ4X3MAEgpleHRlbmQxNl9zABMKZXh0ZW5kMzJfcwAUA2VxegAVAmVxABYCbmUAFwRsdF9zABgEbHRfdQAZBGxlX3MAGgRsZV91ABsEZ3RfcwAcBGd0X3UAHQRnZV9zAB4EZ2VfdQAfCvMBIAcAIAAgAXwLBwAgACABfQsHACAAIAF+CwcAIAAgAX8LBwAgACABgAsHACAAIAGBCwcAIAAgAYILBwAgACABgwsHACAAIAGECwcAIAAgAYULBwAgACABhgsHACAAIAGHCwcAIAAgAYgLBwAgACABiQsHACAAIAGKCwUAIAB5CwUAIAB6CwUAIAB7CwUAIADCCwUAIADDCwUAIADECwUAIABQCwcAIAAgAVELBwAgACABUgsHACAAIAFTCwcAIAAgAVQLBwAgACABVwsHACAAIAFYCwcAIAAgAVULBwAgACABVgsHACAAIAFZCwcAIAAgAVoL", + "AGFzbQEAAAABDQJgAn9/AX9gAn5+AX8DBQQAAAEBB20EGGkzMi5ub19mb2xkX2NtcF9zX29mZnNldAAAGGkzMi5ub19mb2xkX2NtcF91X29mZnNldAABGGk2NC5ub19mb2xkX2NtcF9zX29mZnNldAACGGk2NC5ub19mb2xkX2NtcF91X29mZnNldAADCjkEDQAgAEEBaiABQQFqSAsNACAAQQFqIAFBAWpJCw0AIABCAXwgAUIBfFMLDQAgAEIBfCABQgF8VAs=", + "AGFzbQEAAAABBgFgAX4BfgMCAQAHHQEZaTY0Lm5vX2ZvbGRfd3JhcF9leHRlbmRfcwAACggBBgAgAKesCw==", + "AGFzbQEAAAABBgFgAX4BfgMCAQAHHQEZaTY0Lm5vX2ZvbGRfd3JhcF9leHRlbmRfdQAACggBBgAgAKetCw==", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9zaGxfc2hyX3MAABVpMzIubm9fZm9sZF9zaGxfc2hyX3UAARVpNjQubm9fZm9sZF9zaGxfc2hyX3MAAhVpNjQubm9fZm9sZF9zaGxfc2hyX3UAAwotBAoAIABBAXRBAXULCgAgAEEBdEEBdgsKACAAQgGGQgGHCwoAIABCAYZCAYgL", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9zaHJfc19zaGwAABVpMzIubm9fZm9sZF9zaHJfdV9zaGwAARVpNjQubm9fZm9sZF9zaHJfc19zaGwAAhVpNjQubm9fZm9sZF9zaHJfdV9zaGwAAwotBAoAIABBAXVBAXQLCgAgAEEBdkEBdAsKACAAQgGHQgGGCwoAIABCAYhCAYYL", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9kaXZfc19tdWwAABVpMzIubm9fZm9sZF9kaXZfdV9tdWwAARVpNjQubm9fZm9sZF9kaXZfc19tdWwAAhVpNjQubm9fZm9sZF9kaXZfdV9tdWwAAwotBAoAIABBBm1BBmwLCgAgAEEGbkEGbAsKACAAQgZ/QgZ+CwoAIABCBoBCBn4L", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdlBBZpMzIubm9fZm9sZF9kaXZfc19zZWxmAAAWaTMyLm5vX2ZvbGRfZGl2X3Vfc2VsZgABFmk2NC5ub19mb2xkX2Rpdl9zX3NlbGYAAhZpNjQubm9fZm9sZF9kaXZfdV9zZWxmAAMKIQQHACAAIABtCwcAIAAgAG4LBwAgACAAfwsHACAAIACACw==", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdlBBZpMzIubm9fZm9sZF9yZW1fc19zZWxmAAAWaTMyLm5vX2ZvbGRfcmVtX3Vfc2VsZgABFmk2NC5ub19mb2xkX3JlbV9zX3NlbGYAAhZpNjQubm9fZm9sZF9yZW1fdV9zZWxmAAMKIQQHACAAIABvCwcAIAAgAHALBwAgACAAgQsHACAAIACCCw==", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQdhBBVpMzIubm9fZm9sZF9tdWxfZGl2X3MAABVpMzIubm9fZm9sZF9tdWxfZGl2X3UAARVpNjQubm9fZm9sZF9tdWxfZGl2X3MAAhVpNjQubm9fZm9sZF9tdWxfZGl2X3UAAwotBAoAIABBBmxBBm0LCgAgAEEGbEEGbgsKACAAQgZ+QgZ/CwoAIABCBn5CBoAL", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLQITaTMyLm5vX2ZvbGRfZGl2X3NfMgAAE2k2NC5ub19mb2xkX2Rpdl9zXzIAAQoRAgcAIABBAm0LBwAgAEICfws=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLQITaTMyLm5vX2ZvbGRfcmVtX3NfMgAAE2k2NC5ub19mb2xkX3JlbV9zXzIAAQoRAgcAIABBAm8LBwAgAEICgQs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfMAAAC2kzMi5kaXZfdV8wAAELaTY0LmRpdl9zXzAAAgtpNjQuZGl2X3VfMAADCiEEBwAgAEEAbQsHACAAQQBuCwcAIABCAH8LBwAgAEIAgAs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfMwAAC2kzMi5kaXZfdV8zAAELaTY0LmRpdl9zXzMAAgtpNjQuZGl2X3VfMwADCiEEBwAgAEEDbQsHACAAQQNuCwcAIABCA38LBwAgAEIDgAs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfNQAAC2kzMi5kaXZfdV81AAELaTY0LmRpdl9zXzUAAgtpNjQuZGl2X3VfNQADCiEEBwAgAEEFbQsHACAAQQVuCwcAIABCBX8LBwAgAEIFgAs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIuZGl2X3NfNwAAC2kzMi5kaXZfdV83AAELaTY0LmRpdl9zXzcAAgtpNjQuZGl2X3VfNwADCiEEBwAgAEEHbQsHACAAQQduCwcAIABCB38LBwAgAEIHgAs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfMwAAC2kzMi5yZW1fdV8zAAELaTY0LnJlbV9zXzMAAgtpNjQucmVtX3VfMwADCiEEBwAgAEEDbwsHACAAQQNwCwcAIABCA4ELBwAgAEIDggs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfNQAAC2kzMi5yZW1fdV81AAELaTY0LnJlbV9zXzUAAgtpNjQucmVtX3VfNQADCiEEBwAgAEEFbwsHACAAQQVwCwcAIABCBYELBwAgAEIFggs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwUEAAABAQc5BAtpMzIucmVtX3NfNwAAC2kzMi5yZW1fdV83AAELaTY0LnJlbV9zXzcAAgtpNjQucmVtX3VfNwADCiEEBwAgAEEHbwsHACAAQQdwCwcAIABCB4ELBwAgAEIHggs=", + "AGFzbQEAAAABCwJgAX8Bf2ABfgF+AwMCAAEHLwIUaTMyLm5vX2ZvbGRfZGl2X25lZzEAABRpNjQubm9fZm9sZF9kaXZfbmVnMQABChECBwAgAEF/bQsHACAAQn9/Cw==", + "AGFzbQEAAAABCQJgAAF/YAABfgMfHgAAAAAAAAAAAAAAAQEBAQEBAQEBAQEAAAAAAQEBAQe3Ax4IaTMyLnRlc3QAAAhpMzIudW1heAABCGkzMi5zbWF4AAIMaTMyLm5lZ19zbWF4AAMIaTMyLnNtaW4ABAxpMzIuYWx0X3NtaW4ABQxpMzIuaW5jX3NtaW4ABgxpMzIubmVnX3plcm8ABw1pMzIubm90X29jdGFsAAgUaTMyLnVuc2lnbmVkX2RlY2ltYWwACQ1pMzIucGx1c19zaWduAAoIaTY0LnRlc3QACwhpNjQudW1heAAMCGk2NC5zbWF4AA0MaTY0Lm5lZ19zbWF4AA4IaTY0LnNtaW4ADwxpNjQuYWx0X3NtaW4AEAxpNjQuaW5jX3NtaW4AEQxpNjQubmVnX3plcm8AEg1pNjQubm90X29jdGFsABMUaTY0LnVuc2lnbmVkX2RlY2ltYWwAFA1pNjQucGx1c19zaWduABUMaTMyLWRlYy1zZXAxABYMaTMyLWRlYy1zZXAyABcMaTMyLWhleC1zZXAxABgMaTMyLWhleC1zZXAyABkMaTY0LWRlYy1zZXAxABoMaTY0LWRlYy1zZXAyABsMaTY0LWhleC1zZXAxABwMaTY0LWhleC1zZXAyAB0KlQIeCQBBjaC33QAPCwUAQX8PCwkAQf////8HDwsJAEGBgICAeA8LCQBBgICAgHgPCwkAQYCAgIB4DwsMAEGAgICAeEEBag8LBQBBAA8LBQBBCg8LBQBBfw8LBQBBKg8LDQBC7tSZ3eDN7tUMDwsFAEJ/DwsOAEL///////////8ADwsOAEKBgICAgICAgIB/DwsOAEKAgICAgICAgIB/DwsOAEKAgICAgICAgIB/DwsRAEKAgICAgICAgIB/QgF8DwsFAEIADwsFAEIKDwsFAEJ/DwsFAEIqDwsGAEHAhD0LBQBB6AcLCABBmYG80AALBgBBj9QGCwYAQsCEPQsFAELoBwsLAEKZs4KA8IG8BQsGAEKP1AYL", + "AGFzbQEAAAABPQxgAX8BfmABfgF/YAF9AX9gAXwBf2ABfQF+YAF8AX5gAX8BfWABfgF9YAF/AXxgAX4BfGABfQF8YAF8AX0DIiEAAAECAgMDBAQFBQICAwMEBAUFBgcICQYHCAkKCwYJAgUHkQUhEGk2NC5leHRlbmRfaTMyX3MAABBpNjQuZXh0ZW5kX2kzMl91AAEMaTMyLndyYXBfaTY0AAIPaTMyLnRydW5jX2YzMl9zAAMPaTMyLnRydW5jX2YzMl91AAQPaTMyLnRydW5jX2Y2NF9zAAUPaTMyLnRydW5jX2Y2NF91AAYPaTY0LnRydW5jX2YzMl9zAAcPaTY0LnRydW5jX2YzMl91AAgPaTY0LnRydW5jX2Y2NF9zAAkPaTY0LnRydW5jX2Y2NF91AAoTaTMyLnRydW5jX3NhdF9mMzJfcwALE2kzMi50cnVuY19zYXRfZjMyX3UADBNpMzIudHJ1bmNfc2F0X2Y2NF9zAA0TaTMyLnRydW5jX3NhdF9mNjRfdQAOE2k2NC50cnVuY19zYXRfZjMyX3MADxNpNjQudHJ1bmNfc2F0X2YzMl91ABATaTY0LnRydW5jX3NhdF9mNjRfcwARE2k2NC50cnVuY19zYXRfZjY0X3UAEhFmMzIuY29udmVydF9pMzJfcwATEWYzMi5jb252ZXJ0X2k2NF9zABQRZjY0LmNvbnZlcnRfaTMyX3MAFRFmNjQuY29udmVydF9pNjRfcwAWEWYzMi5jb252ZXJ0X2kzMl91ABcRZjMyLmNvbnZlcnRfaTY0X3UAGBFmNjQuY29udmVydF9pMzJfdQAZEWY2NC5jb252ZXJ0X2k2NF91ABoPZjY0LnByb21vdGVfZjMyABsOZjMyLmRlbW90ZV9mNjQAHBNmMzIucmVpbnRlcnByZXRfaTMyAB0TZjY0LnJlaW50ZXJwcmV0X2k2NAAeE2kzMi5yZWludGVycHJldF9mMzIAHxNpNjQucmVpbnRlcnByZXRfZjY0ACAKzwEhBQAgAKwLBQAgAK0LBQAgAKcLBQAgAKgLBQAgAKkLBQAgAKoLBQAgAKsLBQAgAK4LBQAgAK8LBQAgALALBQAgALELBgAgAPwACwYAIAD8AQsGACAA/AILBgAgAPwDCwYAIAD8BAsGACAA/AULBgAgAPwGCwYAIAD8BwsFACAAsgsFACAAtAsFACAAtwsFACAAuQsFACAAswsFACAAtQsFACAAuAsFACAAugsFACAAuwsFACAAtgsFACAAvgsFACAAvwsFACAAvAsFACAAvQs=", + "AGFzbQEAAAABCwJgAn9/AGACfn4AAwUEAAABAQdNBBBub19kY2UuaTMyLmRpdl9zAAAQbm9fZGNlLmkzMi5kaXZfdQABEG5vX2RjZS5pNjQuZGl2X3MAAhBub19kY2UuaTY0LmRpdl91AAMKJQQIACAAIAFtGgsIACAAIAFuGgsIACAAIAF/GgsIACAAIAGAGgs=", + "AGFzbQEAAAABCwJgAn9/AGACfn4AAwUEAAABAQdNBBBub19kY2UuaTMyLnJlbV9zAAAQbm9fZGNlLmkzMi5yZW1fdQABEG5vX2RjZS5pNjQucmVtX3MAAhBub19kY2UuaTY0LnJlbV91AAMKJQQIACAAIAFvGgsIACAAIAFwGgsIACAAIAGBGgsIACAAIAGCGgs=", + "AGFzbQEAAAABCQJgAX0AYAF8AAMJCAAAAQEAAAEBB8kBCBZub19kY2UuaTMyLnRydW5jX2YzMl9zAAAWbm9fZGNlLmkzMi50cnVuY19mMzJfdQABFm5vX2RjZS5pMzIudHJ1bmNfZjY0X3MAAhZub19kY2UuaTMyLnRydW5jX2Y2NF91AAMWbm9fZGNlLmk2NC50cnVuY19mMzJfcwAEFm5vX2RjZS5pNjQudHJ1bmNfZjMyX3UABRZub19kY2UuaTY0LnRydW5jX2Y2NF9zAAYWbm9fZGNlLmk2NC50cnVuY19mNjRfdQAHCjkIBgAgAKgaCwYAIACpGgsGACAAqhoLBgAgAKsaCwYAIACuGgsGACAArxoLBgAgALAaCwYAIACxGgs=", + "AGFzbQEAAAABBQFgAX8AAw8OAAAAAAAAAAAAAAAAAAAFAwEAAQehAg4Pbm9fZGNlLmkzMi5sb2FkAAATbm9fZGNlLmkzMi5sb2FkMTZfcwABE25vX2RjZS5pMzIubG9hZDE2X3UAAhJub19kY2UuaTMyLmxvYWQ4X3MAAxJub19kY2UuaTMyLmxvYWQ4X3UABA9ub19kY2UuaTY0LmxvYWQABRNub19kY2UuaTY0LmxvYWQzMl9zAAYTbm9fZGNlLmk2NC5sb2FkMzJfdQAHE25vX2RjZS5pNjQubG9hZDE2X3MACBNub19kY2UuaTY0LmxvYWQxNl91AAkSbm9fZGNlLmk2NC5sb2FkOF9zAAoSbm9fZGNlLmk2NC5sb2FkOF91AAsPbm9fZGNlLmYzMi5sb2FkAAwPbm9fZGNlLmY2NC5sb2FkAA0Kfw4IACAAKAIAGgsIACAALgEAGgsIACAALwEAGgsIACAALAAAGgsIACAALQAAGgsIACAAKQMAGgsIACAANAIAGgsIACAANQIAGgsIACAAMgEAGgsIACAAMwEAGgsIACAAMAAAGgsIACAAMQAAGgsIACAAKgIAGgsIACAAKwMAGgs=", +]; +export const script = "[[\"file\",\"i32.wast\"],[\"module\",3,0],[\"invoke\",37,0,\"add\",[\"i32:1\",\"i32:1\"]],[\"invoke\",38,0,\"add\",[\"i32:1\",\"i32:0\"]],[\"invoke\",39,0,\"add\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",40,0,\"add\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",41,0,\"add\",[\"i32:2147483647\",\"i32:1\"]],[\"invoke\",42,0,\"add\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",43,0,\"add\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",44,0,\"add\",[\"i32:1073741823\",\"i32:1\"]],[\"invoke\",46,0,\"sub\",[\"i32:1\",\"i32:1\"]],[\"invoke\",47,0,\"sub\",[\"i32:1\",\"i32:0\"]],[\"invoke\",48,0,\"sub\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",49,0,\"sub\",[\"i32:2147483647\",\"i32:4294967295\"]],[\"invoke\",50,0,\"sub\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",51,0,\"sub\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",52,0,\"sub\",[\"i32:1073741823\",\"i32:4294967295\"]],[\"invoke\",54,0,\"mul\",[\"i32:1\",\"i32:1\"]],[\"invoke\",55,0,\"mul\",[\"i32:1\",\"i32:0\"]],[\"invoke\",56,0,\"mul\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",57,0,\"mul\",[\"i32:268435456\",\"i32:4096\"]],[\"invoke\",58,0,\"mul\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",59,0,\"mul\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",60,0,\"mul\",[\"i32:2147483647\",\"i32:4294967295\"]],[\"invoke\",61,0,\"mul\",[\"i32:19088743\",\"i32:1985229328\"]],[\"invoke\",62,0,\"mul\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",64,0,\"div_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",65,0,\"div_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",66,0,\"div_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",67,0,\"div_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",68,0,\"div_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",69,0,\"div_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",70,0,\"div_s\",[\"i32:0\",\"i32:4294967295\"]],[\"invoke\",71,0,\"div_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",72,0,\"div_s\",[\"i32:2147483648\",\"i32:2\"]],[\"invoke\",73,0,\"div_s\",[\"i32:2147483649\",\"i32:1000\"]],[\"invoke\",74,0,\"div_s\",[\"i32:5\",\"i32:2\"]],[\"invoke\",75,0,\"div_s\",[\"i32:4294967291\",\"i32:2\"]],[\"invoke\",76,0,\"div_s\",[\"i32:5\",\"i32:4294967294\"]],[\"invoke\",77,0,\"div_s\",[\"i32:4294967291\",\"i32:4294967294\"]],[\"invoke\",78,0,\"div_s\",[\"i32:7\",\"i32:3\"]],[\"invoke\",79,0,\"div_s\",[\"i32:4294967289\",\"i32:3\"]],[\"invoke\",80,0,\"div_s\",[\"i32:7\",\"i32:4294967293\"]],[\"invoke\",81,0,\"div_s\",[\"i32:4294967289\",\"i32:4294967293\"]],[\"invoke\",82,0,\"div_s\",[\"i32:11\",\"i32:5\"]],[\"invoke\",83,0,\"div_s\",[\"i32:17\",\"i32:7\"]],[\"invoke\",85,0,\"div_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",86,0,\"div_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",87,0,\"div_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",88,0,\"div_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",89,0,\"div_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",90,0,\"div_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",91,0,\"div_u\",[\"i32:2147483648\",\"i32:2\"]],[\"invoke\",92,0,\"div_u\",[\"i32:2414874608\",\"i32:65537\"]],[\"invoke\",93,0,\"div_u\",[\"i32:2147483649\",\"i32:1000\"]],[\"invoke\",94,0,\"div_u\",[\"i32:5\",\"i32:2\"]],[\"invoke\",95,0,\"div_u\",[\"i32:4294967291\",\"i32:2\"]],[\"invoke\",96,0,\"div_u\",[\"i32:5\",\"i32:4294967294\"]],[\"invoke\",97,0,\"div_u\",[\"i32:4294967291\",\"i32:4294967294\"]],[\"invoke\",98,0,\"div_u\",[\"i32:7\",\"i32:3\"]],[\"invoke\",99,0,\"div_u\",[\"i32:11\",\"i32:5\"]],[\"invoke\",100,0,\"div_u\",[\"i32:17\",\"i32:7\"]],[\"invoke\",102,0,\"rem_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",103,0,\"rem_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",104,0,\"rem_s\",[\"i32:2147483647\",\"i32:4294967295\"]],[\"invoke\",105,0,\"rem_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",106,0,\"rem_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",107,0,\"rem_s\",[\"i32:0\",\"i32:4294967295\"]],[\"invoke\",108,0,\"rem_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",109,0,\"rem_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",110,0,\"rem_s\",[\"i32:2147483648\",\"i32:2\"]],[\"invoke\",111,0,\"rem_s\",[\"i32:2147483649\",\"i32:1000\"]],[\"invoke\",112,0,\"rem_s\",[\"i32:5\",\"i32:2\"]],[\"invoke\",113,0,\"rem_s\",[\"i32:4294967291\",\"i32:2\"]],[\"invoke\",114,0,\"rem_s\",[\"i32:5\",\"i32:4294967294\"]],[\"invoke\",115,0,\"rem_s\",[\"i32:4294967291\",\"i32:4294967294\"]],[\"invoke\",116,0,\"rem_s\",[\"i32:7\",\"i32:3\"]],[\"invoke\",117,0,\"rem_s\",[\"i32:4294967289\",\"i32:3\"]],[\"invoke\",118,0,\"rem_s\",[\"i32:7\",\"i32:4294967293\"]],[\"invoke\",119,0,\"rem_s\",[\"i32:4294967289\",\"i32:4294967293\"]],[\"invoke\",120,0,\"rem_s\",[\"i32:11\",\"i32:5\"]],[\"invoke\",121,0,\"rem_s\",[\"i32:17\",\"i32:7\"]],[\"invoke\",123,0,\"rem_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",124,0,\"rem_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",125,0,\"rem_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",126,0,\"rem_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",127,0,\"rem_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",128,0,\"rem_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",129,0,\"rem_u\",[\"i32:2147483648\",\"i32:2\"]],[\"invoke\",130,0,\"rem_u\",[\"i32:2414874608\",\"i32:65537\"]],[\"invoke\",131,0,\"rem_u\",[\"i32:2147483649\",\"i32:1000\"]],[\"invoke\",132,0,\"rem_u\",[\"i32:5\",\"i32:2\"]],[\"invoke\",133,0,\"rem_u\",[\"i32:4294967291\",\"i32:2\"]],[\"invoke\",134,0,\"rem_u\",[\"i32:5\",\"i32:4294967294\"]],[\"invoke\",135,0,\"rem_u\",[\"i32:4294967291\",\"i32:4294967294\"]],[\"invoke\",136,0,\"rem_u\",[\"i32:7\",\"i32:3\"]],[\"invoke\",137,0,\"rem_u\",[\"i32:11\",\"i32:5\"]],[\"invoke\",138,0,\"rem_u\",[\"i32:17\",\"i32:7\"]],[\"invoke\",140,0,\"and\",[\"i32:1\",\"i32:0\"]],[\"invoke\",141,0,\"and\",[\"i32:0\",\"i32:1\"]],[\"invoke\",142,0,\"and\",[\"i32:1\",\"i32:1\"]],[\"invoke\",143,0,\"and\",[\"i32:0\",\"i32:0\"]],[\"invoke\",144,0,\"and\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",145,0,\"and\",[\"i32:2147483647\",\"i32:4294967295\"]],[\"invoke\",146,0,\"and\",[\"i32:4042326015\",\"i32:4294963440\"]],[\"invoke\",147,0,\"and\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",149,0,\"or\",[\"i32:1\",\"i32:0\"]],[\"invoke\",150,0,\"or\",[\"i32:0\",\"i32:1\"]],[\"invoke\",151,0,\"or\",[\"i32:1\",\"i32:1\"]],[\"invoke\",152,0,\"or\",[\"i32:0\",\"i32:0\"]],[\"invoke\",153,0,\"or\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",154,0,\"or\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",155,0,\"or\",[\"i32:4042326015\",\"i32:4294963440\"]],[\"invoke\",156,0,\"or\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",158,0,\"xor\",[\"i32:1\",\"i32:0\"]],[\"invoke\",159,0,\"xor\",[\"i32:0\",\"i32:1\"]],[\"invoke\",160,0,\"xor\",[\"i32:1\",\"i32:1\"]],[\"invoke\",161,0,\"xor\",[\"i32:0\",\"i32:0\"]],[\"invoke\",162,0,\"xor\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",163,0,\"xor\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",164,0,\"xor\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",165,0,\"xor\",[\"i32:4294967295\",\"i32:2147483647\"]],[\"invoke\",166,0,\"xor\",[\"i32:4042326015\",\"i32:4294963440\"]],[\"invoke\",167,0,\"xor\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",169,0,\"shl\",[\"i32:1\",\"i32:1\"]],[\"invoke\",170,0,\"shl\",[\"i32:1\",\"i32:0\"]],[\"invoke\",171,0,\"shl\",[\"i32:2147483647\",\"i32:1\"]],[\"invoke\",172,0,\"shl\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",173,0,\"shl\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",174,0,\"shl\",[\"i32:1073741824\",\"i32:1\"]],[\"invoke\",175,0,\"shl\",[\"i32:1\",\"i32:31\"]],[\"invoke\",176,0,\"shl\",[\"i32:1\",\"i32:32\"]],[\"invoke\",177,0,\"shl\",[\"i32:1\",\"i32:33\"]],[\"invoke\",178,0,\"shl\",[\"i32:1\",\"i32:4294967295\"]],[\"invoke\",179,0,\"shl\",[\"i32:1\",\"i32:2147483647\"]],[\"invoke\",181,0,\"shr_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",182,0,\"shr_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",183,0,\"shr_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",184,0,\"shr_s\",[\"i32:2147483647\",\"i32:1\"]],[\"invoke\",185,0,\"shr_s\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",186,0,\"shr_s\",[\"i32:1073741824\",\"i32:1\"]],[\"invoke\",187,0,\"shr_s\",[\"i32:1\",\"i32:32\"]],[\"invoke\",188,0,\"shr_s\",[\"i32:1\",\"i32:33\"]],[\"invoke\",189,0,\"shr_s\",[\"i32:1\",\"i32:4294967295\"]],[\"invoke\",190,0,\"shr_s\",[\"i32:1\",\"i32:2147483647\"]],[\"invoke\",191,0,\"shr_s\",[\"i32:1\",\"i32:2147483648\"]],[\"invoke\",192,0,\"shr_s\",[\"i32:2147483648\",\"i32:31\"]],[\"invoke\",193,0,\"shr_s\",[\"i32:4294967295\",\"i32:32\"]],[\"invoke\",194,0,\"shr_s\",[\"i32:4294967295\",\"i32:33\"]],[\"invoke\",195,0,\"shr_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",196,0,\"shr_s\",[\"i32:4294967295\",\"i32:2147483647\"]],[\"invoke\",197,0,\"shr_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",199,0,\"shr_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",200,0,\"shr_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",201,0,\"shr_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",202,0,\"shr_u\",[\"i32:2147483647\",\"i32:1\"]],[\"invoke\",203,0,\"shr_u\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",204,0,\"shr_u\",[\"i32:1073741824\",\"i32:1\"]],[\"invoke\",205,0,\"shr_u\",[\"i32:1\",\"i32:32\"]],[\"invoke\",206,0,\"shr_u\",[\"i32:1\",\"i32:33\"]],[\"invoke\",207,0,\"shr_u\",[\"i32:1\",\"i32:4294967295\"]],[\"invoke\",208,0,\"shr_u\",[\"i32:1\",\"i32:2147483647\"]],[\"invoke\",209,0,\"shr_u\",[\"i32:1\",\"i32:2147483648\"]],[\"invoke\",210,0,\"shr_u\",[\"i32:2147483648\",\"i32:31\"]],[\"invoke\",211,0,\"shr_u\",[\"i32:4294967295\",\"i32:32\"]],[\"invoke\",212,0,\"shr_u\",[\"i32:4294967295\",\"i32:33\"]],[\"invoke\",213,0,\"shr_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",214,0,\"shr_u\",[\"i32:4294967295\",\"i32:2147483647\"]],[\"invoke\",215,0,\"shr_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",217,0,\"rotl\",[\"i32:1\",\"i32:1\"]],[\"invoke\",218,0,\"rotl\",[\"i32:1\",\"i32:0\"]],[\"invoke\",219,0,\"rotl\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",220,0,\"rotl\",[\"i32:1\",\"i32:32\"]],[\"invoke\",221,0,\"rotl\",[\"i32:2882377846\",\"i32:1\"]],[\"invoke\",222,0,\"rotl\",[\"i32:4261469184\",\"i32:4\"]],[\"invoke\",223,0,\"rotl\",[\"i32:2965492451\",\"i32:5\"]],[\"invoke\",224,0,\"rotl\",[\"i32:32768\",\"i32:37\"]],[\"invoke\",225,0,\"rotl\",[\"i32:2965492451\",\"i32:65285\"]],[\"invoke\",226,0,\"rotl\",[\"i32:1989852383\",\"i32:4294967277\"]],[\"invoke\",227,0,\"rotl\",[\"i32:1989852383\",\"i32:2147483661\"]],[\"invoke\",228,0,\"rotl\",[\"i32:1\",\"i32:31\"]],[\"invoke\",229,0,\"rotl\",[\"i32:2147483648\",\"i32:1\"]],[\"invoke\",231,0,\"rotr\",[\"i32:1\",\"i32:1\"]],[\"invoke\",232,0,\"rotr\",[\"i32:1\",\"i32:0\"]],[\"invoke\",233,0,\"rotr\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",234,0,\"rotr\",[\"i32:1\",\"i32:32\"]],[\"invoke\",235,0,\"rotr\",[\"i32:4278242304\",\"i32:1\"]],[\"invoke\",236,0,\"rotr\",[\"i32:524288\",\"i32:4\"]],[\"invoke\",237,0,\"rotr\",[\"i32:2965492451\",\"i32:5\"]],[\"invoke\",238,0,\"rotr\",[\"i32:32768\",\"i32:37\"]],[\"invoke\",239,0,\"rotr\",[\"i32:2965492451\",\"i32:65285\"]],[\"invoke\",240,0,\"rotr\",[\"i32:1989852383\",\"i32:4294967277\"]],[\"invoke\",241,0,\"rotr\",[\"i32:1989852383\",\"i32:2147483661\"]],[\"invoke\",242,0,\"rotr\",[\"i32:1\",\"i32:31\"]],[\"invoke\",243,0,\"rotr\",[\"i32:2147483648\",\"i32:31\"]],[\"invoke\",245,0,\"clz\",[\"i32:4294967295\"]],[\"invoke\",246,0,\"clz\",[\"i32:0\"]],[\"invoke\",247,0,\"clz\",[\"i32:32768\"]],[\"invoke\",248,0,\"clz\",[\"i32:255\"]],[\"invoke\",249,0,\"clz\",[\"i32:2147483648\"]],[\"invoke\",250,0,\"clz\",[\"i32:1\"]],[\"invoke\",251,0,\"clz\",[\"i32:2\"]],[\"invoke\",252,0,\"clz\",[\"i32:2147483647\"]],[\"invoke\",254,0,\"ctz\",[\"i32:4294967295\"]],[\"invoke\",255,0,\"ctz\",[\"i32:0\"]],[\"invoke\",256,0,\"ctz\",[\"i32:32768\"]],[\"invoke\",257,0,\"ctz\",[\"i32:65536\"]],[\"invoke\",258,0,\"ctz\",[\"i32:2147483648\"]],[\"invoke\",259,0,\"ctz\",[\"i32:2147483647\"]],[\"invoke\",261,0,\"popcnt\",[\"i32:4294967295\"]],[\"invoke\",262,0,\"popcnt\",[\"i32:0\"]],[\"invoke\",263,0,\"popcnt\",[\"i32:32768\"]],[\"invoke\",264,0,\"popcnt\",[\"i32:2147516416\"]],[\"invoke\",265,0,\"popcnt\",[\"i32:2147483647\"]],[\"invoke\",266,0,\"popcnt\",[\"i32:2863311530\"]],[\"invoke\",267,0,\"popcnt\",[\"i32:1431655765\"]],[\"invoke\",268,0,\"popcnt\",[\"i32:3735928559\"]],[\"invoke\",270,0,\"extend8_s\",[\"i32:0\"]],[\"invoke\",271,0,\"extend8_s\",[\"i32:127\"]],[\"invoke\",272,0,\"extend8_s\",[\"i32:128\"]],[\"invoke\",273,0,\"extend8_s\",[\"i32:255\"]],[\"invoke\",274,0,\"extend8_s\",[\"i32:19088640\"]],[\"invoke\",275,0,\"extend8_s\",[\"i32:4275878528\"]],[\"invoke\",276,0,\"extend8_s\",[\"i32:4294967295\"]],[\"invoke\",278,0,\"extend16_s\",[\"i32:0\"]],[\"invoke\",279,0,\"extend16_s\",[\"i32:32767\"]],[\"invoke\",280,0,\"extend16_s\",[\"i32:32768\"]],[\"invoke\",281,0,\"extend16_s\",[\"i32:65535\"]],[\"invoke\",282,0,\"extend16_s\",[\"i32:19070976\"]],[\"invoke\",283,0,\"extend16_s\",[\"i32:4275863552\"]],[\"invoke\",284,0,\"extend16_s\",[\"i32:4294967295\"]],[\"invoke\",286,0,\"eqz\",[\"i32:0\"]],[\"invoke\",287,0,\"eqz\",[\"i32:1\"]],[\"invoke\",288,0,\"eqz\",[\"i32:2147483648\"]],[\"invoke\",289,0,\"eqz\",[\"i32:2147483647\"]],[\"invoke\",290,0,\"eqz\",[\"i32:4294967295\"]],[\"invoke\",292,0,\"eq\",[\"i32:0\",\"i32:0\"]],[\"invoke\",293,0,\"eq\",[\"i32:1\",\"i32:1\"]],[\"invoke\",294,0,\"eq\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",295,0,\"eq\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",296,0,\"eq\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",297,0,\"eq\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",298,0,\"eq\",[\"i32:1\",\"i32:0\"]],[\"invoke\",299,0,\"eq\",[\"i32:0\",\"i32:1\"]],[\"invoke\",300,0,\"eq\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",301,0,\"eq\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",302,0,\"eq\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",303,0,\"eq\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",304,0,\"eq\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",305,0,\"eq\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",307,0,\"ne\",[\"i32:0\",\"i32:0\"]],[\"invoke\",308,0,\"ne\",[\"i32:1\",\"i32:1\"]],[\"invoke\",309,0,\"ne\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",310,0,\"ne\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",311,0,\"ne\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",312,0,\"ne\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",313,0,\"ne\",[\"i32:1\",\"i32:0\"]],[\"invoke\",314,0,\"ne\",[\"i32:0\",\"i32:1\"]],[\"invoke\",315,0,\"ne\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",316,0,\"ne\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",317,0,\"ne\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",318,0,\"ne\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",319,0,\"ne\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",320,0,\"ne\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",322,0,\"lt_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",323,0,\"lt_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",324,0,\"lt_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",325,0,\"lt_s\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",326,0,\"lt_s\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",327,0,\"lt_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",328,0,\"lt_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",329,0,\"lt_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",330,0,\"lt_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",331,0,\"lt_s\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",332,0,\"lt_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",333,0,\"lt_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",334,0,\"lt_s\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",335,0,\"lt_s\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",337,0,\"lt_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",338,0,\"lt_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",339,0,\"lt_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",340,0,\"lt_u\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",341,0,\"lt_u\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",342,0,\"lt_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",343,0,\"lt_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",344,0,\"lt_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",345,0,\"lt_u\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",346,0,\"lt_u\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",347,0,\"lt_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",348,0,\"lt_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",349,0,\"lt_u\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",350,0,\"lt_u\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",352,0,\"le_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",353,0,\"le_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",354,0,\"le_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",355,0,\"le_s\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",356,0,\"le_s\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",357,0,\"le_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",358,0,\"le_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",359,0,\"le_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",360,0,\"le_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",361,0,\"le_s\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",362,0,\"le_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",363,0,\"le_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",364,0,\"le_s\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",365,0,\"le_s\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",367,0,\"le_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",368,0,\"le_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",369,0,\"le_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",370,0,\"le_u\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",371,0,\"le_u\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",372,0,\"le_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",373,0,\"le_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",374,0,\"le_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",375,0,\"le_u\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",376,0,\"le_u\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",377,0,\"le_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",378,0,\"le_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",379,0,\"le_u\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",380,0,\"le_u\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",382,0,\"gt_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",383,0,\"gt_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",384,0,\"gt_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",385,0,\"gt_s\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",386,0,\"gt_s\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",387,0,\"gt_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",388,0,\"gt_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",389,0,\"gt_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",390,0,\"gt_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",391,0,\"gt_s\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",392,0,\"gt_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",393,0,\"gt_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",394,0,\"gt_s\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",395,0,\"gt_s\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",397,0,\"gt_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",398,0,\"gt_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",399,0,\"gt_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",400,0,\"gt_u\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",401,0,\"gt_u\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",402,0,\"gt_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",403,0,\"gt_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",404,0,\"gt_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",405,0,\"gt_u\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",406,0,\"gt_u\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",407,0,\"gt_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",408,0,\"gt_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",409,0,\"gt_u\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",410,0,\"gt_u\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",412,0,\"ge_s\",[\"i32:0\",\"i32:0\"]],[\"invoke\",413,0,\"ge_s\",[\"i32:1\",\"i32:1\"]],[\"invoke\",414,0,\"ge_s\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",415,0,\"ge_s\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",416,0,\"ge_s\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",417,0,\"ge_s\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",418,0,\"ge_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",419,0,\"ge_s\",[\"i32:0\",\"i32:1\"]],[\"invoke\",420,0,\"ge_s\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",421,0,\"ge_s\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",422,0,\"ge_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",423,0,\"ge_s\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",424,0,\"ge_s\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",425,0,\"ge_s\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"invoke\",427,0,\"ge_u\",[\"i32:0\",\"i32:0\"]],[\"invoke\",428,0,\"ge_u\",[\"i32:1\",\"i32:1\"]],[\"invoke\",429,0,\"ge_u\",[\"i32:4294967295\",\"i32:1\"]],[\"invoke\",430,0,\"ge_u\",[\"i32:2147483648\",\"i32:2147483648\"]],[\"invoke\",431,0,\"ge_u\",[\"i32:2147483647\",\"i32:2147483647\"]],[\"invoke\",432,0,\"ge_u\",[\"i32:4294967295\",\"i32:4294967295\"]],[\"invoke\",433,0,\"ge_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",434,0,\"ge_u\",[\"i32:0\",\"i32:1\"]],[\"invoke\",435,0,\"ge_u\",[\"i32:2147483648\",\"i32:0\"]],[\"invoke\",436,0,\"ge_u\",[\"i32:0\",\"i32:2147483648\"]],[\"invoke\",437,0,\"ge_u\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",438,0,\"ge_u\",[\"i32:4294967295\",\"i32:2147483648\"]],[\"invoke\",439,0,\"ge_u\",[\"i32:2147483648\",\"i32:2147483647\"]],[\"invoke\",440,0,\"ge_u\",[\"i32:2147483647\",\"i32:2147483648\"]],[\"file\",\"i64.wast\"],[\"module\",3,1],[\"invoke\",38,0,\"add\",[\"i64:1\",\"i64:1\"]],[\"invoke\",39,0,\"add\",[\"i64:1\",\"i64:0\"]],[\"invoke\",40,0,\"add\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",41,0,\"add\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",42,0,\"add\",[\"i64:9223372036854775807\",\"i64:1\"]],[\"invoke\",43,0,\"add\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",44,0,\"add\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",45,0,\"add\",[\"i64:1073741823\",\"i64:1\"]],[\"invoke\",47,0,\"sub\",[\"i64:1\",\"i64:1\"]],[\"invoke\",48,0,\"sub\",[\"i64:1\",\"i64:0\"]],[\"invoke\",49,0,\"sub\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",50,0,\"sub\",[\"i64:9223372036854775807\",\"i64:18446744073709551615\"]],[\"invoke\",51,0,\"sub\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",52,0,\"sub\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",53,0,\"sub\",[\"i64:1073741823\",\"i64:18446744073709551615\"]],[\"invoke\",55,0,\"mul\",[\"i64:1\",\"i64:1\"]],[\"invoke\",56,0,\"mul\",[\"i64:1\",\"i64:0\"]],[\"invoke\",57,0,\"mul\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",58,0,\"mul\",[\"i64:1152921504606846976\",\"i64:4096\"]],[\"invoke\",59,0,\"mul\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",60,0,\"mul\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",61,0,\"mul\",[\"i64:9223372036854775807\",\"i64:18446744073709551615\"]],[\"invoke\",62,0,\"mul\",[\"i64:81985529216486895\",\"i64:18364758544493064720\"]],[\"invoke\",63,0,\"mul\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",65,0,\"div_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",66,0,\"div_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",67,0,\"div_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",68,0,\"div_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",69,0,\"div_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",70,0,\"div_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",71,0,\"div_s\",[\"i64:0\",\"i64:18446744073709551615\"]],[\"invoke\",72,0,\"div_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",73,0,\"div_s\",[\"i64:9223372036854775808\",\"i64:2\"]],[\"invoke\",74,0,\"div_s\",[\"i64:9223372036854775809\",\"i64:1000\"]],[\"invoke\",75,0,\"div_s\",[\"i64:5\",\"i64:2\"]],[\"invoke\",76,0,\"div_s\",[\"i64:18446744073709551611\",\"i64:2\"]],[\"invoke\",77,0,\"div_s\",[\"i64:5\",\"i64:18446744073709551614\"]],[\"invoke\",78,0,\"div_s\",[\"i64:18446744073709551611\",\"i64:18446744073709551614\"]],[\"invoke\",79,0,\"div_s\",[\"i64:7\",\"i64:3\"]],[\"invoke\",80,0,\"div_s\",[\"i64:18446744073709551609\",\"i64:3\"]],[\"invoke\",81,0,\"div_s\",[\"i64:7\",\"i64:18446744073709551613\"]],[\"invoke\",82,0,\"div_s\",[\"i64:18446744073709551609\",\"i64:18446744073709551613\"]],[\"invoke\",83,0,\"div_s\",[\"i64:11\",\"i64:5\"]],[\"invoke\",84,0,\"div_s\",[\"i64:17\",\"i64:7\"]],[\"invoke\",86,0,\"div_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",87,0,\"div_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",88,0,\"div_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",89,0,\"div_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",90,0,\"div_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",91,0,\"div_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",92,0,\"div_u\",[\"i64:9223372036854775808\",\"i64:2\"]],[\"invoke\",93,0,\"div_u\",[\"i64:10371807465568210928\",\"i64:4294967297\"]],[\"invoke\",94,0,\"div_u\",[\"i64:9223372036854775809\",\"i64:1000\"]],[\"invoke\",95,0,\"div_u\",[\"i64:5\",\"i64:2\"]],[\"invoke\",96,0,\"div_u\",[\"i64:18446744073709551611\",\"i64:2\"]],[\"invoke\",97,0,\"div_u\",[\"i64:5\",\"i64:18446744073709551614\"]],[\"invoke\",98,0,\"div_u\",[\"i64:18446744073709551611\",\"i64:18446744073709551614\"]],[\"invoke\",99,0,\"div_u\",[\"i64:7\",\"i64:3\"]],[\"invoke\",100,0,\"div_u\",[\"i64:11\",\"i64:5\"]],[\"invoke\",101,0,\"div_u\",[\"i64:17\",\"i64:7\"]],[\"invoke\",103,0,\"rem_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",104,0,\"rem_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",105,0,\"rem_s\",[\"i64:9223372036854775807\",\"i64:18446744073709551615\"]],[\"invoke\",106,0,\"rem_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",107,0,\"rem_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",108,0,\"rem_s\",[\"i64:0\",\"i64:18446744073709551615\"]],[\"invoke\",109,0,\"rem_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",110,0,\"rem_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",111,0,\"rem_s\",[\"i64:9223372036854775808\",\"i64:2\"]],[\"invoke\",112,0,\"rem_s\",[\"i64:9223372036854775809\",\"i64:1000\"]],[\"invoke\",113,0,\"rem_s\",[\"i64:5\",\"i64:2\"]],[\"invoke\",114,0,\"rem_s\",[\"i64:18446744073709551611\",\"i64:2\"]],[\"invoke\",115,0,\"rem_s\",[\"i64:5\",\"i64:18446744073709551614\"]],[\"invoke\",116,0,\"rem_s\",[\"i64:18446744073709551611\",\"i64:18446744073709551614\"]],[\"invoke\",117,0,\"rem_s\",[\"i64:7\",\"i64:3\"]],[\"invoke\",118,0,\"rem_s\",[\"i64:18446744073709551609\",\"i64:3\"]],[\"invoke\",119,0,\"rem_s\",[\"i64:7\",\"i64:18446744073709551613\"]],[\"invoke\",120,0,\"rem_s\",[\"i64:18446744073709551609\",\"i64:18446744073709551613\"]],[\"invoke\",121,0,\"rem_s\",[\"i64:11\",\"i64:5\"]],[\"invoke\",122,0,\"rem_s\",[\"i64:17\",\"i64:7\"]],[\"invoke\",124,0,\"rem_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",125,0,\"rem_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",126,0,\"rem_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",127,0,\"rem_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",128,0,\"rem_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",129,0,\"rem_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",130,0,\"rem_u\",[\"i64:9223372036854775808\",\"i64:2\"]],[\"invoke\",131,0,\"rem_u\",[\"i64:10371807465568210928\",\"i64:4294967297\"]],[\"invoke\",132,0,\"rem_u\",[\"i64:9223372036854775809\",\"i64:1000\"]],[\"invoke\",133,0,\"rem_u\",[\"i64:5\",\"i64:2\"]],[\"invoke\",134,0,\"rem_u\",[\"i64:18446744073709551611\",\"i64:2\"]],[\"invoke\",135,0,\"rem_u\",[\"i64:5\",\"i64:18446744073709551614\"]],[\"invoke\",136,0,\"rem_u\",[\"i64:18446744073709551611\",\"i64:18446744073709551614\"]],[\"invoke\",137,0,\"rem_u\",[\"i64:7\",\"i64:3\"]],[\"invoke\",138,0,\"rem_u\",[\"i64:11\",\"i64:5\"]],[\"invoke\",139,0,\"rem_u\",[\"i64:17\",\"i64:7\"]],[\"invoke\",141,0,\"and\",[\"i64:1\",\"i64:0\"]],[\"invoke\",142,0,\"and\",[\"i64:0\",\"i64:1\"]],[\"invoke\",143,0,\"and\",[\"i64:1\",\"i64:1\"]],[\"invoke\",144,0,\"and\",[\"i64:0\",\"i64:0\"]],[\"invoke\",145,0,\"and\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",146,0,\"and\",[\"i64:9223372036854775807\",\"i64:18446744073709551615\"]],[\"invoke\",147,0,\"and\",[\"i64:4042326015\",\"i64:4294963440\"]],[\"invoke\",148,0,\"and\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",150,0,\"or\",[\"i64:1\",\"i64:0\"]],[\"invoke\",151,0,\"or\",[\"i64:0\",\"i64:1\"]],[\"invoke\",152,0,\"or\",[\"i64:1\",\"i64:1\"]],[\"invoke\",153,0,\"or\",[\"i64:0\",\"i64:0\"]],[\"invoke\",154,0,\"or\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",155,0,\"or\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",156,0,\"or\",[\"i64:4042326015\",\"i64:4294963440\"]],[\"invoke\",157,0,\"or\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",159,0,\"xor\",[\"i64:1\",\"i64:0\"]],[\"invoke\",160,0,\"xor\",[\"i64:0\",\"i64:1\"]],[\"invoke\",161,0,\"xor\",[\"i64:1\",\"i64:1\"]],[\"invoke\",162,0,\"xor\",[\"i64:0\",\"i64:0\"]],[\"invoke\",163,0,\"xor\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",164,0,\"xor\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",165,0,\"xor\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",166,0,\"xor\",[\"i64:18446744073709551615\",\"i64:9223372036854775807\"]],[\"invoke\",167,0,\"xor\",[\"i64:4042326015\",\"i64:4294963440\"]],[\"invoke\",168,0,\"xor\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",170,0,\"shl\",[\"i64:1\",\"i64:1\"]],[\"invoke\",171,0,\"shl\",[\"i64:1\",\"i64:0\"]],[\"invoke\",172,0,\"shl\",[\"i64:9223372036854775807\",\"i64:1\"]],[\"invoke\",173,0,\"shl\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",174,0,\"shl\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",175,0,\"shl\",[\"i64:4611686018427387904\",\"i64:1\"]],[\"invoke\",176,0,\"shl\",[\"i64:1\",\"i64:63\"]],[\"invoke\",177,0,\"shl\",[\"i64:1\",\"i64:64\"]],[\"invoke\",178,0,\"shl\",[\"i64:1\",\"i64:65\"]],[\"invoke\",179,0,\"shl\",[\"i64:1\",\"i64:18446744073709551615\"]],[\"invoke\",180,0,\"shl\",[\"i64:1\",\"i64:9223372036854775807\"]],[\"invoke\",182,0,\"shr_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",183,0,\"shr_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",184,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",185,0,\"shr_s\",[\"i64:9223372036854775807\",\"i64:1\"]],[\"invoke\",186,0,\"shr_s\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",187,0,\"shr_s\",[\"i64:4611686018427387904\",\"i64:1\"]],[\"invoke\",188,0,\"shr_s\",[\"i64:1\",\"i64:64\"]],[\"invoke\",189,0,\"shr_s\",[\"i64:1\",\"i64:65\"]],[\"invoke\",190,0,\"shr_s\",[\"i64:1\",\"i64:18446744073709551615\"]],[\"invoke\",191,0,\"shr_s\",[\"i64:1\",\"i64:9223372036854775807\"]],[\"invoke\",192,0,\"shr_s\",[\"i64:1\",\"i64:9223372036854775808\"]],[\"invoke\",193,0,\"shr_s\",[\"i64:9223372036854775808\",\"i64:63\"]],[\"invoke\",194,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:64\"]],[\"invoke\",195,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:65\"]],[\"invoke\",196,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",197,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775807\"]],[\"invoke\",198,0,\"shr_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",200,0,\"shr_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",201,0,\"shr_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",202,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",203,0,\"shr_u\",[\"i64:9223372036854775807\",\"i64:1\"]],[\"invoke\",204,0,\"shr_u\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",205,0,\"shr_u\",[\"i64:4611686018427387904\",\"i64:1\"]],[\"invoke\",206,0,\"shr_u\",[\"i64:1\",\"i64:64\"]],[\"invoke\",207,0,\"shr_u\",[\"i64:1\",\"i64:65\"]],[\"invoke\",208,0,\"shr_u\",[\"i64:1\",\"i64:18446744073709551615\"]],[\"invoke\",209,0,\"shr_u\",[\"i64:1\",\"i64:9223372036854775807\"]],[\"invoke\",210,0,\"shr_u\",[\"i64:1\",\"i64:9223372036854775808\"]],[\"invoke\",211,0,\"shr_u\",[\"i64:9223372036854775808\",\"i64:63\"]],[\"invoke\",212,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:64\"]],[\"invoke\",213,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:65\"]],[\"invoke\",214,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",215,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775807\"]],[\"invoke\",216,0,\"shr_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",218,0,\"rotl\",[\"i64:1\",\"i64:1\"]],[\"invoke\",219,0,\"rotl\",[\"i64:1\",\"i64:0\"]],[\"invoke\",220,0,\"rotl\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",221,0,\"rotl\",[\"i64:1\",\"i64:64\"]],[\"invoke\",222,0,\"rotl\",[\"i64:12379718583323101902\",\"i64:1\"]],[\"invoke\",223,0,\"rotl\",[\"i64:18302628889324683264\",\"i64:4\"]],[\"invoke\",224,0,\"rotl\",[\"i64:12379570969274382345\",\"i64:53\"]],[\"invoke\",225,0,\"rotl\",[\"i64:12380715672649826460\",\"i64:63\"]],[\"invoke\",226,0,\"rotl\",[\"i64:12379570969274382345\",\"i64:245\"]],[\"invoke\",227,0,\"rotl\",[\"i64:12379676934707509257\",\"i64:18446744073709551597\"]],[\"invoke\",228,0,\"rotl\",[\"i64:12380715672649826460\",\"i64:9223372036854775871\"]],[\"invoke\",229,0,\"rotl\",[\"i64:1\",\"i64:63\"]],[\"invoke\",230,0,\"rotl\",[\"i64:9223372036854775808\",\"i64:1\"]],[\"invoke\",232,0,\"rotr\",[\"i64:1\",\"i64:1\"]],[\"invoke\",233,0,\"rotr\",[\"i64:1\",\"i64:0\"]],[\"invoke\",234,0,\"rotr\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",235,0,\"rotr\",[\"i64:1\",\"i64:64\"]],[\"invoke\",236,0,\"rotr\",[\"i64:12379718583323101902\",\"i64:1\"]],[\"invoke\",237,0,\"rotr\",[\"i64:18302628889324683264\",\"i64:4\"]],[\"invoke\",238,0,\"rotr\",[\"i64:12379570969274382345\",\"i64:53\"]],[\"invoke\",239,0,\"rotr\",[\"i64:12380715672649826460\",\"i64:63\"]],[\"invoke\",240,0,\"rotr\",[\"i64:12379570969274382345\",\"i64:245\"]],[\"invoke\",241,0,\"rotr\",[\"i64:12379676934707509257\",\"i64:18446744073709551597\"]],[\"invoke\",242,0,\"rotr\",[\"i64:12380715672649826460\",\"i64:9223372036854775871\"]],[\"invoke\",243,0,\"rotr\",[\"i64:1\",\"i64:63\"]],[\"invoke\",244,0,\"rotr\",[\"i64:9223372036854775808\",\"i64:63\"]],[\"invoke\",246,0,\"clz\",[\"i64:18446744073709551615\"]],[\"invoke\",247,0,\"clz\",[\"i64:0\"]],[\"invoke\",248,0,\"clz\",[\"i64:32768\"]],[\"invoke\",249,0,\"clz\",[\"i64:255\"]],[\"invoke\",250,0,\"clz\",[\"i64:9223372036854775808\"]],[\"invoke\",251,0,\"clz\",[\"i64:1\"]],[\"invoke\",252,0,\"clz\",[\"i64:2\"]],[\"invoke\",253,0,\"clz\",[\"i64:9223372036854775807\"]],[\"invoke\",255,0,\"ctz\",[\"i64:18446744073709551615\"]],[\"invoke\",256,0,\"ctz\",[\"i64:0\"]],[\"invoke\",257,0,\"ctz\",[\"i64:32768\"]],[\"invoke\",258,0,\"ctz\",[\"i64:65536\"]],[\"invoke\",259,0,\"ctz\",[\"i64:9223372036854775808\"]],[\"invoke\",260,0,\"ctz\",[\"i64:9223372036854775807\"]],[\"invoke\",262,0,\"popcnt\",[\"i64:18446744073709551615\"]],[\"invoke\",263,0,\"popcnt\",[\"i64:0\"]],[\"invoke\",264,0,\"popcnt\",[\"i64:32768\"]],[\"invoke\",265,0,\"popcnt\",[\"i64:9223512776490647552\"]],[\"invoke\",266,0,\"popcnt\",[\"i64:9223372036854775807\"]],[\"invoke\",267,0,\"popcnt\",[\"i64:12297829381041378645\"]],[\"invoke\",268,0,\"popcnt\",[\"i64:11068046444512062122\"]],[\"invoke\",269,0,\"popcnt\",[\"i64:16045690984833335023\"]],[\"invoke\",271,0,\"extend8_s\",[\"i64:0\"]],[\"invoke\",272,0,\"extend8_s\",[\"i64:127\"]],[\"invoke\",273,0,\"extend8_s\",[\"i64:128\"]],[\"invoke\",274,0,\"extend8_s\",[\"i64:255\"]],[\"invoke\",275,0,\"extend8_s\",[\"i64:81985529216486656\"]],[\"invoke\",276,0,\"extend8_s\",[\"i64:18364758544493064832\"]],[\"invoke\",277,0,\"extend8_s\",[\"i64:18446744073709551615\"]],[\"invoke\",279,0,\"extend16_s\",[\"i64:0\"]],[\"invoke\",280,0,\"extend16_s\",[\"i64:32767\"]],[\"invoke\",281,0,\"extend16_s\",[\"i64:32768\"]],[\"invoke\",282,0,\"extend16_s\",[\"i64:65535\"]],[\"invoke\",283,0,\"extend16_s\",[\"i64:1311768467463733248\"]],[\"invoke\",284,0,\"extend16_s\",[\"i64:18364758544493084672\"]],[\"invoke\",285,0,\"extend16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",287,0,\"extend32_s\",[\"i64:0\"]],[\"invoke\",288,0,\"extend32_s\",[\"i64:32767\"]],[\"invoke\",289,0,\"extend32_s\",[\"i64:32768\"]],[\"invoke\",290,0,\"extend32_s\",[\"i64:65535\"]],[\"invoke\",291,0,\"extend32_s\",[\"i64:2147483647\"]],[\"invoke\",292,0,\"extend32_s\",[\"i64:2147483648\"]],[\"invoke\",293,0,\"extend32_s\",[\"i64:4294967295\"]],[\"invoke\",294,0,\"extend32_s\",[\"i64:81985526906748928\"]],[\"invoke\",295,0,\"extend32_s\",[\"i64:18364758544655319040\"]],[\"invoke\",296,0,\"extend32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",298,0,\"eqz\",[\"i64:0\"]],[\"invoke\",299,0,\"eqz\",[\"i64:1\"]],[\"invoke\",300,0,\"eqz\",[\"i64:9223372036854775808\"]],[\"invoke\",301,0,\"eqz\",[\"i64:9223372036854775807\"]],[\"invoke\",302,0,\"eqz\",[\"i64:18446744073709551615\"]],[\"invoke\",304,0,\"eq\",[\"i64:0\",\"i64:0\"]],[\"invoke\",305,0,\"eq\",[\"i64:1\",\"i64:1\"]],[\"invoke\",306,0,\"eq\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",307,0,\"eq\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",308,0,\"eq\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",309,0,\"eq\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",310,0,\"eq\",[\"i64:1\",\"i64:0\"]],[\"invoke\",311,0,\"eq\",[\"i64:0\",\"i64:1\"]],[\"invoke\",312,0,\"eq\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",313,0,\"eq\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",314,0,\"eq\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",315,0,\"eq\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",316,0,\"eq\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",317,0,\"eq\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",319,0,\"ne\",[\"i64:0\",\"i64:0\"]],[\"invoke\",320,0,\"ne\",[\"i64:1\",\"i64:1\"]],[\"invoke\",321,0,\"ne\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",322,0,\"ne\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",323,0,\"ne\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",324,0,\"ne\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",325,0,\"ne\",[\"i64:1\",\"i64:0\"]],[\"invoke\",326,0,\"ne\",[\"i64:0\",\"i64:1\"]],[\"invoke\",327,0,\"ne\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",328,0,\"ne\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",329,0,\"ne\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",330,0,\"ne\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",331,0,\"ne\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",332,0,\"ne\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",334,0,\"lt_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",335,0,\"lt_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",336,0,\"lt_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",337,0,\"lt_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",338,0,\"lt_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",339,0,\"lt_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",340,0,\"lt_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",341,0,\"lt_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",342,0,\"lt_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",343,0,\"lt_s\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",344,0,\"lt_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",345,0,\"lt_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",346,0,\"lt_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",347,0,\"lt_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",349,0,\"lt_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",350,0,\"lt_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",351,0,\"lt_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",352,0,\"lt_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",353,0,\"lt_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",354,0,\"lt_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",355,0,\"lt_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",356,0,\"lt_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",357,0,\"lt_u\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",358,0,\"lt_u\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",359,0,\"lt_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",360,0,\"lt_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",361,0,\"lt_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",362,0,\"lt_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",364,0,\"le_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",365,0,\"le_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",366,0,\"le_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",367,0,\"le_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",368,0,\"le_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",369,0,\"le_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",370,0,\"le_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",371,0,\"le_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",372,0,\"le_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",373,0,\"le_s\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",374,0,\"le_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",375,0,\"le_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",376,0,\"le_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",377,0,\"le_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",379,0,\"le_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",380,0,\"le_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",381,0,\"le_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",382,0,\"le_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",383,0,\"le_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",384,0,\"le_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",385,0,\"le_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",386,0,\"le_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",387,0,\"le_u\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",388,0,\"le_u\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",389,0,\"le_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",390,0,\"le_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",391,0,\"le_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",392,0,\"le_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",394,0,\"gt_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",395,0,\"gt_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",396,0,\"gt_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",397,0,\"gt_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",398,0,\"gt_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",399,0,\"gt_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",400,0,\"gt_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",401,0,\"gt_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",402,0,\"gt_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",403,0,\"gt_s\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",404,0,\"gt_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",405,0,\"gt_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",406,0,\"gt_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",407,0,\"gt_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",409,0,\"gt_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",410,0,\"gt_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",411,0,\"gt_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",412,0,\"gt_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",413,0,\"gt_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",414,0,\"gt_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",415,0,\"gt_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",416,0,\"gt_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",417,0,\"gt_u\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",418,0,\"gt_u\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",419,0,\"gt_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",420,0,\"gt_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",421,0,\"gt_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",422,0,\"gt_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",424,0,\"ge_s\",[\"i64:0\",\"i64:0\"]],[\"invoke\",425,0,\"ge_s\",[\"i64:1\",\"i64:1\"]],[\"invoke\",426,0,\"ge_s\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",427,0,\"ge_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",428,0,\"ge_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",429,0,\"ge_s\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",430,0,\"ge_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",431,0,\"ge_s\",[\"i64:0\",\"i64:1\"]],[\"invoke\",432,0,\"ge_s\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",433,0,\"ge_s\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",434,0,\"ge_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",435,0,\"ge_s\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",436,0,\"ge_s\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",437,0,\"ge_s\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"invoke\",439,0,\"ge_u\",[\"i64:0\",\"i64:0\"]],[\"invoke\",440,0,\"ge_u\",[\"i64:1\",\"i64:1\"]],[\"invoke\",441,0,\"ge_u\",[\"i64:18446744073709551615\",\"i64:1\"]],[\"invoke\",442,0,\"ge_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775808\"]],[\"invoke\",443,0,\"ge_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775807\"]],[\"invoke\",444,0,\"ge_u\",[\"i64:18446744073709551615\",\"i64:18446744073709551615\"]],[\"invoke\",445,0,\"ge_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",446,0,\"ge_u\",[\"i64:0\",\"i64:1\"]],[\"invoke\",447,0,\"ge_u\",[\"i64:9223372036854775808\",\"i64:0\"]],[\"invoke\",448,0,\"ge_u\",[\"i64:0\",\"i64:9223372036854775808\"]],[\"invoke\",449,0,\"ge_u\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"invoke\",450,0,\"ge_u\",[\"i64:18446744073709551615\",\"i64:9223372036854775808\"]],[\"invoke\",451,0,\"ge_u\",[\"i64:9223372036854775808\",\"i64:9223372036854775807\"]],[\"invoke\",452,0,\"ge_u\",[\"i64:9223372036854775807\",\"i64:9223372036854775808\"]],[\"file\",\"int_exprs.wast\"],[\"module\",6,2],[\"invoke\",18,0,\"i32.no_fold_cmp_s_offset\",[\"i32:2147483647\",\"i32:0\"]],[\"invoke\",19,0,\"i32.no_fold_cmp_u_offset\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",20,0,\"i64.no_fold_cmp_s_offset\",[\"i64:9223372036854775807\",\"i64:0\"]],[\"invoke\",21,0,\"i64.no_fold_cmp_u_offset\",[\"i64:18446744073709551615\",\"i64:0\"]],[\"module\",25,3],[\"invoke\",30,0,\"i64.no_fold_wrap_extend_s\",[\"i64:4538991236898928\"]],[\"invoke\",31,0,\"i64.no_fold_wrap_extend_s\",[\"i64:45230338458316960\"]],[\"module\",35,4],[\"invoke\",40,0,\"i64.no_fold_wrap_extend_u\",[\"i64:4538991236898928\"]],[\"module\",44,5],[\"invoke\",56,0,\"i32.no_fold_shl_shr_s\",[\"i32:2147483648\"]],[\"invoke\",57,0,\"i32.no_fold_shl_shr_u\",[\"i32:2147483648\"]],[\"invoke\",58,0,\"i64.no_fold_shl_shr_s\",[\"i64:9223372036854775808\"]],[\"invoke\",59,0,\"i64.no_fold_shl_shr_u\",[\"i64:9223372036854775808\"]],[\"module\",63,6],[\"invoke\",75,0,\"i32.no_fold_shr_s_shl\",[\"i32:1\"]],[\"invoke\",76,0,\"i32.no_fold_shr_u_shl\",[\"i32:1\"]],[\"invoke\",77,0,\"i64.no_fold_shr_s_shl\",[\"i64:1\"]],[\"invoke\",78,0,\"i64.no_fold_shr_u_shl\",[\"i64:1\"]],[\"module\",82,7],[\"invoke\",94,0,\"i32.no_fold_div_s_mul\",[\"i32:1\"]],[\"invoke\",95,0,\"i32.no_fold_div_u_mul\",[\"i32:1\"]],[\"invoke\",96,0,\"i64.no_fold_div_s_mul\",[\"i64:1\"]],[\"invoke\",97,0,\"i64.no_fold_div_u_mul\",[\"i64:1\"]],[\"module\",101,8],[\"invoke\",113,0,\"i32.no_fold_div_s_self\",[\"i32:0\"]],[\"invoke\",114,0,\"i32.no_fold_div_u_self\",[\"i32:0\"]],[\"invoke\",115,0,\"i64.no_fold_div_s_self\",[\"i64:0\"]],[\"invoke\",116,0,\"i64.no_fold_div_u_self\",[\"i64:0\"]],[\"module\",120,9],[\"invoke\",132,0,\"i32.no_fold_rem_s_self\",[\"i32:0\"]],[\"invoke\",133,0,\"i32.no_fold_rem_u_self\",[\"i32:0\"]],[\"invoke\",134,0,\"i64.no_fold_rem_s_self\",[\"i64:0\"]],[\"invoke\",135,0,\"i64.no_fold_rem_u_self\",[\"i64:0\"]],[\"module\",139,10],[\"invoke\",151,0,\"i32.no_fold_mul_div_s\",[\"i32:2147483648\"]],[\"invoke\",152,0,\"i32.no_fold_mul_div_u\",[\"i32:2147483648\"]],[\"invoke\",153,0,\"i64.no_fold_mul_div_s\",[\"i64:9223372036854775808\"]],[\"invoke\",154,0,\"i64.no_fold_mul_div_u\",[\"i64:9223372036854775808\"]],[\"module\",158,11],[\"invoke\",166,0,\"i32.no_fold_div_s_2\",[\"i32:4294967285\"]],[\"invoke\",167,0,\"i64.no_fold_div_s_2\",[\"i64:18446744073709551605\"]],[\"module\",171,12],[\"invoke\",179,0,\"i32.no_fold_rem_s_2\",[\"i32:4294967285\"]],[\"invoke\",180,0,\"i64.no_fold_rem_s_2\",[\"i64:18446744073709551605\"]],[\"module\",184,13],[\"invoke\",196,0,\"i32.div_s_0\",[\"i32:71\"]],[\"invoke\",197,0,\"i32.div_u_0\",[\"i32:71\"]],[\"invoke\",198,0,\"i64.div_s_0\",[\"i64:71\"]],[\"invoke\",199,0,\"i64.div_u_0\",[\"i64:71\"]],[\"module\",203,14],[\"invoke\",215,0,\"i32.div_s_3\",[\"i32:71\"]],[\"invoke\",216,0,\"i32.div_s_3\",[\"i32:1610612736\"]],[\"invoke\",217,0,\"i32.div_u_3\",[\"i32:71\"]],[\"invoke\",218,0,\"i32.div_u_3\",[\"i32:3221225472\"]],[\"invoke\",219,0,\"i64.div_s_3\",[\"i64:71\"]],[\"invoke\",220,0,\"i64.div_s_3\",[\"i64:3458764513820540928\"]],[\"invoke\",221,0,\"i64.div_u_3\",[\"i64:71\"]],[\"invoke\",222,0,\"i64.div_u_3\",[\"i64:13835058055282163712\"]],[\"module\",226,15],[\"invoke\",238,0,\"i32.div_s_5\",[\"i32:71\"]],[\"invoke\",239,0,\"i32.div_s_5\",[\"i32:1342177280\"]],[\"invoke\",240,0,\"i32.div_u_5\",[\"i32:71\"]],[\"invoke\",241,0,\"i32.div_u_5\",[\"i32:2684354560\"]],[\"invoke\",242,0,\"i64.div_s_5\",[\"i64:71\"]],[\"invoke\",243,0,\"i64.div_s_5\",[\"i64:5764607523034234880\"]],[\"invoke\",244,0,\"i64.div_u_5\",[\"i64:71\"]],[\"invoke\",245,0,\"i64.div_u_5\",[\"i64:11529215046068469760\"]],[\"module\",249,16],[\"invoke\",261,0,\"i32.div_s_7\",[\"i32:71\"]],[\"invoke\",262,0,\"i32.div_s_7\",[\"i32:1879048192\"]],[\"invoke\",263,0,\"i32.div_u_7\",[\"i32:71\"]],[\"invoke\",264,0,\"i32.div_u_7\",[\"i32:3758096384\"]],[\"invoke\",265,0,\"i64.div_s_7\",[\"i64:71\"]],[\"invoke\",266,0,\"i64.div_s_7\",[\"i64:8070450532247928832\"]],[\"invoke\",267,0,\"i64.div_u_7\",[\"i64:71\"]],[\"invoke\",268,0,\"i64.div_u_7\",[\"i64:16140901064495857664\"]],[\"module\",272,17],[\"invoke\",284,0,\"i32.rem_s_3\",[\"i32:71\"]],[\"invoke\",285,0,\"i32.rem_s_3\",[\"i32:1610612736\"]],[\"invoke\",286,0,\"i32.rem_u_3\",[\"i32:71\"]],[\"invoke\",287,0,\"i32.rem_u_3\",[\"i32:3221225472\"]],[\"invoke\",288,0,\"i64.rem_s_3\",[\"i64:71\"]],[\"invoke\",289,0,\"i64.rem_s_3\",[\"i64:3458764513820540928\"]],[\"invoke\",290,0,\"i64.rem_u_3\",[\"i64:71\"]],[\"invoke\",291,0,\"i64.rem_u_3\",[\"i64:13835058055282163712\"]],[\"module\",295,18],[\"invoke\",307,0,\"i32.rem_s_5\",[\"i32:71\"]],[\"invoke\",308,0,\"i32.rem_s_5\",[\"i32:1342177280\"]],[\"invoke\",309,0,\"i32.rem_u_5\",[\"i32:71\"]],[\"invoke\",310,0,\"i32.rem_u_5\",[\"i32:2684354560\"]],[\"invoke\",311,0,\"i64.rem_s_5\",[\"i64:71\"]],[\"invoke\",312,0,\"i64.rem_s_5\",[\"i64:5764607523034234880\"]],[\"invoke\",313,0,\"i64.rem_u_5\",[\"i64:71\"]],[\"invoke\",314,0,\"i64.rem_u_5\",[\"i64:11529215046068469760\"]],[\"module\",318,19],[\"invoke\",330,0,\"i32.rem_s_7\",[\"i32:71\"]],[\"invoke\",331,0,\"i32.rem_s_7\",[\"i32:1879048192\"]],[\"invoke\",332,0,\"i32.rem_u_7\",[\"i32:71\"]],[\"invoke\",333,0,\"i32.rem_u_7\",[\"i32:3758096384\"]],[\"invoke\",334,0,\"i64.rem_s_7\",[\"i64:71\"]],[\"invoke\",335,0,\"i64.rem_s_7\",[\"i64:8070450532247928832\"]],[\"invoke\",336,0,\"i64.rem_u_7\",[\"i64:71\"]],[\"invoke\",337,0,\"i64.rem_u_7\",[\"i64:16140901064495857664\"]],[\"module\",341,20],[\"invoke\",349,0,\"i32.no_fold_div_neg1\",[\"i32:2147483648\"]],[\"invoke\",350,0,\"i64.no_fold_div_neg1\",[\"i64:9223372036854775808\"]],[\"file\",\"int_literals.wast\"],[\"module\",1,21],[\"invoke\",37,0,\"i32.test\",[]],[\"invoke\",38,0,\"i32.umax\",[]],[\"invoke\",39,0,\"i32.smax\",[]],[\"invoke\",40,0,\"i32.neg_smax\",[]],[\"invoke\",41,0,\"i32.smin\",[]],[\"invoke\",42,0,\"i32.alt_smin\",[]],[\"invoke\",43,0,\"i32.inc_smin\",[]],[\"invoke\",44,0,\"i32.neg_zero\",[]],[\"invoke\",45,0,\"i32.not_octal\",[]],[\"invoke\",46,0,\"i32.unsigned_decimal\",[]],[\"invoke\",47,0,\"i32.plus_sign\",[]],[\"invoke\",49,0,\"i64.test\",[]],[\"invoke\",50,0,\"i64.umax\",[]],[\"invoke\",51,0,\"i64.smax\",[]],[\"invoke\",52,0,\"i64.neg_smax\",[]],[\"invoke\",53,0,\"i64.smin\",[]],[\"invoke\",54,0,\"i64.alt_smin\",[]],[\"invoke\",55,0,\"i64.inc_smin\",[]],[\"invoke\",56,0,\"i64.neg_zero\",[]],[\"invoke\",57,0,\"i64.not_octal\",[]],[\"invoke\",58,0,\"i64.unsigned_decimal\",[]],[\"invoke\",59,0,\"i64.plus_sign\",[]],[\"invoke\",61,0,\"i32-dec-sep1\",[]],[\"invoke\",62,0,\"i32-dec-sep2\",[]],[\"invoke\",63,0,\"i32-hex-sep1\",[]],[\"invoke\",64,0,\"i32-hex-sep2\",[]],[\"invoke\",66,0,\"i64-dec-sep1\",[]],[\"invoke\",67,0,\"i64-dec-sep2\",[]],[\"invoke\",68,0,\"i64-hex-sep1\",[]],[\"invoke\",69,0,\"i64-hex-sep2\",[]],[\"file\",\"conversions.wast\"],[\"module\",1,22],[\"invoke\",37,0,\"i64.extend_i32_s\",[\"i32:0\"]],[\"invoke\",39,0,\"i64.extend_i32_s\",[\"i32:4294957296\"]],[\"invoke\",40,0,\"i64.extend_i32_s\",[\"i32:4294967295\"]],[\"invoke\",42,0,\"i64.extend_i32_s\",[\"i32:2147483648\"]],[\"invoke\",44,0,\"i64.extend_i32_u\",[\"i32:0\"]],[\"invoke\",45,0,\"i64.extend_i32_u\",[\"i32:10000\"]],[\"invoke\",47,0,\"i64.extend_i32_u\",[\"i32:4294967295\"]],[\"invoke\",48,0,\"i64.extend_i32_u\",[\"i32:2147483647\"]],[\"invoke\",51,0,\"i32.wrap_i64\",[\"i64:18446744073709551615\"]],[\"invoke\",52,0,\"i32.wrap_i64\",[\"i64:18446744073709451616\"]],[\"invoke\",53,0,\"i32.wrap_i64\",[\"i64:2147483648\"]],[\"invoke\",55,0,\"i32.wrap_i64\",[\"i64:18446744069414584320\"]],[\"invoke\",56,0,\"i32.wrap_i64\",[\"i64:18446744069414584319\"]],[\"invoke\",57,0,\"i32.wrap_i64\",[\"i64:18446744069414584321\"]],[\"invoke\",59,0,\"i32.wrap_i64\",[\"i64:1311768467463790320\"]],[\"invoke\",60,0,\"i32.wrap_i64\",[\"i64:4294967295\"]],[\"invoke\",62,0,\"i32.wrap_i64\",[\"i64:4294967297\"]],[\"invoke\",64,0,\"i32.trunc_f32_s\",[\"f32:0\"]],[\"invoke\",65,0,\"i32.trunc_f32_s\",[\"f32:2147483648\"]],[\"invoke\",67,0,\"i32.trunc_f32_s\",[\"f32:2147483649\"]],[\"invoke\",68,0,\"i32.trunc_f32_s\",[\"f32:1065353216\"]],[\"invoke\",69,0,\"i32.trunc_f32_s\",[\"f32:1066192077\"]],[\"invoke\",71,0,\"i32.trunc_f32_s\",[\"f32:3212836864\"]],[\"invoke\",72,0,\"i32.trunc_f32_s\",[\"f32:3213675725\"]],[\"invoke\",74,0,\"i32.trunc_f32_s\",[\"f32:3220386611\"]],[\"invoke\",75,0,\"i32.trunc_f32_s\",[\"f32:3221225472\"]],[\"invoke\",76,0,\"i32.trunc_f32_s\",[\"f32:1325400063\"]],[\"invoke\",78,0,\"i32.trunc_f32_s\",[\"f32:1325400064\"]],[\"invoke\",79,0,\"i32.trunc_f32_s\",[\"f32:3472883713\"]],[\"invoke\",80,0,\"i32.trunc_f32_s\",[\"f32:2139095040\"]],[\"invoke\",82,0,\"i32.trunc_f32_s\",[\"f32:2143289344\"]],[\"invoke\",84,0,\"i32.trunc_f32_s\",[\"f32:4290772992\"]],[\"invoke\",88,0,\"i32.trunc_f32_u\",[\"f32:2147483648\"]],[\"invoke\",89,0,\"i32.trunc_f32_u\",[\"f32:1\"]],[\"invoke\",90,0,\"i32.trunc_f32_u\",[\"f32:2147483649\"]],[\"invoke\",92,0,\"i32.trunc_f32_u\",[\"f32:1066192077\"]],[\"invoke\",93,0,\"i32.trunc_f32_u\",[\"f32:1069547520\"]],[\"invoke\",94,0,\"i32.trunc_f32_u\",[\"f32:1072902963\"]],[\"invoke\",96,0,\"i32.trunc_f32_u\",[\"f32:1325400064\"]],[\"invoke\",97,0,\"i32.trunc_f32_u\",[\"f32:1333788671\"]],[\"invoke\",99,0,\"i32.trunc_f32_u\",[\"f32:3212836863\"]],[\"invoke\",100,0,\"i32.trunc_f32_u\",[\"f32:1333788672\"]],[\"invoke\",101,0,\"i32.trunc_f32_u\",[\"f32:3212836864\"]],[\"invoke\",103,0,\"i32.trunc_f32_u\",[\"f32:4286578688\"]],[\"invoke\",104,0,\"i32.trunc_f32_u\",[\"f32:2143289344\"]],[\"invoke\",106,0,\"i32.trunc_f32_u\",[\"f32:4290772992\"]],[\"invoke\",110,0,\"i32.trunc_f64_s\",[\"f64:9223372036854775808\"]],[\"invoke\",111,0,\"i32.trunc_f64_s\",[\"f64:1\"]],[\"invoke\",113,0,\"i32.trunc_f64_s\",[\"f64:4607182418800017408\"]],[\"invoke\",114,0,\"i32.trunc_f64_s\",[\"f64:4607632778762754458\"]],[\"invoke\",115,0,\"i32.trunc_f64_s\",[\"f64:4609434218613702656\"]],[\"invoke\",117,0,\"i32.trunc_f64_s\",[\"f64:13831004815617530266\"]],[\"invoke\",118,0,\"i32.trunc_f64_s\",[\"f64:13832806255468478464\"]],[\"invoke\",120,0,\"i32.trunc_f64_s\",[\"f64:13835058055282163712\"]],[\"invoke\",121,0,\"i32.trunc_f64_s\",[\"f64:4746794007244308480\"]],[\"invoke\",122,0,\"i32.trunc_f64_s\",[\"f64:13970166044103278592\"]],[\"invoke\",124,0,\"i32.trunc_f64_s\",[\"f64:4746794007248083354\"]],[\"invoke\",125,0,\"i32.trunc_f64_s\",[\"f64:4746794007248502784\"]],[\"invoke\",126,0,\"i32.trunc_f64_s\",[\"f64:13970166044105375744\"]],[\"invoke\",128,0,\"i32.trunc_f64_s\",[\"f64:18442240474082181120\"]],[\"invoke\",129,0,\"i32.trunc_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",134,0,\"i32.trunc_f64_u\",[\"f64:0\"]],[\"invoke\",135,0,\"i32.trunc_f64_u\",[\"f64:9223372036854775808\"]],[\"invoke\",136,0,\"i32.trunc_f64_u\",[\"f64:1\"]],[\"invoke\",138,0,\"i32.trunc_f64_u\",[\"f64:4607182418800017408\"]],[\"invoke\",139,0,\"i32.trunc_f64_u\",[\"f64:4607632778762754458\"]],[\"invoke\",140,0,\"i32.trunc_f64_u\",[\"f64:4609434218613702656\"]],[\"invoke\",142,0,\"i32.trunc_f64_u\",[\"f64:4611686018427387904\"]],[\"invoke\",143,0,\"i32.trunc_f64_u\",[\"f64:4746794007248502784\"]],[\"invoke\",145,0,\"i32.trunc_f64_u\",[\"f64:13829653735729319117\"]],[\"invoke\",146,0,\"i32.trunc_f64_u\",[\"f64:13830554455654793215\"]],[\"invoke\",147,0,\"i32.trunc_f64_u\",[\"f64:4726483295884279808\"]],[\"invoke\",149,0,\"i32.trunc_f64_u\",[\"f64:4751297606875663565\"]],[\"invoke\",150,0,\"i32.trunc_f64_u\",[\"f64:4751297606875873280\"]],[\"invoke\",151,0,\"i32.trunc_f64_u\",[\"f64:13830554455654793216\"]],[\"invoke\",153,0,\"i32.trunc_f64_u\",[\"f64:5055640609639927018\"]],[\"invoke\",154,0,\"i32.trunc_f64_u\",[\"f64:4890909195324358656\"]],[\"invoke\",156,0,\"i32.trunc_f64_u\",[\"f64:18442240474082181120\"]],[\"invoke\",157,0,\"i32.trunc_f64_u\",[\"f64:9221120237041090560\"]],[\"invoke\",159,0,\"i32.trunc_f64_u\",[\"f64:18444492273895866368\"]],[\"invoke\",163,0,\"i64.trunc_f32_s\",[\"f32:2147483648\"]],[\"invoke\",164,0,\"i64.trunc_f32_s\",[\"f32:1\"]],[\"invoke\",165,0,\"i64.trunc_f32_s\",[\"f32:2147483649\"]],[\"invoke\",167,0,\"i64.trunc_f32_s\",[\"f32:1066192077\"]],[\"invoke\",168,0,\"i64.trunc_f32_s\",[\"f32:1069547520\"]],[\"invoke\",170,0,\"i64.trunc_f32_s\",[\"f32:3213675725\"]],[\"invoke\",171,0,\"i64.trunc_f32_s\",[\"f32:3217031168\"]],[\"invoke\",172,0,\"i64.trunc_f32_s\",[\"f32:3220386611\"]],[\"invoke\",174,0,\"i64.trunc_f32_s\",[\"f32:1333788672\"]],[\"invoke\",175,0,\"i64.trunc_f32_s\",[\"f32:3481272320\"]],[\"invoke\",176,0,\"i64.trunc_f32_s\",[\"f32:1593835519\"]],[\"invoke\",178,0,\"i64.trunc_f32_s\",[\"f32:1593835520\"]],[\"invoke\",179,0,\"i64.trunc_f32_s\",[\"f32:3741319169\"]],[\"invoke\",181,0,\"i64.trunc_f32_s\",[\"f32:4286578688\"]],[\"invoke\",182,0,\"i64.trunc_f32_s\",[\"f32:2143289344\"]],[\"invoke\",184,0,\"i64.trunc_f32_s\",[\"f32:4290772992\"]],[\"invoke\",188,0,\"i64.trunc_f32_u\",[\"f32:2147483648\"]],[\"invoke\",189,0,\"i64.trunc_f32_u\",[\"f32:1\"]],[\"invoke\",190,0,\"i64.trunc_f32_u\",[\"f32:2147483649\"]],[\"invoke\",192,0,\"i64.trunc_f32_u\",[\"f32:1066192077\"]],[\"invoke\",193,0,\"i64.trunc_f32_u\",[\"f32:1069547520\"]],[\"invoke\",195,0,\"i64.trunc_f32_u\",[\"f32:1602224127\"]],[\"invoke\",196,0,\"i64.trunc_f32_u\",[\"f32:3211159142\"]],[\"invoke\",197,0,\"i64.trunc_f32_u\",[\"f32:3212836863\"]],[\"invoke\",199,0,\"i64.trunc_f32_u\",[\"f32:3212836864\"]],[\"invoke\",200,0,\"i64.trunc_f32_u\",[\"f32:2139095040\"]],[\"invoke\",202,0,\"i64.trunc_f32_u\",[\"f32:2143289344\"]],[\"invoke\",204,0,\"i64.trunc_f32_u\",[\"f32:4290772992\"]],[\"invoke\",207,0,\"i64.trunc_f64_s\",[\"f64:0\"]],[\"invoke\",209,0,\"i64.trunc_f64_s\",[\"f64:1\"]],[\"invoke\",210,0,\"i64.trunc_f64_s\",[\"f64:9223372036854775809\"]],[\"invoke\",211,0,\"i64.trunc_f64_s\",[\"f64:4607182418800017408\"]],[\"invoke\",213,0,\"i64.trunc_f64_s\",[\"f64:4609434218613702656\"]],[\"invoke\",214,0,\"i64.trunc_f64_s\",[\"f64:13830554455654793216\"]],[\"invoke\",216,0,\"i64.trunc_f64_s\",[\"f64:13832806255468478464\"]],[\"invoke\",217,0,\"i64.trunc_f64_s\",[\"f64:13834607695319426662\"]],[\"invoke\",218,0,\"i64.trunc_f64_s\",[\"f64:13835058055282163712\"]],[\"invoke\",220,0,\"i64.trunc_f64_s\",[\"f64:13974669643730649088\"]],[\"invoke\",221,0,\"i64.trunc_f64_s\",[\"f64:4890909195324358655\"]],[\"invoke\",222,0,\"i64.trunc_f64_s\",[\"f64:14114281232179134464\"]],[\"invoke\",224,0,\"i64.trunc_f64_s\",[\"f64:14114281232179134465\"]],[\"invoke\",225,0,\"i64.trunc_f64_s\",[\"f64:9218868437227405312\"]],[\"invoke\",227,0,\"i64.trunc_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",229,0,\"i64.trunc_f64_s\",[\"f64:18444492273895866368\"]],[\"invoke\",232,0,\"i64.trunc_f64_u\",[\"f64:0\"]],[\"invoke\",234,0,\"i64.trunc_f64_u\",[\"f64:1\"]],[\"invoke\",235,0,\"i64.trunc_f64_u\",[\"f64:9223372036854775809\"]],[\"invoke\",236,0,\"i64.trunc_f64_u\",[\"f64:4607182418800017408\"]],[\"invoke\",238,0,\"i64.trunc_f64_u\",[\"f64:4609434218613702656\"]],[\"invoke\",239,0,\"i64.trunc_f64_u\",[\"f64:4751297606873776128\"]],[\"invoke\",241,0,\"i64.trunc_f64_u\",[\"f64:4895412794951729151\"]],[\"invoke\",242,0,\"i64.trunc_f64_u\",[\"f64:13829653735729319117\"]],[\"invoke\",243,0,\"i64.trunc_f64_u\",[\"f64:13830554455654793215\"]],[\"invoke\",245,0,\"i64.trunc_f64_u\",[\"f64:4846369599423283200\"]],[\"invoke\",246,0,\"i64.trunc_f64_u\",[\"f64:4890909195324358656\"]],[\"invoke\",247,0,\"i64.trunc_f64_u\",[\"f64:4895412794951729152\"]],[\"invoke\",249,0,\"i64.trunc_f64_u\",[\"f64:9218868437227405312\"]],[\"invoke\",250,0,\"i64.trunc_f64_u\",[\"f64:18442240474082181120\"]],[\"invoke\",253,0,\"i64.trunc_f64_u\",[\"f64:18444492273895866368\"]],[\"invoke\",256,0,\"f32.convert_i32_s\",[\"i32:1\"]],[\"invoke\",257,0,\"f32.convert_i32_s\",[\"i32:4294967295\"]],[\"invoke\",259,0,\"f32.convert_i32_s\",[\"i32:2147483647\"]],[\"invoke\",260,0,\"f32.convert_i32_s\",[\"i32:2147483648\"]],[\"invoke\",261,0,\"f32.convert_i32_s\",[\"i32:1234567890\"]],[\"invoke\",266,0,\"i32.trunc_sat_f32_s\",[\"f32:2147483648\"]],[\"invoke\",267,0,\"i32.trunc_sat_f32_s\",[\"f32:1\"]],[\"invoke\",269,0,\"i32.trunc_sat_f32_s\",[\"f32:1065353216\"]],[\"invoke\",270,0,\"i32.trunc_sat_f32_s\",[\"f32:1066192077\"]],[\"invoke\",271,0,\"i32.trunc_sat_f32_s\",[\"f32:1069547520\"]],[\"invoke\",273,0,\"i32.trunc_sat_f32_s\",[\"f32:3213675725\"]],[\"invoke\",274,0,\"i32.trunc_sat_f32_s\",[\"f32:3217031168\"]],[\"invoke\",276,0,\"i32.trunc_sat_f32_s\",[\"f32:3221225472\"]],[\"invoke\",277,0,\"i32.trunc_sat_f32_s\",[\"f32:1325400063\"]],[\"invoke\",278,0,\"i32.trunc_sat_f32_s\",[\"f32:3472883712\"]],[\"invoke\",280,0,\"i32.trunc_sat_f32_s\",[\"f32:3472883713\"]],[\"invoke\",281,0,\"i32.trunc_sat_f32_s\",[\"f32:2139095040\"]],[\"invoke\",282,0,\"i32.trunc_sat_f32_s\",[\"f32:4286578688\"]],[\"invoke\",285,0,\"i32.trunc_sat_f32_s\",[\"f32:4290772992\"]],[\"invoke\",288,0,\"i32.trunc_sat_f32_u\",[\"f32:0\"]],[\"invoke\",290,0,\"i32.trunc_sat_f32_u\",[\"f32:1\"]],[\"invoke\",291,0,\"i32.trunc_sat_f32_u\",[\"f32:2147483649\"]],[\"invoke\",292,0,\"i32.trunc_sat_f32_u\",[\"f32:1065353216\"]],[\"invoke\",294,0,\"i32.trunc_sat_f32_u\",[\"f32:1069547520\"]],[\"invoke\",295,0,\"i32.trunc_sat_f32_u\",[\"f32:1072902963\"]],[\"invoke\",296,0,\"i32.trunc_sat_f32_u\",[\"f32:1073741824\"]],[\"invoke\",298,0,\"i32.trunc_sat_f32_u\",[\"f32:1333788671\"]],[\"invoke\",299,0,\"i32.trunc_sat_f32_u\",[\"f32:3211159142\"]],[\"invoke\",301,0,\"i32.trunc_sat_f32_u\",[\"f32:1333788672\"]],[\"invoke\",302,0,\"i32.trunc_sat_f32_u\",[\"f32:3212836864\"]],[\"invoke\",303,0,\"i32.trunc_sat_f32_u\",[\"f32:2139095040\"]],[\"invoke\",305,0,\"i32.trunc_sat_f32_u\",[\"f32:2143289344\"]],[\"invoke\",307,0,\"i32.trunc_sat_f32_u\",[\"f32:4290772992\"]],[\"invoke\",310,0,\"i32.trunc_sat_f64_s\",[\"f64:0\"]],[\"invoke\",312,0,\"i32.trunc_sat_f64_s\",[\"f64:1\"]],[\"invoke\",313,0,\"i32.trunc_sat_f64_s\",[\"f64:9223372036854775809\"]],[\"invoke\",315,0,\"i32.trunc_sat_f64_s\",[\"f64:4607632778762754458\"]],[\"invoke\",316,0,\"i32.trunc_sat_f64_s\",[\"f64:4609434218613702656\"]],[\"invoke\",317,0,\"i32.trunc_sat_f64_s\",[\"f64:13830554455654793216\"]],[\"invoke\",319,0,\"i32.trunc_sat_f64_s\",[\"f64:13832806255468478464\"]],[\"invoke\",320,0,\"i32.trunc_sat_f64_s\",[\"f64:13834607695319426662\"]],[\"invoke\",321,0,\"i32.trunc_sat_f64_s\",[\"f64:13835058055282163712\"]],[\"invoke\",323,0,\"i32.trunc_sat_f64_s\",[\"f64:13970166044103278592\"]],[\"invoke\",324,0,\"i32.trunc_sat_f64_s\",[\"f64:4746794007248502784\"]],[\"invoke\",326,0,\"i32.trunc_sat_f64_s\",[\"f64:9218868437227405312\"]],[\"invoke\",327,0,\"i32.trunc_sat_f64_s\",[\"f64:18442240474082181120\"]],[\"invoke\",328,0,\"i32.trunc_sat_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",333,0,\"i32.trunc_sat_f64_u\",[\"f64:0\"]],[\"invoke\",334,0,\"i32.trunc_sat_f64_u\",[\"f64:9223372036854775808\"]],[\"invoke\",335,0,\"i32.trunc_sat_f64_u\",[\"f64:1\"]],[\"invoke\",337,0,\"i32.trunc_sat_f64_u\",[\"f64:4607182418800017408\"]],[\"invoke\",338,0,\"i32.trunc_sat_f64_u\",[\"f64:4607632778762754458\"]],[\"invoke\",340,0,\"i32.trunc_sat_f64_u\",[\"f64:4611235658464650854\"]],[\"invoke\",341,0,\"i32.trunc_sat_f64_u\",[\"f64:4611686018427387904\"]],[\"invoke\",342,0,\"i32.trunc_sat_f64_u\",[\"f64:4746794007248502784\"]],[\"invoke\",344,0,\"i32.trunc_sat_f64_u\",[\"f64:13829653735729319117\"]],[\"invoke\",345,0,\"i32.trunc_sat_f64_u\",[\"f64:13830554455654793215\"]],[\"invoke\",346,0,\"i32.trunc_sat_f64_u\",[\"f64:4726483295884279808\"]],[\"invoke\",348,0,\"i32.trunc_sat_f64_u\",[\"f64:13830554455654793216\"]],[\"invoke\",349,0,\"i32.trunc_sat_f64_u\",[\"f64:4846369599423283200\"]],[\"invoke\",351,0,\"i32.trunc_sat_f64_u\",[\"f64:4890909195324358656\"]],[\"invoke\",352,0,\"i32.trunc_sat_f64_u\",[\"f64:9218868437227405312\"]],[\"invoke\",353,0,\"i32.trunc_sat_f64_u\",[\"f64:18442240474082181120\"]],[\"invoke\",356,0,\"i32.trunc_sat_f64_u\",[\"f64:18444492273895866368\"]],[\"invoke\",359,0,\"i64.trunc_sat_f32_s\",[\"f32:0\"]],[\"invoke\",361,0,\"i64.trunc_sat_f32_s\",[\"f32:1\"]],[\"invoke\",362,0,\"i64.trunc_sat_f32_s\",[\"f32:2147483649\"]],[\"invoke\",363,0,\"i64.trunc_sat_f32_s\",[\"f32:1065353216\"]],[\"invoke\",365,0,\"i64.trunc_sat_f32_s\",[\"f32:1069547520\"]],[\"invoke\",366,0,\"i64.trunc_sat_f32_s\",[\"f32:3212836864\"]],[\"invoke\",367,0,\"i64.trunc_sat_f32_s\",[\"f32:3213675725\"]],[\"invoke\",369,0,\"i64.trunc_sat_f32_s\",[\"f32:3220386611\"]],[\"invoke\",370,0,\"i64.trunc_sat_f32_s\",[\"f32:3221225472\"]],[\"invoke\",372,0,\"i64.trunc_sat_f32_s\",[\"f32:3481272320\"]],[\"invoke\",373,0,\"i64.trunc_sat_f32_s\",[\"f32:1593835519\"]],[\"invoke\",374,0,\"i64.trunc_sat_f32_s\",[\"f32:3741319168\"]],[\"invoke\",376,0,\"i64.trunc_sat_f32_s\",[\"f32:3741319169\"]],[\"invoke\",377,0,\"i64.trunc_sat_f32_s\",[\"f32:2139095040\"]],[\"invoke\",378,0,\"i64.trunc_sat_f32_s\",[\"f32:4286578688\"]],[\"invoke\",381,0,\"i64.trunc_sat_f32_s\",[\"f32:4290772992\"]],[\"invoke\",384,0,\"i64.trunc_sat_f32_u\",[\"f32:0\"]],[\"invoke\",386,0,\"i64.trunc_sat_f32_u\",[\"f32:1\"]],[\"invoke\",387,0,\"i64.trunc_sat_f32_u\",[\"f32:2147483649\"]],[\"invoke\",388,0,\"i64.trunc_sat_f32_u\",[\"f32:1065353216\"]],[\"invoke\",390,0,\"i64.trunc_sat_f32_u\",[\"f32:1069547520\"]],[\"invoke\",391,0,\"i64.trunc_sat_f32_u\",[\"f32:1333788672\"]],[\"invoke\",392,0,\"i64.trunc_sat_f32_u\",[\"f32:1602224127\"]],[\"invoke\",394,0,\"i64.trunc_sat_f32_u\",[\"f32:3212836863\"]],[\"invoke\",395,0,\"i64.trunc_sat_f32_u\",[\"f32:1602224128\"]],[\"invoke\",397,0,\"i64.trunc_sat_f32_u\",[\"f32:2139095040\"]],[\"invoke\",398,0,\"i64.trunc_sat_f32_u\",[\"f32:4286578688\"]],[\"invoke\",399,0,\"i64.trunc_sat_f32_u\",[\"f32:2143289344\"]],[\"invoke\",404,0,\"i64.trunc_sat_f64_s\",[\"f64:0\"]],[\"invoke\",405,0,\"i64.trunc_sat_f64_s\",[\"f64:9223372036854775808\"]],[\"invoke\",406,0,\"i64.trunc_sat_f64_s\",[\"f64:1\"]],[\"invoke\",408,0,\"i64.trunc_sat_f64_s\",[\"f64:4607182418800017408\"]],[\"invoke\",409,0,\"i64.trunc_sat_f64_s\",[\"f64:4607632778762754458\"]],[\"invoke\",411,0,\"i64.trunc_sat_f64_s\",[\"f64:13830554455654793216\"]],[\"invoke\",412,0,\"i64.trunc_sat_f64_s\",[\"f64:13831004815617530266\"]],[\"invoke\",413,0,\"i64.trunc_sat_f64_s\",[\"f64:13832806255468478464\"]],[\"invoke\",415,0,\"i64.trunc_sat_f64_s\",[\"f64:13835058055282163712\"]],[\"invoke\",416,0,\"i64.trunc_sat_f64_s\",[\"f64:4751297606875873280\"]],[\"invoke\",417,0,\"i64.trunc_sat_f64_s\",[\"f64:13974669643730649088\"]],[\"invoke\",419,0,\"i64.trunc_sat_f64_s\",[\"f64:14114281232179134464\"]],[\"invoke\",420,0,\"i64.trunc_sat_f64_s\",[\"f64:4890909195324358656\"]],[\"invoke\",422,0,\"i64.trunc_sat_f64_s\",[\"f64:9218868437227405312\"]],[\"invoke\",423,0,\"i64.trunc_sat_f64_s\",[\"f64:18442240474082181120\"]],[\"invoke\",424,0,\"i64.trunc_sat_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",429,0,\"i64.trunc_sat_f64_u\",[\"f64:0\"]],[\"invoke\",430,0,\"i64.trunc_sat_f64_u\",[\"f64:9223372036854775808\"]],[\"invoke\",431,0,\"i64.trunc_sat_f64_u\",[\"f64:1\"]],[\"invoke\",433,0,\"i64.trunc_sat_f64_u\",[\"f64:4607182418800017408\"]],[\"invoke\",434,0,\"i64.trunc_sat_f64_u\",[\"f64:4607632778762754458\"]],[\"invoke\",436,0,\"i64.trunc_sat_f64_u\",[\"f64:4751297606873776128\"]],[\"invoke\",437,0,\"i64.trunc_sat_f64_u\",[\"f64:4751297606875873280\"]],[\"invoke\",438,0,\"i64.trunc_sat_f64_u\",[\"f64:4895412794951729151\"]],[\"invoke\",440,0,\"i64.trunc_sat_f64_u\",[\"f64:13830554455654793215\"]],[\"invoke\",441,0,\"i64.trunc_sat_f64_u\",[\"f64:4726483295884279808\"]],[\"invoke\",443,0,\"i64.trunc_sat_f64_u\",[\"f64:4890909195324358656\"]],[\"invoke\",444,0,\"i64.trunc_sat_f64_u\",[\"f64:4895412794951729152\"]],[\"invoke\",445,0,\"i64.trunc_sat_f64_u\",[\"f64:13830554455654793216\"]],[\"invoke\",447,0,\"i64.trunc_sat_f64_u\",[\"f64:18442240474082181120\"]],[\"invoke\",448,0,\"i64.trunc_sat_f64_u\",[\"f64:9221120237041090560\"]],[\"invoke\",450,0,\"i64.trunc_sat_f64_u\",[\"f64:18444492273895866368\"]],[\"invoke\",455,0,\"f32.convert_i32_s\",[\"i32:4278190079\"]],[\"invoke\",456,0,\"f32.convert_i32_s\",[\"i32:16777219\"]],[\"invoke\",459,0,\"f32.convert_i64_s\",[\"i64:1\"]],[\"invoke\",460,0,\"f32.convert_i64_s\",[\"i64:18446744073709551615\"]],[\"invoke\",461,0,\"f32.convert_i64_s\",[\"i64:0\"]],[\"invoke\",463,0,\"f32.convert_i64_s\",[\"i64:9223372036854775808\"]],[\"invoke\",464,0,\"f32.convert_i64_s\",[\"i64:314159265358979\"]],[\"invoke\",466,0,\"f32.convert_i64_s\",[\"i64:16777217\"]],[\"invoke\",468,0,\"f32.convert_i64_s\",[\"i64:16777219\"]],[\"invoke\",469,0,\"f32.convert_i64_s\",[\"i64:18446744073692774397\"]],[\"invoke\",472,0,\"f32.convert_i64_s\",[\"i64:9223372311732682753\"]],[\"invoke\",473,0,\"f32.convert_i64_s\",[\"i64:9007199791611905\"]],[\"invoke\",474,0,\"f32.convert_i64_s\",[\"i64:18437736873917939711\"]],[\"invoke\",477,0,\"f64.convert_i32_s\",[\"i32:4294967295\"]],[\"invoke\",478,0,\"f64.convert_i32_s\",[\"i32:0\"]],[\"invoke\",479,0,\"f64.convert_i32_s\",[\"i32:2147483647\"]],[\"invoke\",481,0,\"f64.convert_i32_s\",[\"i32:987654321\"]],[\"invoke\",483,0,\"f64.convert_i64_s\",[\"i64:1\"]],[\"invoke\",485,0,\"f64.convert_i64_s\",[\"i64:0\"]],[\"invoke\",486,0,\"f64.convert_i64_s\",[\"i64:9223372036854775807\"]],[\"invoke\",487,0,\"f64.convert_i64_s\",[\"i64:9223372036854775808\"]],[\"invoke\",490,0,\"f64.convert_i64_s\",[\"i64:9007199254740993\"]],[\"invoke\",491,0,\"f64.convert_i64_s\",[\"i64:18437736874454810623\"]],[\"invoke\",492,0,\"f64.convert_i64_s\",[\"i64:9007199254740995\"]],[\"invoke\",495,0,\"f32.convert_i32_u\",[\"i32:1\"]],[\"invoke\",496,0,\"f32.convert_i32_u\",[\"i32:0\"]],[\"invoke\",498,0,\"f32.convert_i32_u\",[\"i32:2147483648\"]],[\"invoke\",499,0,\"f32.convert_i32_u\",[\"i32:305419896\"]],[\"invoke\",500,0,\"f32.convert_i32_u\",[\"i32:4294967295\"]],[\"invoke\",502,0,\"f32.convert_i32_u\",[\"i32:2147483777\"]],[\"invoke\",503,0,\"f32.convert_i32_u\",[\"i32:2147483778\"]],[\"invoke\",504,0,\"f32.convert_i32_u\",[\"i32:4294966912\"]],[\"invoke\",506,0,\"f32.convert_i32_u\",[\"i32:4294966914\"]],[\"invoke\",508,0,\"f32.convert_i32_u\",[\"i32:16777217\"]],[\"invoke\",511,0,\"f32.convert_i64_u\",[\"i64:1\"]],[\"invoke\",512,0,\"f32.convert_i64_u\",[\"i64:0\"]],[\"invoke\",513,0,\"f32.convert_i64_u\",[\"i64:9223372036854775807\"]],[\"invoke\",515,0,\"f32.convert_i64_u\",[\"i64:18446744073709551615\"]],[\"invoke\",517,0,\"f32.convert_i64_u\",[\"i64:16777217\"]],[\"invoke\",520,0,\"f32.convert_i64_u\",[\"i64:9007199791611905\"]],[\"invoke\",521,0,\"f32.convert_i64_u\",[\"i64:9223371761976868863\"]],[\"invoke\",522,0,\"f32.convert_i64_u\",[\"i64:9223372586610589697\"]],[\"invoke\",525,0,\"f64.convert_i32_u\",[\"i32:1\"]],[\"invoke\",526,0,\"f64.convert_i32_u\",[\"i32:0\"]],[\"invoke\",527,0,\"f64.convert_i32_u\",[\"i32:2147483647\"]],[\"invoke\",529,0,\"f64.convert_i32_u\",[\"i32:4294967295\"]],[\"invoke\",531,0,\"f64.convert_i64_u\",[\"i64:1\"]],[\"invoke\",533,0,\"f64.convert_i64_u\",[\"i64:9223372036854775807\"]],[\"invoke\",534,0,\"f64.convert_i64_u\",[\"i64:9223372036854775808\"]],[\"invoke\",535,0,\"f64.convert_i64_u\",[\"i64:18446744073709551615\"]],[\"invoke\",537,0,\"f64.convert_i64_u\",[\"i64:9223372036854776833\"]],[\"invoke\",538,0,\"f64.convert_i64_u\",[\"i64:9223372036854776834\"]],[\"invoke\",539,0,\"f64.convert_i64_u\",[\"i64:18446744073709548544\"]],[\"invoke\",541,0,\"f64.convert_i64_u\",[\"i64:18446744073709548546\"]],[\"invoke\",543,0,\"f64.convert_i64_u\",[\"i64:9007199254740993\"]],[\"invoke\",546,0,\"f64.promote_f32\",[\"f32:0\"]],[\"invoke\",547,0,\"f64.promote_f32\",[\"f32:2147483648\"]],[\"invoke\",548,0,\"f64.promote_f32\",[\"f32:1\"]],[\"invoke\",550,0,\"f64.promote_f32\",[\"f32:1065353216\"]],[\"invoke\",551,0,\"f64.promote_f32\",[\"f32:3212836864\"]],[\"invoke\",552,0,\"f64.promote_f32\",[\"f32:4286578687\"]],[\"invoke\",555,0,\"f64.promote_f32\",[\"f32:67108864\"]],[\"invoke\",557,0,\"f64.promote_f32\",[\"f32:2118632255\"]],[\"invoke\",559,0,\"f64.promote_f32\",[\"f32:4286578688\"]],[\"invoke\",560,0,\"f64.promote_f32\",[\"f32:2143289344\"]],[\"invoke\",562,0,\"f64.promote_f32\",[\"f32:4290772992\"]],[\"invoke\",566,0,\"f32.demote_f64\",[\"f64:9223372036854775808\"]],[\"invoke\",567,0,\"f32.demote_f64\",[\"f64:1\"]],[\"invoke\",568,0,\"f32.demote_f64\",[\"f64:9223372036854775809\"]],[\"invoke\",570,0,\"f32.demote_f64\",[\"f64:13830554455654793216\"]],[\"invoke\",571,0,\"f32.demote_f64\",[\"f64:4039728865214464000\"]],[\"invoke\",573,0,\"f32.demote_f64\",[\"f64:4039728865214463999\"]],[\"invoke\",574,0,\"f32.demote_f64\",[\"f64:13263100902069239807\"]],[\"invoke\",575,0,\"f32.demote_f64\",[\"f64:3936146074321813504\"]],[\"invoke\",577,0,\"f32.demote_f64\",[\"f64:5183643170298134528\"]],[\"invoke\",578,0,\"f32.demote_f64\",[\"f64:14407015207152910336\"]],[\"invoke\",579,0,\"f32.demote_f64\",[\"f64:5183643170298134529\"]],[\"invoke\",581,0,\"f32.demote_f64\",[\"f64:5183643170566569984\"]],[\"invoke\",582,0,\"f32.demote_f64\",[\"f64:14407015207421345792\"]],[\"invoke\",584,0,\"f32.demote_f64\",[\"f64:14407015207689781247\"]],[\"invoke\",585,0,\"f32.demote_f64\",[\"f64:5183643170835005440\"]],[\"invoke\",586,0,\"f32.demote_f64\",[\"f64:14407015207689781248\"]],[\"invoke\",588,0,\"f32.demote_f64\",[\"f64:5172657297058430976\"]],[\"invoke\",589,0,\"f32.demote_f64\",[\"f64:9218868437227405312\"]],[\"invoke\",590,0,\"f32.demote_f64\",[\"f64:18442240474082181120\"]],[\"invoke\",592,0,\"f32.demote_f64\",[\"f64:4607182418800017407\"]],[\"invoke\",593,0,\"f32.demote_f64\",[\"f64:4607182419068452864\"]],[\"invoke\",595,0,\"f32.demote_f64\",[\"f64:4607182419605323775\"]],[\"invoke\",596,0,\"f32.demote_f64\",[\"f64:4607182419605323776\"]],[\"invoke\",597,0,\"f32.demote_f64\",[\"f64:4607182420142194688\"]],[\"invoke\",599,0,\"f32.demote_f64\",[\"f64:4715268810125344769\"]],[\"invoke\",600,0,\"f32.demote_f64\",[\"f64:4715268810662215679\"]],[\"invoke\",602,0,\"f32.demote_f64\",[\"f64:5094955347580439664\"]],[\"invoke\",603,0,\"f32.demote_f64\",[\"f64:4101111194527827589\"]],[\"invoke\",604,0,\"f32.demote_f64\",[\"f64:4038806939559600639\"]],[\"invoke\",606,0,\"f32.demote_f64\",[\"f64:14338315240173327556\"]],[\"invoke\",607,0,\"f32.demote_f64\",[\"f64:9221120237041090560\"]],[\"invoke\",609,0,\"f32.demote_f64\",[\"f64:18444492273895866368\"]],[\"invoke\",612,0,\"f32.demote_f64\",[\"f64:9227875636482146304\"]],[\"invoke\",613,0,\"f32.demote_f64\",[\"f64:3931642474694443008\"]],[\"invoke\",615,0,\"f32.demote_f64\",[\"f64:3931642474694443009\"]],[\"invoke\",616,0,\"f32.demote_f64\",[\"f64:13155014511549218817\"]],[\"invoke\",618,0,\"f32.reinterpret_i32\",[\"i32:0\"]],[\"invoke\",620,0,\"f32.reinterpret_i32\",[\"i32:1\"]],[\"invoke\",621,0,\"f32.reinterpret_i32\",[\"i32:4294967295\"]],[\"invoke\",622,0,\"f32.reinterpret_i32\",[\"i32:123456789\"]],[\"invoke\",624,0,\"f32.reinterpret_i32\",[\"i32:2139095040\"]],[\"invoke\",625,0,\"f32.reinterpret_i32\",[\"i32:4286578688\"]],[\"invoke\",627,0,\"f32.reinterpret_i32\",[\"i32:4290772992\"]],[\"invoke\",628,0,\"f32.reinterpret_i32\",[\"i32:2141192192\"]],[\"invoke\",629,0,\"f32.reinterpret_i32\",[\"i32:4288675840\"]],[\"invoke\",632,0,\"f64.reinterpret_i64\",[\"i64:1\"]],[\"invoke\",633,0,\"f64.reinterpret_i64\",[\"i64:18446744073709551615\"]],[\"invoke\",634,0,\"f64.reinterpret_i64\",[\"i64:9223372036854775808\"]],[\"invoke\",636,0,\"f64.reinterpret_i64\",[\"i64:9223372036854775809\"]],[\"invoke\",637,0,\"f64.reinterpret_i64\",[\"i64:9218868437227405312\"]],[\"invoke\",639,0,\"f64.reinterpret_i64\",[\"i64:9221120237041090560\"]],[\"invoke\",640,0,\"f64.reinterpret_i64\",[\"i64:18444492273895866368\"]],[\"invoke\",641,0,\"f64.reinterpret_i64\",[\"i64:9219994337134247936\"]],[\"invoke\",644,0,\"i32.reinterpret_f32\",[\"f32:0\"]],[\"invoke\",645,0,\"i32.reinterpret_f32\",[\"f32:2147483648\"]],[\"invoke\",646,0,\"i32.reinterpret_f32\",[\"f32:1\"]],[\"invoke\",649,0,\"i32.reinterpret_f32\",[\"f32:1065353216\"]],[\"invoke\",650,0,\"i32.reinterpret_f32\",[\"f32:1078530010\"]],[\"invoke\",652,0,\"i32.reinterpret_f32\",[\"f32:4286578687\"]],[\"invoke\",653,0,\"i32.reinterpret_f32\",[\"f32:2139095040\"]],[\"invoke\",654,0,\"i32.reinterpret_f32\",[\"f32:4286578688\"]],[\"invoke\",656,0,\"i32.reinterpret_f32\",[\"f32:4290772992\"]],[\"invoke\",660,0,\"i64.reinterpret_f64\",[\"f64:0\"]],[\"invoke\",661,0,\"i64.reinterpret_f64\",[\"f64:9223372036854775808\"]],[\"invoke\",664,0,\"i64.reinterpret_f64\",[\"f64:9223372036854775809\"]],[\"invoke\",665,0,\"i64.reinterpret_f64\",[\"f64:4607182418800017408\"]],[\"invoke\",667,0,\"i64.reinterpret_f64\",[\"f64:9218868437227405311\"]],[\"invoke\",668,0,\"i64.reinterpret_f64\",[\"f64:18442240474082181119\"]],[\"invoke\",669,0,\"i64.reinterpret_f64\",[\"f64:9218868437227405312\"]],[\"invoke\",671,0,\"i64.reinterpret_f64\",[\"f64:9221120237041090560\"]],[\"invoke\",672,0,\"i64.reinterpret_f64\",[\"f64:18444492273895866368\"]],[\"file\",\"traps.wast\"],[\"module\",5,23],[\"invoke\",16,0,\"no_dce.i32.div_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",17,0,\"no_dce.i32.div_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",18,0,\"no_dce.i64.div_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",19,0,\"no_dce.i64.div_u\",[\"i64:1\",\"i64:0\"]],[\"invoke\",20,0,\"no_dce.i32.div_s\",[\"i32:2147483648\",\"i32:4294967295\"]],[\"invoke\",21,0,\"no_dce.i64.div_s\",[\"i64:9223372036854775808\",\"i64:18446744073709551615\"]],[\"module\",23,24],[\"invoke\",34,0,\"no_dce.i32.rem_s\",[\"i32:1\",\"i32:0\"]],[\"invoke\",35,0,\"no_dce.i32.rem_u\",[\"i32:1\",\"i32:0\"]],[\"invoke\",36,0,\"no_dce.i64.rem_s\",[\"i64:1\",\"i64:0\"]],[\"invoke\",37,0,\"no_dce.i64.rem_u\",[\"i64:1\",\"i64:0\"]],[\"module\",39,25],[\"invoke\",50,0,\"no_dce.i32.trunc_f32_s\",[\"f32:2143289344\"]],[\"invoke\",51,0,\"no_dce.i32.trunc_f32_u\",[\"f32:2143289344\"]],[\"invoke\",52,0,\"no_dce.i32.trunc_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",53,0,\"no_dce.i32.trunc_f64_u\",[\"f64:9221120237041090560\"]],[\"invoke\",54,0,\"no_dce.i64.trunc_f32_s\",[\"f32:2143289344\"]],[\"invoke\",55,0,\"no_dce.i64.trunc_f32_u\",[\"f32:2143289344\"]],[\"invoke\",56,0,\"no_dce.i64.trunc_f64_s\",[\"f64:9221120237041090560\"]],[\"invoke\",57,0,\"no_dce.i64.trunc_f64_u\",[\"f64:9221120237041090560\"]],[\"module\",59,26],[\"invoke\",78,0,\"no_dce.i32.load\",[\"i32:65536\"]],[\"invoke\",79,0,\"no_dce.i32.load16_s\",[\"i32:65536\"]],[\"invoke\",80,0,\"no_dce.i32.load16_u\",[\"i32:65536\"]],[\"invoke\",81,0,\"no_dce.i32.load8_s\",[\"i32:65536\"]],[\"invoke\",82,0,\"no_dce.i32.load8_u\",[\"i32:65536\"]],[\"invoke\",83,0,\"no_dce.i64.load\",[\"i32:65536\"]],[\"invoke\",84,0,\"no_dce.i64.load32_s\",[\"i32:65536\"]],[\"invoke\",85,0,\"no_dce.i64.load32_u\",[\"i32:65536\"]],[\"invoke\",86,0,\"no_dce.i64.load16_s\",[\"i32:65536\"]],[\"invoke\",87,0,\"no_dce.i64.load16_u\",[\"i32:65536\"]],[\"invoke\",88,0,\"no_dce.i64.load8_s\",[\"i32:65536\"]],[\"invoke\",89,0,\"no_dce.i64.load8_u\",[\"i32:65536\"]],[\"invoke\",90,0,\"no_dce.f32.load\",[\"i32:65536\"]],[\"invoke\",91,0,\"no_dce.f64.load\",[\"i32:65536\"]]]"; diff --git a/tests/corpus/webassembly-spec-int/main.mjs b/tests/corpus/webassembly-spec-int/main.mjs new file mode 100644 index 000000000..3f5bef066 --- /dev/null +++ b/tests/corpus/webassembly-spec-int/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (6 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-memory/data.mjs b/tests/corpus/webassembly-spec-memory/data.mjs new file mode 100644 index 000000000..d62a7e491 --- /dev/null +++ b/tests/corpus/webassembly-spec-memory/data.mjs @@ -0,0 +1,175 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * address.wast, align.wast, endianness.wast, float_memory.wast, load.wast, store.wast, memory.wast, memory_grow.wast, memory_redundancy.wast, memory_size.wast, memory_trap.wast, bulk.wast, memory_copy.wast, memory_fill.wast, memory_init.wast, data.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABCgJgAX8Bf2ABfwADHx4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEFAwEAAQfNAh4IOHVfZ29vZDEAAAg4dV9nb29kMgABCDh1X2dvb2QzAAIIOHVfZ29vZDQAAwg4dV9nb29kNQAECDhzX2dvb2QxAAUIOHNfZ29vZDIABgg4c19nb29kMwAHCDhzX2dvb2Q0AAgIOHNfZ29vZDUACQkxNnVfZ29vZDEACgkxNnVfZ29vZDIACwkxNnVfZ29vZDMADAkxNnVfZ29vZDQADQkxNnVfZ29vZDUADgkxNnNfZ29vZDEADwkxNnNfZ29vZDIAEAkxNnNfZ29vZDMAEQkxNnNfZ29vZDQAEgkxNnNfZ29vZDUAEwgzMl9nb29kMQAUCDMyX2dvb2QyABUIMzJfZ29vZDMAFggzMl9nb29kNAAXCDMyX2dvb2Q1ABgGOHVfYmFkABkGOHNfYmFkABoHMTZ1X2JhZAAbBzE2c19iYWQAHAYzMl9iYWQAHQqKAh4HACAALQAACwcAIAAtAAALBwAgAC0AAQsHACAALQACCwcAIAAtABkLBwAgACwAAAsHACAALAAACwcAIAAsAAELBwAgACwAAgsHACAALAAZCwcAIAAvAQALBwAgAC8AAAsHACAALwABCwcAIAAvAQILBwAgAC8BGQsHACAALgEACwcAIAAuAAALBwAgAC4AAQsHACAALgECCwcAIAAuARkLBwAgACgCAAsHACAAKAAACwcAIAAoAAELBwAgACgBAgsHACAAKAIZCwwAIAAtAP////8PGgsMACAALAD/////DxoLDAAgAC8B/////w8aCwwAIAAuAf////8PGgsMACAAKAL/////DxoLCyABAEEACxphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg==", + "AGFzbQEAAAABCgJgAX8BfmABfwADKyoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEFAwEAAQfZAyoIOHVfZ29vZDEAAAg4dV9nb29kMgABCDh1X2dvb2QzAAIIOHVfZ29vZDQAAwg4dV9nb29kNQAECDhzX2dvb2QxAAUIOHNfZ29vZDIABgg4c19nb29kMwAHCDhzX2dvb2Q0AAgIOHNfZ29vZDUACQkxNnVfZ29vZDEACgkxNnVfZ29vZDIACwkxNnVfZ29vZDMADAkxNnVfZ29vZDQADQkxNnVfZ29vZDUADgkxNnNfZ29vZDEADwkxNnNfZ29vZDIAEAkxNnNfZ29vZDMAEQkxNnNfZ29vZDQAEgkxNnNfZ29vZDUAEwkzMnVfZ29vZDEAFAkzMnVfZ29vZDIAFQkzMnVfZ29vZDMAFgkzMnVfZ29vZDQAFwkzMnVfZ29vZDUAGAkzMnNfZ29vZDEAGQkzMnNfZ29vZDIAGgkzMnNfZ29vZDMAGwkzMnNfZ29vZDQAHAkzMnNfZ29vZDUAHQg2NF9nb29kMQAeCDY0X2dvb2QyAB8INjRfZ29vZDMAIAg2NF9nb29kNAAhCDY0X2dvb2Q1ACIGOHVfYmFkACMGOHNfYmFkACQHMTZ1X2JhZAAlBzE2c19iYWQAJgczMnVfYmFkACcHMzJzX2JhZAAoBjY0X2JhZAApCvQCKgcAIAAxAAALBwAgADEAAAsHACAAMQABCwcAIAAxAAILBwAgADEAGQsHACAAMAAACwcAIAAwAAALBwAgADAAAQsHACAAMAACCwcAIAAwABkLBwAgADMBAAsHACAAMwAACwcAIAAzAAELBwAgADMBAgsHACAAMwEZCwcAIAAyAQALBwAgADIAAAsHACAAMgABCwcAIAAyAQILBwAgADIBGQsHACAANQIACwcAIAA1AAALBwAgADUAAQsHACAANQECCwcAIAA1AhkLBwAgADQCAAsHACAANAAACwcAIAA0AAELBwAgADQBAgsHACAANAIZCwcAIAApAwALBwAgACkAAAsHACAAKQABCwcAIAApAQILBwAgACkDGQsMACAAMQD/////DxoLDAAgADAA/////w8aCwwAIAAzAf////8PGgsMACAAMgH/////DxoLDAAgADUC/////w8aCwwAIAA0Av////8PGgsMACAAKQP/////DxoLCyABAEEACxphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5eg==", + "AGFzbQEAAAABCgJgAX8BfWABfwADBwYAAAAAAAEFAwEAAQdBBggzMl9nb29kMQAACDMyX2dvb2QyAAEIMzJfZ29vZDMAAggzMl9nb29kNAADCDMyX2dvb2Q1AAQGMzJfYmFkAAUKNgYHACAAKgIACwcAIAAqAAALBwAgACoAAQsHACAAKgECCwcAIAAqAggLDAAgACoC/////w8aCwsSAQBBAAsMAAAAAAAAoH8BANB/", + "AGFzbQEAAAABCgJgAX8BfGABfwADBwYAAAAAAAEFAwEAAQdBBgg2NF9nb29kMQAACDY0X2dvb2QyAAEINjRfZ29vZDMAAgg2NF9nb29kNAADCDY0X2dvb2Q1AAQGNjRfYmFkAAUKNgYHACAAKwMACwcAIAArAAALBwAgACsAAQsHACAAKwECCwcAIAArAxILDAAgACsD/////w8aCwsgAQBBAAsaAAAAAAAAAAAAAAAAAAAAAPR/AQAAAAAA/H8=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALAAAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALQAAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALgEAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEALwEAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKAIAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMAAAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMQAAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMgEAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAMwEAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEANAIAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEANQIAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKQMAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKgIAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCgEIAEEAKwMAGgs=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE6AAAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE7AQAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQQE2AgAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE8AAAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE9AQAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE+AgAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKCwEJAEEAQgE3AwAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKDgEMAEEAQwAAgD84AgAL", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAAKEgEQAEEARAAAAAAAAPA/OQMACw==", + "AGFzbQEAAAABFwRgAX8BfWABfwF8YAJ/fwF/YAJ/fwF+AwUEAAECAwUDAQABB00EEGYzMl9hbGlnbl9zd2l0Y2gAABBmNjRfYWxpZ25fc3dpdGNoAAEQaTMyX2FsaWduX3N3aXRjaAACEGk2NF9hbGlnbl9zd2l0Y2gAAwr7CARjAQJ9QwAAIEEhAQJAAkACQAJAAkAgAA4EAAECAwQLQQAgATgCAEEAKgIAIQIMAwtBACABOAAAQQAqAAAhAgwCC0EAIAE4AQBBACoBACECDAELQQAgATgCAEEAKgIAIQILIAILewECfEQAAAAAAAAkQCEBAkACQAJAAkACQAJAIAAOBQABAgMEBQtBACABOQMAQQArAwAhAgwEC0EAIAE5AABBACsAACECDAMLQQAgATkBAEEAKwEAIQIMAgtBACABOQIAQQArAgAhAgwBC0EAIAE5AwBBACsDACECCyACC+ICAQJ/QQohAgJAAkACQAJAAkACQCAADgUAAQIDBAULIAFBAEYEQEEAIAI6AABBACwAACEDCyABQQFGBEBBACACOgAAQQAsAAAhAwsMBAsgAUEARgRAQQAgAjoAAEEALQAAIQMLIAFBAUYEQEEAIAI6AABBAC0AACEDCwwDCyABQQBGBEBBACACOwEAQQAuAQAhAwsgAUEBRgRAQQAgAjsAAEEALgAAIQMLIAFBAkYEQEEAIAI7AQBBAC4BACEDCwwCCyABQQBGBEBBACACOwEAQQAvAQAhAwsgAUEBRgRAQQAgAjsAAEEALwAAIQMLIAFBAkYEQEEAIAI7AQBBAC8BACEDCwwBCyABQQBGBEBBACACNgIAQQAoAgAhAwsgAUEBRgRAQQAgAjYAAEEAKAAAIQMLIAFBAkYEQEEAIAI2AQBBACgBACEDCyABQQRGBEBBACACNgIAQQAoAgAhAwsLIAMLtAQBAn5CCiECAkACQAJAAkACQAJAAkACQCAADgcAAQIDBAUGBwsgAUEARgRAQQAgAjwAAEEAMAAAIQMLIAFBAUYEQEEAIAI8AABBADAAACEDCwwGCyABQQBGBEBBACACPAAAQQAxAAAhAwsgAUEBRgRAQQAgAjwAAEEAMQAAIQMLDAULIAFBAEYEQEEAIAI9AQBBADIBACEDCyABQQFGBEBBACACPQAAQQAyAAAhAwsgAUECRgRAQQAgAj0BAEEAMgEAIQMLDAQLIAFBAEYEQEEAIAI9AQBBADMBACEDCyABQQFGBEBBACACPQAAQQAzAAAhAwsgAUECRgRAQQAgAj0BAEEAMwEAIQMLDAMLIAFBAEYEQEEAIAI+AgBBADQCACEDCyABQQFGBEBBACACPgAAQQA0AAAhAwsgAUECRgRAQQAgAj4BAEEANAEAIQMLIAFBBEYEQEEAIAI+AgBBADQCACEDCwwCCyABQQBGBEBBACACPgIAQQA1AgAhAwsgAUEBRgRAQQAgAj4AAEEANQAAIQMLIAFBAkYEQEEAIAI+AQBBADUBACEDCyABQQRGBEBBACACPgIAQQA1AgAhAwsMAQsgAUEARgRAQQAgAjcDAEEAKQMAIQMLIAFBAUYEQEEAIAI3AABBACkAACEDCyABQQJGBEBBACACNwEAQQApAQAhAwsgAUEERgRAQQAgAjcCAEEAKQIAIQMLIAFBCEYEQEEAIAI3AwBBACkDACEDCwsgAws=", + "AGFzbQEAAAABCwJgAn9+AGABfwF/AwMCAAEFAwEAAQcQAgVzdG9yZQAABGxvYWQAAQoTAgkAIAAgATcCAAsHACAAKAIACw==", + "AGFzbQEAAAABJAdgAn9/AGACf34AYAF/AX9gAX8BfmABfgF+YAF9AX1gAXwBfAMYFwAAAQICAwICAgQEBAQEBQYCAgQEBAUGBQMBAAEH4QERDGkzMl9sb2FkMTZfcwAGDGkzMl9sb2FkMTZfdQAHCGkzMl9sb2FkAAgMaTY0X2xvYWQxNl9zAAkMaTY0X2xvYWQxNl91AAoMaTY0X2xvYWQzMl9zAAsMaTY0X2xvYWQzMl91AAwIaTY0X2xvYWQADQhmMzJfbG9hZAAOCGY2NF9sb2FkAA8LaTMyX3N0b3JlMTYAEAlpMzJfc3RvcmUAEQtpNjRfc3RvcmUxNgASC2k2NF9zdG9yZTMyABMJaTY0X3N0b3JlABQJZjMyX3N0b3JlABUJZjY0X3N0b3JlABYK9gIXFgAgACABOgAAIABBAWogAUEIdjoAAAsUACAAIAEQACAAQQJqIAFBEHYQAAsWACAAIAGnEAEgAEEEaiABQiCIpxABCxMAIAAtAAAgAEEBai0AAEEIdHILEQAgABADIABBAmoQA0EQdHILEwAgABAErSAAQQRqEAStQiCGhAsNAEEAIAAQAEEALgEACw0AQQAgABAAQQAvAQALDQBBACAAEAFBACgCAAsOAEEAIACnEABBADIBAAsOAEEAIACnEABBADMBAAsOAEEAIACnEAFBADQCAAsOAEEAIACnEAFBADUCAAsNAEEAIAAQAkEAKQMACw4AQQAgALwQAUEAKgIACw4AQQAgAL0QAkEAKwMACw0AQQAgADsBAEEAEAMLDQBBACAANgIAQQAQBAsOAEEAIAA9AQBBABADrQsOAEEAIAA+AgBBABAErQsNAEEAIAA3AwBBABAFCw4AQQAgADgCAEEAEAS+Cw4AQQAgADkDAEEAEAW/Cw==", + "AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBACoCAAsHAEEAKAIACwwAQQBDAACgfzgCAAsNAEEAQYCAgP0HNgIACwkAQQBBADYCAAsLCgEAQQALBAAAoH8=", + "AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBACsDAAsHAEEAKQMACxAAQQBEAAAAAAAA9H85AwALEgBBAEKAgICAgICA+v8ANwMACwkAQQBCADcDAAsLDgEAQQALCAAAAAAAAPR/", + "AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBASoCAAsHAEEBKAIACwwAQQFDAACgfzgCAAsNAEEBQYCAgP0HNgIACwkAQQFBADYCAAsLCwEAQQALBQAAAKB/", + "AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBASsDAAsHAEEBKQMACxAAQQFEAAAAAAAA9H85AwALEgBBAUKAgICAgICA+v8ANwMACwkAQQFCADcDAAsLDwEAQQALCQAAAAAAAAD0fw==", + "AGFzbQEAAAABDANgAAF9YAABf2AAAAMGBQABAgICBQQBAQEBBzcFCGYzMi5sb2FkAAAIaTMyLmxvYWQAAQlmMzIuc3RvcmUAAglpMzIuc3RvcmUAAwVyZXNldAAECjYFBwBBACoCAAsHAEEAKAIACwwAQQBDAQDQfzgCAAsNAEEAQYGAwP4HNgIACwkAQQBBADYCAAsLCgEAQQALBAEA0H8=", + "AGFzbQEAAAABDANgAAF8YAABfmAAAAMGBQABAgICBQQBAQEBBzcFCGY2NC5sb2FkAAAIaTY0LmxvYWQAAQlmNjQuc3RvcmUAAglpNjQuc3RvcmUAAwVyZXNldAAECj8FBwBBACsDAAsHAEEAKQMACxAAQQBEAQAAAAAA/H85AwALEgBBAEKBgICAgICA/v8ANwMACwkAQQBCADcDAAsLDgEAQQALCAEAAAAAAPx/", + "AGFzbQEAAAABFQRgA39/fwF/YAABf2AAAGACf38BfwMnJgECAQECAQEBAQEBAwMBAAEBAQEBAQECAQIBAQICAgIBAQEBAQEBBAUBcAEBAQUDAQABBgYBfwFBAAsHtwUlC2FzLWJyLXZhbHVlAAANYXMtYnJfaWYtY29uZAABDmFzLWJyX2lmLXZhbHVlAAITYXMtYnJfaWYtdmFsdWUtY29uZAADEWFzLWJyX3RhYmxlLWluZGV4AAQRYXMtYnJfdGFibGUtdmFsdWUABRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAGD2FzLXJldHVybi12YWx1ZQAHCmFzLWlmLWNvbmQACAphcy1pZi10aGVuAAkKYXMtaWYtZWxzZQAKD2FzLXNlbGVjdC1maXJzdAALEGFzLXNlbGVjdC1zZWNvbmQADA5hcy1zZWxlY3QtY29uZAANDWFzLWNhbGwtZmlyc3QADwthcy1jYWxsLW1pZAAQDGFzLWNhbGwtbGFzdAARFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAEhRhcy1jYWxsX2luZGlyZWN0LW1pZAATFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAUFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAFRJhcy1sb2NhbC5zZXQtdmFsdWUAFhJhcy1sb2NhbC50ZWUtdmFsdWUAFxNhcy1nbG9iYWwuc2V0LXZhbHVlABgPYXMtbG9hZC1hZGRyZXNzABkQYXMtbG9hZE4tYWRkcmVzcwAaEGFzLXN0b3JlLWFkZHJlc3MAGw5hcy1zdG9yZS12YWx1ZQAcEWFzLXN0b3JlTi1hZGRyZXNzAB0PYXMtc3RvcmVOLXZhbHVlAB4QYXMtdW5hcnktb3BlcmFuZAAfDmFzLWJpbmFyeS1sZWZ0ACAPYXMtYmluYXJ5LXJpZ2h0ACEPYXMtdGVzdC1vcGVyYW5kACIPYXMtY29tcGFyZS1sZWZ0ACMQYXMtY29tcGFyZS1yaWdodAAkE2FzLW1lbW9yeS5ncm93LXNpemUAJQkHAQBBAAsBDgqGBCYMAAJ/QQAoAgAMAAsLDAACQEEAKAIADQALCxEAAn9BACgCAEEBDQAaQQcLCxEAAn9BBkEAKAIADQAaQQcLCw8AAkBBACgCAA4CAAAACwsTAAJ/QQAoAgBBAQ4CAAAAQQcLCxIAAn9BBkEAKAIADgEAAEEHCwsIAEEAKAIADwsPAEEAKAIABH9BAAVBAQsLDwBBAQR/QQAoAgAFQQALCw8AQQAEf0EABUEAKAIACwsMAEEAKAIAIAAgARsLDAAgAEEAKAIAIAEbCwwAQQBBAUEAKAIAGwsEAEF/Cw0AQQAoAgBBAkEDEA4LDQBBAUEAKAIAQQMQDgsNAEEBQQJBACgCABAOCxAAQQAoAgBBAkEDQQARAAALEABBAUEAKAIAQQNBABEAAAsQAEEBQQJBACgCAEEAEQAACxAAQQFBAkEDQQAoAgARAAALCwEBf0EAKAIAIQALCwEBf0EAKAIAIgALCwEBf0EAKAIAJAALCgBBACgCACgCAAsKAEEAKAIALAAACwwAQQAoAgBBBzYCAAsMAEECQQAoAgA2AgALDABBACwAAEEHOgAACwwAQQJBACgCADsBAAsJAEHkACgCAGcLCwBB5AAoAgBBCmoLCwBBCkHkACgCAGsLCQBB5AAoAgBFCwsAQeQAKAIAQQpMCwsAQQpB5AAoAgBHCwoAQeQAKAIAQAAL", + "AGFzbQEAAAABBAFgAAADCgkAAAAAAAAAAAAFAwEAAQeXAQkOYXMtYmxvY2stdmFsdWUAAA1hcy1sb29wLXZhbHVlAAELYXMtYnItdmFsdWUAAg5hcy1icl9pZi12YWx1ZQADE2FzLWJyX2lmLXZhbHVlLWNvbmQABBFhcy1icl90YWJsZS12YWx1ZQAFD2FzLXJldHVybi12YWx1ZQAGCmFzLWlmLXRoZW4ABwphcy1pZi1lbHNlAAgKiAEJDAACQEEAQQE2AgALCwwAA0BBAEEBNgIACwsOAAJAQQBBATYCAAwACwsQAAJAQQBBATYCAEEBDQALCxAAAkBBBkEAQQE2AgANAAsLEQACQEEAQQE2AgBBAQ4AAAsLCgBBAEEBNgIADwsOAEEBBEBBAEEBNgIACwsPAEEABEAFQQBBATYCAAsL", + "AGFzbQEAAAAFAwEAAA==", + "AGFzbQEAAAAFAwEAAQ==", + "AGFzbQEAAAAFBAEBAAA=", + "AGFzbQEAAAAFBAEBAAE=", + "AGFzbQEAAAAFBQEBAYAC", + "AGFzbQEAAAAFBgEBAICABA==", + "AGFzbQEAAAABBQFgAAF/AwIBAAUEAQEAAAcLAQdtZW1zaXplAAAKBgEEAD8ACwsGAQBBAAsA", + "AGFzbQEAAAABBQFgAAF/AwIBAAUEAQEBAQcLAQdtZW1zaXplAAAKBgEEAD8ACwsHAQBBAAsBeA==", + "AGFzbQEAAAABEwRgAAF/YAABfGABfwF/YAF+AX4DDQwAAQICAgIDAwMDAwMFAwEAAQehAQwEZGF0YQAABGNhc3QAAQtpMzJfbG9hZDhfcwACC2kzMl9sb2FkOF91AAMMaTMyX2xvYWQxNl9zAAQMaTMyX2xvYWQxNl91AAULaTY0X2xvYWQ4X3MABgtpNjRfbG9hZDhfdQAHDGk2NF9sb2FkMTZfcwAIDGk2NF9sb2FkMTZfdQAJDGk2NF9sb2FkMzJfcwAKDGk2NF9sb2FkMzJfdQALCp8CDE4AQQAtAABBwQBGQQMtAABBpwFGcUEGLQAAQQBGQRMtAABBAEZxcUEULQAAQdcARkEXLQAAQc0ARnFBGC0AAEEARkH/By0AAEEARnFxcQs4AEEIQseffzcDAEEIKwMAQseff79hBEBEAAAAAAAAAAAPC0EJQgA3AABBD0HFgAE7AABBCSsAAAsOAEEIIAA6AABBCCwAAAsOAEEIIAA6AABBCC0AAAsOAEEIIAA7AQBBCC4BAAsOAEEIIAA7AQBBCC8BAAsOAEEIIAA8AABBCDAAAAsOAEEIIAA8AABBCDEAAAsOAEEIIAA9AQBBCDIBAAsOAEEIIAA9AQBBCDMBAAsOAEEIIAA+AgBBCDQCAAsOAEEIIAA+AgBBCDUCAAsLFAIAQQALBUFCQ6dEAEEUCwRXQVNN", + "AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAQEGFgN/AEGQzgALfwBBkM4AC38AQZDOAAsHOgUGbWVtb3J5AgAKX19kYXRhX2VuZAMAC19fc3RhY2tfdG9wAwELX19oZWFwX2Jhc2UDAgRsb2FkAAAKCQEHACAALQAACw==", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8DBwYAAQABAgAFAwEAAAdXBgxsb2FkX2F0X3plcm8AAA1zdG9yZV9hdF96ZXJvAAERbG9hZF9hdF9wYWdlX3NpemUAAhJzdG9yZV9hdF9wYWdlX3NpemUAAwRncm93AAQEc2l6ZQAFCjUGBwBBACgCAAsJAEEAQQI2AgALCQBBgIAEKAIACwsAQYCABEEDNgIACwYAIABAAAsEAD8ACw==", + "AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAAcIAQRncm93AAAKCAEGACAAQAAL", + "AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAAoHCAEEZ3JvdwAACggBBgAgAEAACw==", + "AGFzbQEAAAABDAJgAX8Bf2ACf38BfwMDAgABBQMBAAEHHAIEZ3JvdwAAEWNoZWNrLW1lbW9yeS16ZXJvAAEKPAIGACAAQAALMwEBf0EBIQICQANAIAAtAAAhAiACQQBHDQEgACABTw0BIABBAWohACAAIAFNDQALCyACCw==", + "AGFzbQEAAAABFQRgA39/fwF/YAABf2AAAGACf38BfwMnJgECAQECAQEBAQEBAwMBAAEBAQEBAQECAQIBAQICAgIBAQEBAQEBBAUBcAEBAQUDAQABBgYBfwFBAAsHtwUlC2FzLWJyLXZhbHVlAAANYXMtYnJfaWYtY29uZAABDmFzLWJyX2lmLXZhbHVlAAITYXMtYnJfaWYtdmFsdWUtY29uZAADEWFzLWJyX3RhYmxlLWluZGV4AAQRYXMtYnJfdGFibGUtdmFsdWUABRdhcy1icl90YWJsZS12YWx1ZS1pbmRleAAGD2FzLXJldHVybi12YWx1ZQAHCmFzLWlmLWNvbmQACAphcy1pZi10aGVuAAkKYXMtaWYtZWxzZQAKD2FzLXNlbGVjdC1maXJzdAALEGFzLXNlbGVjdC1zZWNvbmQADA5hcy1zZWxlY3QtY29uZAANDWFzLWNhbGwtZmlyc3QADwthcy1jYWxsLW1pZAAQDGFzLWNhbGwtbGFzdAARFmFzLWNhbGxfaW5kaXJlY3QtZmlyc3QAEhRhcy1jYWxsX2luZGlyZWN0LW1pZAATFWFzLWNhbGxfaW5kaXJlY3QtbGFzdAAUFmFzLWNhbGxfaW5kaXJlY3QtaW5kZXgAFRJhcy1sb2NhbC5zZXQtdmFsdWUAFhJhcy1sb2NhbC50ZWUtdmFsdWUAFxNhcy1nbG9iYWwuc2V0LXZhbHVlABgPYXMtbG9hZC1hZGRyZXNzABkQYXMtbG9hZE4tYWRkcmVzcwAaEGFzLXN0b3JlLWFkZHJlc3MAGw5hcy1zdG9yZS12YWx1ZQAcEWFzLXN0b3JlTi1hZGRyZXNzAB0PYXMtc3RvcmVOLXZhbHVlAB4QYXMtdW5hcnktb3BlcmFuZAAfDmFzLWJpbmFyeS1sZWZ0ACAPYXMtYmluYXJ5LXJpZ2h0ACEPYXMtdGVzdC1vcGVyYW5kACIPYXMtY29tcGFyZS1sZWZ0ACMQYXMtY29tcGFyZS1yaWdodAAkE2FzLW1lbW9yeS5ncm93LXNpemUAJQkHAQBBAAsBDgraAyYLAAJ/QQBAAAwACwsLAAJAQQBAAA0ACwsQAAJ/QQBAAEEBDQAaQQcLCxAAAn9BBkEAQAANABpBBwsLDgACQEEAQAAOAgAAAAsLEgACf0EAQABBAQ4CAAAAQQcLCxEAAn9BBkEAQAAOAQAAQQcLCwcAQQBAAA8LDgBBAEAABH9BAAVBAQsLDgBBAQR/QQBAAAVBAAsLDgBBAAR/QQAFQQBAAAsLCwBBAEAAIAAgARsLCwAgAEEAQAAgARsLCwBBAEEBQQBAABsLBABBfwsMAEEAQABBAkEDEA4LDABBAUEAQABBAxAOCwwAQQFBAkEAQAAQDgsPAEEAQABBAkEDQQARAAALDwBBAUEAQABBA0EAEQAACw8AQQFBAkEAQABBABEAAAsPAEEBQQJBA0EAQAARAAALCgEBf0EAQAAhAAsKAQF/QQBAACIACwoBAX9BAEAAJAALCQBBAEAAKAIACwkAQQBAACwAAAsLAEEAQABBBzYCAAsLAEECQQBAADYCAAsLAEEAQABBBzoAAAsLAEECQQBAADsBAAsHAEEAQABnCwkAQQBAAEEKagsJAEEKQQBAAGsLBwBBAEAARQsJAEEAQABBCkwLCQBBCkEAQABHCwgAQQBAAEAACw==", + "AGFzbQEAAAABBQFgAAF/AwIBAAUDAQABBxECBm1lbW9yeQIABGdyb3cAAAoIAQYAQQFAAAs=", + "AGFzbQEAAAABBQFgAAF/AhgBDGdyb3duLW1lbW9yeQZtZW1vcnkCAAIDAgEABxECBm1lbW9yeQIABGdyb3cAAAoIAQYAQQFAAAs=", + "AGFzbQEAAAABBQFgAAF/AiEBFWdyb3duLWltcG9ydGVkLW1lbW9yeQZtZW1vcnkCAAMDAgEABwgBBHNpemUAAAoGAQQAPwAL", + "AGFzbQEAAAABEQRgAABgAAF/YAABfWABfwF/AwcGAAEBAgMBBQQBAQEBB2sGD3plcm9fZXZlcnl0aGluZwAAEnRlc3Rfc3RvcmVfdG9fbG9hZAABE3Rlc3RfcmVkdW5kYW50X2xvYWQAAg90ZXN0X2RlYWRfc3RvcmUAAwZtYWxsb2MABA9tYWxsb2NfYWxpYXNpbmcABQqlAQYeAEEAQQA2AgBBBEEANgIAQQhBADYCAEEMQQA2AgALGABBCEEANgIAQQVDAAAAgDgCAEEIKAIACyIBAn9BCCgCACEAQQVBgICAgHg2AgBBCCgCACEBIAAgAWoLHwEBfUEIQaPGjJkCNgIAQQsqAgAhAEEIQQA2AgAgAAsEAEEQCyMBAn9BBBAEIQBBBBAEIQEgAEEqNgIAIAFBKzYCACAAKAIACw==", + "AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQMBAAAHDwIEc2l6ZQAABGdyb3cAAQoOAgQAPwALBwAgAEAAGgs=", + "AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQMBAAEHDwIEc2l6ZQAABGdyb3cAAQoOAgQAPwALBwAgAEAAGgs=", + "AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQQBAQACBw8CBHNpemUAAARncm93AAEKDgIEAD8ACwcAIABAABoL", + "AGFzbQEAAAABCQJgAAF/YAF/AAMDAgABBQQBAQMIBw8CBHNpemUAAARncm93AAEKDgIEAD8ACwcAIABAABoL", + "AGFzbQEAAAABDwNgAAF/YAJ/fwBgAX8BfwMFBAABAgIFAwEAAQceAwVzdG9yZQABBGxvYWQAAgttZW1vcnkuZ3JvdwADCioECQA/AEGAgARsCwwAEAAgAGogATYCAAsKABAAIABqKAIACwYAIABAAAs=", + "AGFzbQEAAAABKQhgAX8Bf2ABfwF+YAF/AX1gAX8BfGACf38AYAJ/fgBgAn99AGACf3wAAxgXAAECAwAAAAABAQEBAQEEBQYHBAQFBQUFAwEAAQezAhcIaTMyLmxvYWQAAAhpNjQubG9hZAABCGYzMi5sb2FkAAIIZjY0LmxvYWQAAwtpMzIubG9hZDhfcwAEC2kzMi5sb2FkOF91AAUMaTMyLmxvYWQxNl9zAAYMaTMyLmxvYWQxNl91AAcLaTY0LmxvYWQ4X3MACAtpNjQubG9hZDhfdQAJDGk2NC5sb2FkMTZfcwAKDGk2NC5sb2FkMTZfdQALDGk2NC5sb2FkMzJfcwAMDGk2NC5sb2FkMzJfdQANCWkzMi5zdG9yZQAOCWk2NC5zdG9yZQAPCWYzMi5zdG9yZQAQCWY2NC5zdG9yZQARCmkzMi5zdG9yZTgAEgtpMzIuc3RvcmUxNgATCmk2NC5zdG9yZTgAFAtpNjQuc3RvcmUxNgAVC2k2NC5zdG9yZTMyABYKywEXBwAgACgCAAsHACAAKQMACwcAIAAqAgALBwAgACsDAAsHACAALAAACwcAIAAtAAALBwAgAC4BAAsHACAALwEACwcAIAAwAAALBwAgADEAAAsHACAAMgEACwcAIAAzAQALBwAgADQCAAsHACAANQIACwkAIAAgATYCAAsJACAAIAE3AwALCQAgACABOAIACwkAIAAgATkDAAsJACAAIAE6AAALCQAgACABOwEACwkAIAAgATwAAAsJACAAIAE9AQALCQAgACABPgIACwsdAgBBAAsIYWJjZGVmZ2gAQfj/AwsIYWJjZGVmZ2g=", + "AGFzbQEAAAAFAwEAAQsGAQEDZm9v", + "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAAAwkNAQVwA9IAC9BwC9IBCwoHAgIACwIACw==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHEgIEZmlsbAAAB2xvYWQ4X3UAAQoVAgsAIAAgASAC/AsACwcAIAAtAAAL", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxICBGNvcHkAAAdsb2FkOF91AAEKFgIMACAAIAEgAvwKAAALBwAgAC0AAAsLCgEAQQALBKq7zN0=", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHEgIEaW5pdAAAB2xvYWQ4X3UAAQwBAQoWAgwAIAAgASAC/AgAAAsHACAALQAACwsHAQEEqrvM3Q==", + "AGFzbQEAAAABCAJgAABgAX8AAwUEAAEAAQUDAQABBzsEDGRyb3BfcGFzc2l2ZQAADGluaXRfcGFzc2l2ZQABC2Ryb3BfYWN0aXZlAAILaW5pdF9hY3RpdmUAAwwBAgonBAUA/AkACwwAQQBBACAA/AgAAAsFAPwJAQsMAEEAQQAgAPwIAQALCwoCAQF4AEEACwF4", + "AGFzbQEAAAABBAFgAAADAgEADAFBCgcBBQD8CUALC4MBQQEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA=", + "AGFzbQEAAAABBAFgAAADAgEADAEBCgcBBQD8CQALCwoBAQdnb29kYnll", + "AGFzbQEAAAABEANgAAF/YAN/f38AYAF/AX8DBQQAAAECBAQBcAADBw8CBGluaXQAAgRjYWxsAAMJCAEBAAQAAQABCiAEBABBAAsEAEEBCwwAIAAgASAC/AwAAAsHACAAEQAACw==", + "AGFzbQEAAAABCAJgAABgAX8AAwYFAAABAAEEBAFwAAEHOwQMZHJvcF9wYXNzaXZlAAEMaW5pdF9wYXNzaXZlAAILZHJvcF9hY3RpdmUAAwtpbml0X2FjdGl2ZQAECQsCAQABAABBAAsBAAoqBQIACwUA/A0ACwwAQQBBACAA/AwAAAsFAPwNAQsMAEEAQQAgAPwMAQAL", + "AGFzbQEAAAABBAFgAAADAgEACcQBQQEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAoHAQUA/A1ACw==", + "AGFzbQEAAAABBAFgAAADAgEACQUBAQABAAoHAQUA/A0ACw==", + "AGFzbQEAAAABEANgAAF/YAN/f38AYAF/AX8DBgUAAAABAgQEAXAACgcPAgRjb3B5AAMEY2FsbAAECQkBAEEACwMAAQIKJQUEAEEACwQAQQELBABBAgsMACAAIAEgAvwOAAALBwAgABEAAAs=", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABCg0CAwABCwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDUECQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBGUEPQQL8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDUEZQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBFEEWQQT8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBGUEBQQP8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBCkEMQQf8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABChYCDABBDEEKQQf8CgAACwcAIAAtAAALCxQCAEECCwQDAQQBAEEMCwUHBQIDBg==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsaAQBBAAsUAAECAwQFBgcICQoLDA0ODxAREhM=", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsbAQBBAAsVAAECAwQFBgcICQoLDA0ODxAREhMU", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBB7P8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwsdAQBB6/8DCxUAAQIDBAUGBwgJCgsMDQ4PEBESExQ=", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBBzv8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBB4v8DCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw==", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQMBAAEHFwMDbWVtAgADcnVuAAAHbG9hZDhfdQABChYCDAAgACABIAL8CgAACwcAIAAtAAALCxwBAEHs/wMLFAABAgMEBQYHCAkKCwwNDg8QERIT", + "AGFzbQEAAAABDAJgA39/fwBgAX8BfwMDAgABBQQBAQEBBxcDA21lbQIAA3J1bgAAB2xvYWQ4X3UAAQoWAgwAIAAgASAC/AoAAAsHACAALQAACwscAQBBgOADCxQAAQIDBAUGBwgJCgsMDQ4PEBESEw==", + "AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpAAhYAQQpB1QBBCvwLAEEJQQpBBfwKAAALJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCw==", + "AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpAAhYAQQpB1QBBCvwLAEEQQQ9BBfwKAAALJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCw==", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoTAREAQYD+A0GAgAJBgQL8CgAACw==", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYB+QYCAAUGBAvwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoTAREAQYCAAkGA/gNBgQL8CgAACw==", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCAAUGAfkGBAvwKAAAL", + "AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQpUAioAQQBB1QBBgIAC/AsAQYCAAkGqAUGAgAL8CwBBgKACQYDgAUEA/AoAAAsnAANAIAAgAUYEQEF/DwsgAC0AACACRgRAIABBAWohAAwBCwsgAA8L", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCABEGA4AFBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCACEGA4AFBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCgAkGAgARBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCgAkGAgAhBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCABEGAgARBAPwKAAAL", + "AGFzbQEAAAABBAFgAAADAgEABQQBAQEBBwgBBHRlc3QAAAoSARAAQYCACEGAgAhBAPwKAAAL", + "AGFzbQEAAAABCwJgAABgA39/fwF/AwMCAAEFBAEBAQEHFQIEdGVzdAAACmNoZWNrUmFuZ2UAAQq3FQKMFQBB54oBQQFBwAr8CwBB6bACQQJBnwj8CwBB0bgDQQNB3Af8CwBByqgCQQRBwgL8CwBBqT5BBUHKD/wLAEG6sQFBBkHcF/wLAEHygwFBB0HEEvwLAEHj0wJBCEHDBvwLAEH8AEEJQfEK/AsAQdQQQQpBxhX8CwBBm8YAQQtBmhj8CwBB55sDQQxB5QX8CwBB9h5BDUGHFvwLAEGzhANBDkGACvwLAEHJiQNBD0G6C/wLAEGNoAFBEEHWGPwLAEGx9AJBEUGgBPwLAEGj4QBBEkHtFPwLAEGlwgFBE0HbFPwLAEGF4gJBFEGiDPwLAEHY0AJBFUGbDfwLAEHeiAJBFkGGBfwLAEGr+wJBF0HCDvwLAEHNoQNBGEHhFPwLAEGb7QFBGUHVB/wLAEHUyABBGkGPDvwLAEGOiANBG0HnA/wLAEGh6gNBHEGSBPwLAEHcmwJBHUGvB/wLAEHwNEEeQf0C/AsAQb6QA0EfQZEY/AsAQcGEA0EgQZIF/AsAQfzbAkEhQaYN/AsAQb6EAkEiQcQI/AsAQf6MA0EjQYIL/AsAQerzAkEkQZwR/AsAQeumA0ElQdoS/AsAQY+vA0EmQfoB/AsAQdywAUEnQbEQ/AsAQeyFAUEoQcAZ/AsAQbuoA0EpQeMZ/AsAQbK0AkEqQewV/AsAQbyaAkErQZYQ/AsAQeyTAkEsQcsV/AsAQdv/AUEtQbgC/AsAQYLyA0EuQcAB/AsAQf7xAUEvQdQE/AsAQfuBAUEwQfUD/AsAQaq9A0ExQa4F/AsAQfuLAkEyQYED/AsAQdHbA0EzQYcH/AsAQYXgA0E0QdYS/AsAQfzuAkE1QaEL/AsAQfXKAUE2QdoY/AsAQb4rQTdB1xD8CwBBiZkCQThBhwT8CwBB3N4CQTlB0Bn8CwBBqO0CQTpBjg38CwBBj+wCQTtB4Bj8CwBBsa8BQTxBoQv8CwBB8ckDQT1BlwX8CwBBhfwBQT5Bhw38CwBB9xdBP0HRBfwLAEHpiQJBwABB1AD8CwBBuoQCQcEAQe0P/AsAQcqfAkHCAEEd/AsAQcuVAUHDAEHaF/wLAEHI4gBBxABBkwj8CwBB5I4BQcUAQfwZ/AsAQZ8kQcYAQcMI/AsAQZ7+AEHHAEHND/wLAEGcjgFByABB0xH8CwBB5IoDQckAQfUY/AsAQZTWAEHKAEGwD/wLAEHa/ABBywBBrwv8CwBB3uICQcwAQZkJ/AsAQfmmA0HNAEGgDPwLAEG7ggJBzgBB6gz8CwBB5NwDQc8AQdQZ/AsAQZGUA0HQAEHfAfwLAEGJIkHRAEH7EPwLAEGqwQNB0gBBqgr8CwBBrLMDQdMAQdgU/AsAQZu8AUHUAEGVCPwLAEGv0QJB1QBBmRj8CwBBs/wBQdYAQewV/AsAQeMdQdcAQdoP/AsAQcisA0HYAEEA/AsAQZWGA0HZAEGVEPwLAEG7nwFB2gBB0Bb8CwBBoogCQdsAQcAB/AsAQbrJAEHcAEGTEfwLAEH94ABB3QBBGPwLAEGL7gBB3gBBwQT8CwBBmtgCQd8AQakQ/AsAQf+eAkHgAEHsGvwLAEH4tQFB4QBBzRX8CwBB+DFB4gBBvgb8CwBBm4QCQeMAQZIP/AsAQbWrAUHkAEG+FfwLAEHOzgNB6KcDQbIQ/AoAAEGy7ANBuLICQeYB/AoAAEH5lANBzbgBQfwR/AoAAEG0NEG8uwFB/wT8CgAAQc42QfeEAkHJCPwKAABBy5cBQezQAEH9GPwKAABBrNUBQYapA0HkAPwKAABB1dQBQaLVAkG1DfwKAABB8NgDQbXDAEH3APwKAABBuy5BhBJBkgX8CgAAQbMlQa+TA0HdEfwKAABByeIAQf2VAUHBBvwKAABBztwAQanrAkHkGfwKAABB8NgAQdTfAkHpEfwKAABBiosCQak0QYwU/AoAAEHIJkGaDUGwCvwKAABBvO0DQdU7QYYN/AoAAEGY3AJBqI8BQSH8CgAAQY7XAkHMrgFBkwv8CgAAQa3sAkGbhQNBmgv8CgAAQcTxA0GzxABBwgb8CgAAQc2FAkGjnQFB9Rn8CgAAQf+8AkGtqANBgRn8CgAAQdTJAUH2zgNBlBP8CgAAQd6ZAUGyvANB2gL8CgAAQez7AEHKmAJB/hL8CgAAQbDcAEH2lQJBrAL8CgAAQaPQA0GF7QBB0Rj8CgAAQfuLAkGy2QNBgQr8CgAAQYTGAEH03wBBrwf8CgAAQYsWQbnRAEHfDvwKAABButECQYbXAkHiBfwKAABBvuwDQYWUAUH6APwKAABB7LsBQdndAkHbDfwKAABB0LABQaPzAEG+BfwKAABBlNgAQdPPAUGmDvwKAABBtLQBQfefAUGoCPwKAABBoL8DQfKrA0HHFPwKAABBlMcBQYEIQakY/AoAAEG0gwNBvNkCQc8H/AoAAEH43AFB+sUCQaAS/AoAAEHp3gNB5gFBuBb8CgAAQdCvAUGamgNBlRH8CgAAQem8AkHqygBBpg/8CgAAQcziAUH+ogFBihH8CgAAQaWeA0Gz1wJBjQj8CgAAQYTHAUHTlgJB8gz8CgAAQZTJA0H75QJBwg/8CgAAQZmrAkGQLUGjD/wKAABB194BQcSwA0HAEvwKAABBm+kDQbyNAUHMCvwKAABB5YcDQaXsAEH+AvwKAABBiIQBQfWbAkHsDvwKAABB4vcCQd7YAEH3FfwKAABB4N4BQaq7AkHDAvwKAABBspUCQdDZAUGGDfwKAABB+usDQdSgA0G9CvwKAABBte4AQejpAkGEBfwKAABB5uIBQYKVAUHwA/wKAABBmN8CQdnzAkHgFfwKAABBh7UCQfXcAkHGCvwKAABB8NAAQdrkAUHDC/wKAABBv+4CQeLoAkG7C/wKAABBqSZBxOABQecO/AoAAEH8qAJBpb8DQdcN/AoAAEHOzgFB19QBQecI/AoAAEHTywNB0cABQacI/AoAAEGs3wNBhq8CQf4F/AoAAEGA2QJB7BFB8Av8CgAAQeT/AUGF8QJBxhf8CgAAQYzXAEGMpgFB8wf8CgAAQfE7Qfz2AUHaF/wKAABB/IwBQbvlAEH4GfwKAABB2r8DQeG0A0G0AvwKAABB48ABQa+DAUGDCfwKAABBvJsBQYPPAEHSBfwKAABB6RZBry5BwhL8CgAAQf/7AUGvhwNB7hb8CgAAQZb2AEGThwFBrxT8CgAAQYfkAkGf3gFB/Q/8CgAAQe2uA0GRmgJBpBT8CgAAQa3eAUGNpwNBkAn8CgAAQc/2AkGJoQNBwRj8CgAAQbbvAUHj4AJB2RT8CgAAQcEnQcchQTT8CgAAQaQ0QYO9AUG5A/wKAABB2IECQe3TAUH1GvwKAABBkv4BQezPA0HhFfwKAABBuYwCQYLGAEHmEvwKAABB5YsBQYqqA0G1GvwKAABBnbEBQffYAkGIAfwKAABB0c0DQaU3QZUI/AoAAEHBzwJB9K0DQdUS/AoAAEGV3QJBqp0BQe0G/AoAAEHKnwJB7MQBQfca/AoAAEGu5QJBkPkBQdYG/AoAAEGsvQFB+vgBQeEK/AoAAEHyhwJBtAVBugz8CgAAQcrZA0GZkQFBqxf8CgAAQcKJA0G3wgJB/gr8CgAACycAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADws=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo5AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsPAEGA/gNB1QBBgAL8CwAL", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo5AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsPAEGA/gNB1QBBgQL8CwAL", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAfkHVAEGBAvwLAAs=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo2AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsMAEESQdUAQQD8CwAL", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAgARB1QBBAPwLAAs=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEGAgAhB1QBBAPwLAAs=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQo4AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsOAEEBQaoBQf7/A/wLAAs=", + "AGFzbQEAAAABCwJgA39/fwF/YAAAAwMCAAEFBAEBAQEHFQIKY2hlY2tSYW5nZQAABHRlc3QAAQpAAicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsWAEESQdUAQQr8CwBBFUGqAUEE/AsACw==", + "AGFzbQEAAAABDgJgA39/fwF/YAN/f38AAwMCAAEFBAEBAQEHFAIKY2hlY2tSYW5nZQAAA3J1bgABCjUCJwADQCAAIAFGBEBBfw8LIAAtAAAgAkYEQCAAQQFqIQAMAQsLIAAPCwsAIAAgASAC/AsACw==", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABCg0CAwABCwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY=", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEChYCDABBB0EAQQT8CAEACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY=", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEChYCDABBD0EBQQP8CAMACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY=", + "AGFzbQEAAAABCQJgAABgAX8BfwMDAgABBQQBAQEBBxwDB21lbW9yeTACAAR0ZXN0AAAHbG9hZDhfdQABDAEEClgCTgBBB0EAQQT8CAEA/AkBQQ9BAUED/AgDAPwJA0EUQQ9BBfwKAABBFUEdQQH8CgAAQRhBCkEB/AoAAEENQQtBBPwKAABBE0EUQQX8CgAACwcAIAAtAAALCyEEAEECCwQDAQQBAQQCBwEIAEEMCwUHBQIDBgEFBQkCBwY=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCgoBCAD8CQD8CQALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChIBEAD8CQBB0glBAUEB/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAUEB/AgAAAsLBwEAQQALATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChgBFgBBAUEAQQH8CAAAQQFBAEEB/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAEEF/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAkED/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBB/v8DQQFBA/wIAAALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBBEEA/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBCg8BDQBB0glBAUEA/AgAAAsLBAEBATc=", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgYAEQQBBAPwIAAALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgIAEQQBBAPwIAAALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgIAEQQFBAPwIAAALCwQBAQE3", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEHCAEEdGVzdAAADAEBChABDgBBgYAEQQRBAPwIAAALCwQBAQE3", + "AGFzbQEAAAABDQJgA39/fwF/YAJ/fwADAwIAAQUEAQEBAQcUAgpjaGVja1JhbmdlAAADcnVuAAEMAQEKNgInAANAIAAgAUYEQEF/DwsgAC0AACACRgRAIABBAWohAAwBCwsgAA8LDAAgAEEAIAH8CAAACwsTAQEQQkJCQkJCQkJCQkJCQkJCQg==", + "AGFzbQEAAAABDQJgA39/fwF/YAJ/fwADAwIAAQUDAQABBxQCCmNoZWNrUmFuZ2UAAANydW4AAQwBAQo2AicAA0AgACABRgRAQX8PCyAALQAAIAJGBEAgAEEBaiEADAELCyAADwsMACAAQQAgAfwIAAALCxMBARBCQkJCQkJCQkJCQkJCQkJC", + "AGFzbQEAAAABBAFgAAADAgEABQMBAAEMAUEKDgEMAEEAQQBBAPwIQAALC4MBQQEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQA=", + "AGFzbQEAAAAFAwEAAQuzARwAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMAQQALAABBAQsEYWJjZABBAAsAAEEACwNhYmMBAAEEYWJjZABBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwBBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwBBAAsAAEEBCwRhYmNkAEEACwAAQQALA2FiYwEAAQRhYmNk", + "AGFzbQEAAAAFAwEAAQsHAQBBAAsBYQ==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwcBAEEACwFh", + "AGFzbQEAAAAFAwEAAQsiBQBBAAsBYQBBAwsBYgBB5AALA2NkZQBBBQsBeABBAwsBYw==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCycGAEEACwFhAEEBCwFiAEECCwNjZGUAQQMLAWYAQQILAWcAQQELAWg=", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQABCwcBACMACwFh", + "AGFzbQEAAAACKwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAAELBwEAIwALAWE=", + "AGFzbQEAAAAFAwEAAQsPAgBBAAsBYQBB//8DCwFi", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCw8CAEEACwFhAEH//wMLAWI=", + "AGFzbQEAAAAFAwEAAgsJAQBB//8HCwFh", + "AGFzbQEAAAAFAwEAAAsGAQBBAAsA", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwYBAEEACwA=", + "AGFzbQEAAAAFBAEBAAALBgEAQQALAA==", + "AGFzbQEAAAAFAwEAAQsIAQBBgIAECwA=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwcBAEEACwFh", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAwsHAQBBAAsBYQ==", + "AGFzbQEAAAACKwIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAAALBwEAIwALAWE=", + "AGFzbQEAAAACLAIIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAhzcGVjdGVzdAZtZW1vcnkCAQADCwcBACMACwFh", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgAACwcBAEEBCwFh", + "AGFzbQEAAAACFQEIc3BlY3Rlc3QGbWVtb3J5AgEAAwsHAQBBAQsBYQ==", + "AGFzbQEAAAAFAwEAAAsHAQBBAAsBYQ==", + "AGFzbQEAAAAFBAEBAAALBwEAQQALAWE=", + "AGFzbQEAAAAFBAEBAAELBwEAQQALAWE=", + "AGFzbQEAAAAFAwEAAAsGAQBBAQsA", + "AGFzbQEAAAAFBAEBAAELBgEAQQELAA==", + "AGFzbQEAAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAUDAQAACwcBACMACwFh", + "AGFzbQEAAAAFBAEBAQILCQEAQYCABAsBYQ==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwkBAEGAgAQLAWE=", + "AGFzbQEAAAAFAwEAAgsJAQBBgIAICwFh", + "AGFzbQEAAAAFBAEBAgMLCQEAQYCACAsBYQ==", + "AGFzbQEAAAAFAwEAAQsHAQBBfwsBYQ==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwcBAEF/CwFh", + "AGFzbQEAAAAFAwEAAgsIAQBBnH8LAWE=", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QGbWVtb3J5AgABCwgBAEGcfwsBYQ==", +]; +export const script = "[[\"file\",\"address.wast\"],[\"module\",3,0],[\"invoke\",104,0,\"8u_good1\",[\"i32:0\"]],[\"invoke\",105,0,\"8u_good2\",[\"i32:0\"]],[\"invoke\",106,0,\"8u_good3\",[\"i32:0\"]],[\"invoke\",107,0,\"8u_good4\",[\"i32:0\"]],[\"invoke\",108,0,\"8u_good5\",[\"i32:0\"]],[\"invoke\",110,0,\"8s_good1\",[\"i32:0\"]],[\"invoke\",111,0,\"8s_good2\",[\"i32:0\"]],[\"invoke\",112,0,\"8s_good3\",[\"i32:0\"]],[\"invoke\",113,0,\"8s_good4\",[\"i32:0\"]],[\"invoke\",114,0,\"8s_good5\",[\"i32:0\"]],[\"invoke\",116,0,\"16u_good1\",[\"i32:0\"]],[\"invoke\",117,0,\"16u_good2\",[\"i32:0\"]],[\"invoke\",118,0,\"16u_good3\",[\"i32:0\"]],[\"invoke\",119,0,\"16u_good4\",[\"i32:0\"]],[\"invoke\",120,0,\"16u_good5\",[\"i32:0\"]],[\"invoke\",122,0,\"16s_good1\",[\"i32:0\"]],[\"invoke\",123,0,\"16s_good2\",[\"i32:0\"]],[\"invoke\",124,0,\"16s_good3\",[\"i32:0\"]],[\"invoke\",125,0,\"16s_good4\",[\"i32:0\"]],[\"invoke\",126,0,\"16s_good5\",[\"i32:0\"]],[\"invoke\",128,0,\"32_good1\",[\"i32:0\"]],[\"invoke\",129,0,\"32_good2\",[\"i32:0\"]],[\"invoke\",130,0,\"32_good3\",[\"i32:0\"]],[\"invoke\",131,0,\"32_good4\",[\"i32:0\"]],[\"invoke\",132,0,\"32_good5\",[\"i32:0\"]],[\"invoke\",134,0,\"8u_good1\",[\"i32:65507\"]],[\"invoke\",135,0,\"8u_good2\",[\"i32:65507\"]],[\"invoke\",136,0,\"8u_good3\",[\"i32:65507\"]],[\"invoke\",137,0,\"8u_good4\",[\"i32:65507\"]],[\"invoke\",138,0,\"8u_good5\",[\"i32:65507\"]],[\"invoke\",140,0,\"8s_good1\",[\"i32:65507\"]],[\"invoke\",141,0,\"8s_good2\",[\"i32:65507\"]],[\"invoke\",142,0,\"8s_good3\",[\"i32:65507\"]],[\"invoke\",143,0,\"8s_good4\",[\"i32:65507\"]],[\"invoke\",144,0,\"8s_good5\",[\"i32:65507\"]],[\"invoke\",146,0,\"16u_good1\",[\"i32:65507\"]],[\"invoke\",147,0,\"16u_good2\",[\"i32:65507\"]],[\"invoke\",148,0,\"16u_good3\",[\"i32:65507\"]],[\"invoke\",149,0,\"16u_good4\",[\"i32:65507\"]],[\"invoke\",150,0,\"16u_good5\",[\"i32:65507\"]],[\"invoke\",152,0,\"16s_good1\",[\"i32:65507\"]],[\"invoke\",153,0,\"16s_good2\",[\"i32:65507\"]],[\"invoke\",154,0,\"16s_good3\",[\"i32:65507\"]],[\"invoke\",155,0,\"16s_good4\",[\"i32:65507\"]],[\"invoke\",156,0,\"16s_good5\",[\"i32:65507\"]],[\"invoke\",158,0,\"32_good1\",[\"i32:65507\"]],[\"invoke\",159,0,\"32_good2\",[\"i32:65507\"]],[\"invoke\",160,0,\"32_good3\",[\"i32:65507\"]],[\"invoke\",161,0,\"32_good4\",[\"i32:65507\"]],[\"invoke\",162,0,\"32_good5\",[\"i32:65507\"]],[\"invoke\",164,0,\"8u_good1\",[\"i32:65508\"]],[\"invoke\",165,0,\"8u_good2\",[\"i32:65508\"]],[\"invoke\",166,0,\"8u_good3\",[\"i32:65508\"]],[\"invoke\",167,0,\"8u_good4\",[\"i32:65508\"]],[\"invoke\",168,0,\"8u_good5\",[\"i32:65508\"]],[\"invoke\",170,0,\"8s_good1\",[\"i32:65508\"]],[\"invoke\",171,0,\"8s_good2\",[\"i32:65508\"]],[\"invoke\",172,0,\"8s_good3\",[\"i32:65508\"]],[\"invoke\",173,0,\"8s_good4\",[\"i32:65508\"]],[\"invoke\",174,0,\"8s_good5\",[\"i32:65508\"]],[\"invoke\",176,0,\"16u_good1\",[\"i32:65508\"]],[\"invoke\",177,0,\"16u_good2\",[\"i32:65508\"]],[\"invoke\",178,0,\"16u_good3\",[\"i32:65508\"]],[\"invoke\",179,0,\"16u_good4\",[\"i32:65508\"]],[\"invoke\",180,0,\"16u_good5\",[\"i32:65508\"]],[\"invoke\",182,0,\"16s_good1\",[\"i32:65508\"]],[\"invoke\",183,0,\"16s_good2\",[\"i32:65508\"]],[\"invoke\",184,0,\"16s_good3\",[\"i32:65508\"]],[\"invoke\",185,0,\"16s_good4\",[\"i32:65508\"]],[\"invoke\",186,0,\"16s_good5\",[\"i32:65508\"]],[\"invoke\",188,0,\"32_good1\",[\"i32:65508\"]],[\"invoke\",189,0,\"32_good2\",[\"i32:65508\"]],[\"invoke\",190,0,\"32_good3\",[\"i32:65508\"]],[\"invoke\",191,0,\"32_good4\",[\"i32:65508\"]],[\"invoke\",192,0,\"32_good5\",[\"i32:65508\"]],[\"invoke\",194,0,\"8u_good3\",[\"i32:4294967295\"]],[\"invoke\",195,0,\"8s_good3\",[\"i32:4294967295\"]],[\"invoke\",196,0,\"16u_good3\",[\"i32:4294967295\"]],[\"invoke\",197,0,\"16s_good3\",[\"i32:4294967295\"]],[\"invoke\",198,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",199,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",201,0,\"8u_bad\",[\"i32:0\"]],[\"invoke\",202,0,\"8s_bad\",[\"i32:0\"]],[\"invoke\",203,0,\"16u_bad\",[\"i32:0\"]],[\"invoke\",204,0,\"16s_bad\",[\"i32:0\"]],[\"invoke\",205,0,\"32_bad\",[\"i32:0\"]],[\"invoke\",207,0,\"8u_bad\",[\"i32:1\"]],[\"invoke\",208,0,\"8s_bad\",[\"i32:1\"]],[\"invoke\",209,0,\"16u_bad\",[\"i32:1\"]],[\"invoke\",210,0,\"16s_bad\",[\"i32:1\"]],[\"invoke\",211,0,\"32_bad\",[\"i32:1\"]],[\"module\",223,1],[\"invoke\",362,0,\"8u_good1\",[\"i32:0\"]],[\"invoke\",363,0,\"8u_good2\",[\"i32:0\"]],[\"invoke\",364,0,\"8u_good3\",[\"i32:0\"]],[\"invoke\",365,0,\"8u_good4\",[\"i32:0\"]],[\"invoke\",366,0,\"8u_good5\",[\"i32:0\"]],[\"invoke\",368,0,\"8s_good1\",[\"i32:0\"]],[\"invoke\",369,0,\"8s_good2\",[\"i32:0\"]],[\"invoke\",370,0,\"8s_good3\",[\"i32:0\"]],[\"invoke\",371,0,\"8s_good4\",[\"i32:0\"]],[\"invoke\",372,0,\"8s_good5\",[\"i32:0\"]],[\"invoke\",374,0,\"16u_good1\",[\"i32:0\"]],[\"invoke\",375,0,\"16u_good2\",[\"i32:0\"]],[\"invoke\",376,0,\"16u_good3\",[\"i32:0\"]],[\"invoke\",377,0,\"16u_good4\",[\"i32:0\"]],[\"invoke\",378,0,\"16u_good5\",[\"i32:0\"]],[\"invoke\",380,0,\"16s_good1\",[\"i32:0\"]],[\"invoke\",381,0,\"16s_good2\",[\"i32:0\"]],[\"invoke\",382,0,\"16s_good3\",[\"i32:0\"]],[\"invoke\",383,0,\"16s_good4\",[\"i32:0\"]],[\"invoke\",384,0,\"16s_good5\",[\"i32:0\"]],[\"invoke\",386,0,\"32u_good1\",[\"i32:0\"]],[\"invoke\",387,0,\"32u_good2\",[\"i32:0\"]],[\"invoke\",388,0,\"32u_good3\",[\"i32:0\"]],[\"invoke\",389,0,\"32u_good4\",[\"i32:0\"]],[\"invoke\",390,0,\"32u_good5\",[\"i32:0\"]],[\"invoke\",392,0,\"32s_good1\",[\"i32:0\"]],[\"invoke\",393,0,\"32s_good2\",[\"i32:0\"]],[\"invoke\",394,0,\"32s_good3\",[\"i32:0\"]],[\"invoke\",395,0,\"32s_good4\",[\"i32:0\"]],[\"invoke\",396,0,\"32s_good5\",[\"i32:0\"]],[\"invoke\",398,0,\"64_good1\",[\"i32:0\"]],[\"invoke\",399,0,\"64_good2\",[\"i32:0\"]],[\"invoke\",400,0,\"64_good3\",[\"i32:0\"]],[\"invoke\",401,0,\"64_good4\",[\"i32:0\"]],[\"invoke\",402,0,\"64_good5\",[\"i32:0\"]],[\"invoke\",404,0,\"8u_good1\",[\"i32:65503\"]],[\"invoke\",405,0,\"8u_good2\",[\"i32:65503\"]],[\"invoke\",406,0,\"8u_good3\",[\"i32:65503\"]],[\"invoke\",407,0,\"8u_good4\",[\"i32:65503\"]],[\"invoke\",408,0,\"8u_good5\",[\"i32:65503\"]],[\"invoke\",410,0,\"8s_good1\",[\"i32:65503\"]],[\"invoke\",411,0,\"8s_good2\",[\"i32:65503\"]],[\"invoke\",412,0,\"8s_good3\",[\"i32:65503\"]],[\"invoke\",413,0,\"8s_good4\",[\"i32:65503\"]],[\"invoke\",414,0,\"8s_good5\",[\"i32:65503\"]],[\"invoke\",416,0,\"16u_good1\",[\"i32:65503\"]],[\"invoke\",417,0,\"16u_good2\",[\"i32:65503\"]],[\"invoke\",418,0,\"16u_good3\",[\"i32:65503\"]],[\"invoke\",419,0,\"16u_good4\",[\"i32:65503\"]],[\"invoke\",420,0,\"16u_good5\",[\"i32:65503\"]],[\"invoke\",422,0,\"16s_good1\",[\"i32:65503\"]],[\"invoke\",423,0,\"16s_good2\",[\"i32:65503\"]],[\"invoke\",424,0,\"16s_good3\",[\"i32:65503\"]],[\"invoke\",425,0,\"16s_good4\",[\"i32:65503\"]],[\"invoke\",426,0,\"16s_good5\",[\"i32:65503\"]],[\"invoke\",428,0,\"32u_good1\",[\"i32:65503\"]],[\"invoke\",429,0,\"32u_good2\",[\"i32:65503\"]],[\"invoke\",430,0,\"32u_good3\",[\"i32:65503\"]],[\"invoke\",431,0,\"32u_good4\",[\"i32:65503\"]],[\"invoke\",432,0,\"32u_good5\",[\"i32:65503\"]],[\"invoke\",434,0,\"32s_good1\",[\"i32:65503\"]],[\"invoke\",435,0,\"32s_good2\",[\"i32:65503\"]],[\"invoke\",436,0,\"32s_good3\",[\"i32:65503\"]],[\"invoke\",437,0,\"32s_good4\",[\"i32:65503\"]],[\"invoke\",438,0,\"32s_good5\",[\"i32:65503\"]],[\"invoke\",440,0,\"64_good1\",[\"i32:65503\"]],[\"invoke\",441,0,\"64_good2\",[\"i32:65503\"]],[\"invoke\",442,0,\"64_good3\",[\"i32:65503\"]],[\"invoke\",443,0,\"64_good4\",[\"i32:65503\"]],[\"invoke\",444,0,\"64_good5\",[\"i32:65503\"]],[\"invoke\",446,0,\"8u_good1\",[\"i32:65504\"]],[\"invoke\",447,0,\"8u_good2\",[\"i32:65504\"]],[\"invoke\",448,0,\"8u_good3\",[\"i32:65504\"]],[\"invoke\",449,0,\"8u_good4\",[\"i32:65504\"]],[\"invoke\",450,0,\"8u_good5\",[\"i32:65504\"]],[\"invoke\",452,0,\"8s_good1\",[\"i32:65504\"]],[\"invoke\",453,0,\"8s_good2\",[\"i32:65504\"]],[\"invoke\",454,0,\"8s_good3\",[\"i32:65504\"]],[\"invoke\",455,0,\"8s_good4\",[\"i32:65504\"]],[\"invoke\",456,0,\"8s_good5\",[\"i32:65504\"]],[\"invoke\",458,0,\"16u_good1\",[\"i32:65504\"]],[\"invoke\",459,0,\"16u_good2\",[\"i32:65504\"]],[\"invoke\",460,0,\"16u_good3\",[\"i32:65504\"]],[\"invoke\",461,0,\"16u_good4\",[\"i32:65504\"]],[\"invoke\",462,0,\"16u_good5\",[\"i32:65504\"]],[\"invoke\",464,0,\"16s_good1\",[\"i32:65504\"]],[\"invoke\",465,0,\"16s_good2\",[\"i32:65504\"]],[\"invoke\",466,0,\"16s_good3\",[\"i32:65504\"]],[\"invoke\",467,0,\"16s_good4\",[\"i32:65504\"]],[\"invoke\",468,0,\"16s_good5\",[\"i32:65504\"]],[\"invoke\",470,0,\"32u_good1\",[\"i32:65504\"]],[\"invoke\",471,0,\"32u_good2\",[\"i32:65504\"]],[\"invoke\",472,0,\"32u_good3\",[\"i32:65504\"]],[\"invoke\",473,0,\"32u_good4\",[\"i32:65504\"]],[\"invoke\",474,0,\"32u_good5\",[\"i32:65504\"]],[\"invoke\",476,0,\"32s_good1\",[\"i32:65504\"]],[\"invoke\",477,0,\"32s_good2\",[\"i32:65504\"]],[\"invoke\",478,0,\"32s_good3\",[\"i32:65504\"]],[\"invoke\",479,0,\"32s_good4\",[\"i32:65504\"]],[\"invoke\",480,0,\"32s_good5\",[\"i32:65504\"]],[\"invoke\",482,0,\"64_good1\",[\"i32:65504\"]],[\"invoke\",483,0,\"64_good2\",[\"i32:65504\"]],[\"invoke\",484,0,\"64_good3\",[\"i32:65504\"]],[\"invoke\",485,0,\"64_good4\",[\"i32:65504\"]],[\"invoke\",486,0,\"64_good5\",[\"i32:65504\"]],[\"invoke\",488,0,\"8u_good3\",[\"i32:4294967295\"]],[\"invoke\",489,0,\"8s_good3\",[\"i32:4294967295\"]],[\"invoke\",490,0,\"16u_good3\",[\"i32:4294967295\"]],[\"invoke\",491,0,\"16s_good3\",[\"i32:4294967295\"]],[\"invoke\",492,0,\"32u_good3\",[\"i32:4294967295\"]],[\"invoke\",493,0,\"32s_good3\",[\"i32:4294967295\"]],[\"invoke\",494,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",496,0,\"8u_bad\",[\"i32:0\"]],[\"invoke\",497,0,\"8s_bad\",[\"i32:0\"]],[\"invoke\",498,0,\"16u_bad\",[\"i32:0\"]],[\"invoke\",499,0,\"16s_bad\",[\"i32:0\"]],[\"invoke\",500,0,\"32u_bad\",[\"i32:0\"]],[\"invoke\",501,0,\"32s_bad\",[\"i32:0\"]],[\"invoke\",502,0,\"64_bad\",[\"i32:0\"]],[\"invoke\",504,0,\"8u_bad\",[\"i32:1\"]],[\"invoke\",505,0,\"8s_bad\",[\"i32:1\"]],[\"invoke\",506,0,\"16u_bad\",[\"i32:1\"]],[\"invoke\",507,0,\"16s_bad\",[\"i32:1\"]],[\"invoke\",508,0,\"32u_bad\",[\"i32:0\"]],[\"invoke\",509,0,\"32s_bad\",[\"i32:0\"]],[\"invoke\",510,0,\"64_bad\",[\"i32:1\"]],[\"module\",514,2],[\"invoke\",538,0,\"32_good1\",[\"i32:0\"]],[\"invoke\",539,0,\"32_good2\",[\"i32:0\"]],[\"invoke\",540,0,\"32_good3\",[\"i32:0\"]],[\"invoke\",541,0,\"32_good4\",[\"i32:0\"]],[\"invoke\",542,0,\"32_good5\",[\"i32:0\"]],[\"invoke\",544,0,\"32_good1\",[\"i32:65524\"]],[\"invoke\",545,0,\"32_good2\",[\"i32:65524\"]],[\"invoke\",546,0,\"32_good3\",[\"i32:65524\"]],[\"invoke\",547,0,\"32_good4\",[\"i32:65524\"]],[\"invoke\",548,0,\"32_good5\",[\"i32:65524\"]],[\"invoke\",550,0,\"32_good1\",[\"i32:65525\"]],[\"invoke\",551,0,\"32_good2\",[\"i32:65525\"]],[\"invoke\",552,0,\"32_good3\",[\"i32:65525\"]],[\"invoke\",553,0,\"32_good4\",[\"i32:65525\"]],[\"invoke\",554,0,\"32_good5\",[\"i32:65525\"]],[\"invoke\",556,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",557,0,\"32_good3\",[\"i32:4294967295\"]],[\"invoke\",559,0,\"32_bad\",[\"i32:0\"]],[\"invoke\",560,0,\"32_bad\",[\"i32:1\"]],[\"module\",564,3],[\"invoke\",588,0,\"64_good1\",[\"i32:0\"]],[\"invoke\",589,0,\"64_good2\",[\"i32:0\"]],[\"invoke\",590,0,\"64_good3\",[\"i32:0\"]],[\"invoke\",591,0,\"64_good4\",[\"i32:0\"]],[\"invoke\",592,0,\"64_good5\",[\"i32:0\"]],[\"invoke\",594,0,\"64_good1\",[\"i32:65510\"]],[\"invoke\",595,0,\"64_good2\",[\"i32:65510\"]],[\"invoke\",596,0,\"64_good3\",[\"i32:65510\"]],[\"invoke\",597,0,\"64_good4\",[\"i32:65510\"]],[\"invoke\",598,0,\"64_good5\",[\"i32:65510\"]],[\"invoke\",600,0,\"64_good1\",[\"i32:65511\"]],[\"invoke\",601,0,\"64_good2\",[\"i32:65511\"]],[\"invoke\",602,0,\"64_good3\",[\"i32:65511\"]],[\"invoke\",603,0,\"64_good4\",[\"i32:65511\"]],[\"invoke\",604,0,\"64_good5\",[\"i32:65511\"]],[\"invoke\",606,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",607,0,\"64_good3\",[\"i32:4294967295\"]],[\"invoke\",609,0,\"64_bad\",[\"i32:0\"]],[\"invoke\",610,0,\"64_bad\",[\"i32:1\"]],[\"file\",\"align.wast\"],[\"module\",3,4],[\"module\",4,5],[\"module\",5,6],[\"module\",6,7],[\"module\",7,8],[\"module\",8,9],[\"module\",9,10],[\"module\",10,11],[\"module\",11,12],[\"module\",12,13],[\"module\",13,14],[\"module\",14,15],[\"module\",15,16],[\"module\",16,17],[\"module\",17,18],[\"module\",18,19],[\"module\",19,20],[\"module\",20,21],[\"module\",21,22],[\"module\",22,23],[\"module\",23,24],[\"module\",24,25],[\"module\",25,26],[\"module\",458,27],[\"invoke\",802,0,\"f32_align_switch\",[\"i32:0\"]],[\"invoke\",803,0,\"f32_align_switch\",[\"i32:1\"]],[\"invoke\",804,0,\"f32_align_switch\",[\"i32:2\"]],[\"invoke\",805,0,\"f32_align_switch\",[\"i32:3\"]],[\"invoke\",807,0,\"f64_align_switch\",[\"i32:0\"]],[\"invoke\",808,0,\"f64_align_switch\",[\"i32:1\"]],[\"invoke\",809,0,\"f64_align_switch\",[\"i32:2\"]],[\"invoke\",810,0,\"f64_align_switch\",[\"i32:3\"]],[\"invoke\",811,0,\"f64_align_switch\",[\"i32:4\"]],[\"invoke\",813,0,\"i32_align_switch\",[\"i32:0\",\"i32:0\"]],[\"invoke\",814,0,\"i32_align_switch\",[\"i32:0\",\"i32:1\"]],[\"invoke\",815,0,\"i32_align_switch\",[\"i32:1\",\"i32:0\"]],[\"invoke\",816,0,\"i32_align_switch\",[\"i32:1\",\"i32:1\"]],[\"invoke\",817,0,\"i32_align_switch\",[\"i32:2\",\"i32:0\"]],[\"invoke\",818,0,\"i32_align_switch\",[\"i32:2\",\"i32:1\"]],[\"invoke\",819,0,\"i32_align_switch\",[\"i32:2\",\"i32:2\"]],[\"invoke\",820,0,\"i32_align_switch\",[\"i32:3\",\"i32:0\"]],[\"invoke\",821,0,\"i32_align_switch\",[\"i32:3\",\"i32:1\"]],[\"invoke\",822,0,\"i32_align_switch\",[\"i32:3\",\"i32:2\"]],[\"invoke\",823,0,\"i32_align_switch\",[\"i32:4\",\"i32:0\"]],[\"invoke\",824,0,\"i32_align_switch\",[\"i32:4\",\"i32:1\"]],[\"invoke\",825,0,\"i32_align_switch\",[\"i32:4\",\"i32:2\"]],[\"invoke\",826,0,\"i32_align_switch\",[\"i32:4\",\"i32:4\"]],[\"invoke\",828,0,\"i64_align_switch\",[\"i32:0\",\"i32:0\"]],[\"invoke\",829,0,\"i64_align_switch\",[\"i32:0\",\"i32:1\"]],[\"invoke\",830,0,\"i64_align_switch\",[\"i32:1\",\"i32:0\"]],[\"invoke\",831,0,\"i64_align_switch\",[\"i32:1\",\"i32:1\"]],[\"invoke\",832,0,\"i64_align_switch\",[\"i32:2\",\"i32:0\"]],[\"invoke\",833,0,\"i64_align_switch\",[\"i32:2\",\"i32:1\"]],[\"invoke\",834,0,\"i64_align_switch\",[\"i32:2\",\"i32:2\"]],[\"invoke\",835,0,\"i64_align_switch\",[\"i32:3\",\"i32:0\"]],[\"invoke\",836,0,\"i64_align_switch\",[\"i32:3\",\"i32:1\"]],[\"invoke\",837,0,\"i64_align_switch\",[\"i32:3\",\"i32:2\"]],[\"invoke\",838,0,\"i64_align_switch\",[\"i32:4\",\"i32:0\"]],[\"invoke\",839,0,\"i64_align_switch\",[\"i32:4\",\"i32:1\"]],[\"invoke\",840,0,\"i64_align_switch\",[\"i32:4\",\"i32:2\"]],[\"invoke\",841,0,\"i64_align_switch\",[\"i32:4\",\"i32:4\"]],[\"invoke\",842,0,\"i64_align_switch\",[\"i32:5\",\"i32:0\"]],[\"invoke\",843,0,\"i64_align_switch\",[\"i32:5\",\"i32:1\"]],[\"invoke\",844,0,\"i64_align_switch\",[\"i32:5\",\"i32:2\"]],[\"invoke\",845,0,\"i64_align_switch\",[\"i32:5\",\"i32:4\"]],[\"invoke\",846,0,\"i64_align_switch\",[\"i32:6\",\"i32:0\"]],[\"invoke\",847,0,\"i64_align_switch\",[\"i32:6\",\"i32:1\"]],[\"invoke\",848,0,\"i64_align_switch\",[\"i32:6\",\"i32:2\"]],[\"invoke\",849,0,\"i64_align_switch\",[\"i32:6\",\"i32:4\"]],[\"invoke\",850,0,\"i64_align_switch\",[\"i32:6\",\"i32:8\"]],[\"module\",854,28],[\"invoke\",864,0,\"store\",[\"i32:65532\",\"i64:18446744073709551615\"]],[\"invoke\",866,0,\"load\",[\"i32:65532\"]],[\"file\",\"endianness.wast\"],[\"module\",1,29],[\"invoke\",133,0,\"i32_load16_s\",[\"i32:4294967295\"]],[\"invoke\",134,0,\"i32_load16_s\",[\"i32:4294963054\"]],[\"invoke\",135,0,\"i32_load16_s\",[\"i32:42\"]],[\"invoke\",136,0,\"i32_load16_s\",[\"i32:12816\"]],[\"invoke\",138,0,\"i32_load16_u\",[\"i32:4294967295\"]],[\"invoke\",139,0,\"i32_load16_u\",[\"i32:4294963054\"]],[\"invoke\",140,0,\"i32_load16_u\",[\"i32:42\"]],[\"invoke\",141,0,\"i32_load16_u\",[\"i32:51966\"]],[\"invoke\",143,0,\"i32_load\",[\"i32:4294967295\"]],[\"invoke\",144,0,\"i32_load\",[\"i32:4252543054\"]],[\"invoke\",145,0,\"i32_load\",[\"i32:42424242\"]],[\"invoke\",146,0,\"i32_load\",[\"i32:2880249322\"]],[\"invoke\",148,0,\"i64_load16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",149,0,\"i64_load16_s\",[\"i64:18446744073709547374\"]],[\"invoke\",150,0,\"i64_load16_s\",[\"i64:42\"]],[\"invoke\",151,0,\"i64_load16_s\",[\"i64:12816\"]],[\"invoke\",153,0,\"i64_load16_u\",[\"i64:18446744073709551615\"]],[\"invoke\",154,0,\"i64_load16_u\",[\"i64:18446744073709547374\"]],[\"invoke\",155,0,\"i64_load16_u\",[\"i64:42\"]],[\"invoke\",156,0,\"i64_load16_u\",[\"i64:51966\"]],[\"invoke\",158,0,\"i64_load32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",159,0,\"i64_load32_s\",[\"i64:18446744073667127374\"]],[\"invoke\",160,0,\"i64_load32_s\",[\"i64:42424242\"]],[\"invoke\",161,0,\"i64_load32_s\",[\"i64:305419896\"]],[\"invoke\",163,0,\"i64_load32_u\",[\"i64:18446744073709551615\"]],[\"invoke\",164,0,\"i64_load32_u\",[\"i64:18446744073667127374\"]],[\"invoke\",165,0,\"i64_load32_u\",[\"i64:42424242\"]],[\"invoke\",166,0,\"i64_load32_u\",[\"i64:2880249322\"]],[\"invoke\",168,0,\"i64_load\",[\"i64:18446744073709551615\"]],[\"invoke\",169,0,\"i64_load\",[\"i64:18446744073667127374\"]],[\"invoke\",170,0,\"i64_load\",[\"i64:2880249322\"]],[\"invoke\",171,0,\"i64_load\",[\"i64:12370766947463011818\"]],[\"invoke\",173,0,\"f32_load\",[\"f32:3212836864\"]],[\"invoke\",174,0,\"f32_load\",[\"f32:1011494326\"]],[\"invoke\",175,0,\"f32_load\",[\"f32:1166316389\"]],[\"invoke\",176,0,\"f32_load\",[\"f32:2139095039\"]],[\"invoke\",178,0,\"f64_load\",[\"f64:13830554455654793216\"]],[\"invoke\",179,0,\"f64_load\",[\"f64:4653144502447687399\"]],[\"invoke\",180,0,\"f64_load\",[\"f64:4691032041816096430\"]],[\"invoke\",181,0,\"f64_load\",[\"f64:9218868437227405311\"]],[\"invoke\",184,0,\"i32_store16\",[\"i32:4294967295\"]],[\"invoke\",185,0,\"i32_store16\",[\"i32:4294963054\"]],[\"invoke\",186,0,\"i32_store16\",[\"i32:42\"]],[\"invoke\",187,0,\"i32_store16\",[\"i32:51966\"]],[\"invoke\",189,0,\"i32_store\",[\"i32:4294967295\"]],[\"invoke\",190,0,\"i32_store\",[\"i32:4294963054\"]],[\"invoke\",191,0,\"i32_store\",[\"i32:42424242\"]],[\"invoke\",192,0,\"i32_store\",[\"i32:3735931646\"]],[\"invoke\",194,0,\"i64_store16\",[\"i64:18446744073709551615\"]],[\"invoke\",195,0,\"i64_store16\",[\"i64:18446744073709547374\"]],[\"invoke\",196,0,\"i64_store16\",[\"i64:42\"]],[\"invoke\",197,0,\"i64_store16\",[\"i64:51966\"]],[\"invoke\",199,0,\"i64_store32\",[\"i64:18446744073709551615\"]],[\"invoke\",200,0,\"i64_store32\",[\"i64:18446744073709547374\"]],[\"invoke\",201,0,\"i64_store32\",[\"i64:42424242\"]],[\"invoke\",202,0,\"i64_store32\",[\"i64:3735931646\"]],[\"invoke\",204,0,\"i64_store\",[\"i64:18446744073709551615\"]],[\"invoke\",205,0,\"i64_store\",[\"i64:18446744073667127374\"]],[\"invoke\",206,0,\"i64_store\",[\"i64:2880249322\"]],[\"invoke\",207,0,\"i64_store\",[\"i64:12370766947463011818\"]],[\"invoke\",209,0,\"f32_store\",[\"f32:3212836864\"]],[\"invoke\",210,0,\"f32_store\",[\"f32:1011494326\"]],[\"invoke\",211,0,\"f32_store\",[\"f32:1166316389\"]],[\"invoke\",212,0,\"f32_store\",[\"f32:2139095039\"]],[\"invoke\",214,0,\"f64_store\",[\"f64:13830554455654793216\"]],[\"invoke\",215,0,\"f64_store\",[\"f64:4653144502447687399\"]],[\"invoke\",216,0,\"f64_store\",[\"f64:4691032041816096430\"]],[\"invoke\",217,0,\"f64_store\",[\"f64:9218868437227405311\"]],[\"file\",\"float_memory.wast\"],[\"module\",5,30],[\"invoke\",15,0,\"i32.load\",[]],[\"invoke\",16,0,\"f32.load\",[]],[\"invoke\",17,0,\"reset\",[]],[\"invoke\",18,0,\"i32.load\",[]],[\"invoke\",19,0,\"f32.load\",[]],[\"invoke\",20,0,\"f32.store\",[]],[\"invoke\",21,0,\"i32.load\",[]],[\"invoke\",22,0,\"f32.load\",[]],[\"invoke\",23,0,\"reset\",[]],[\"invoke\",24,0,\"i32.load\",[]],[\"invoke\",25,0,\"f32.load\",[]],[\"invoke\",26,0,\"i32.store\",[]],[\"invoke\",27,0,\"i32.load\",[]],[\"invoke\",28,0,\"f32.load\",[]],[\"module\",30,31],[\"invoke\",40,0,\"i64.load\",[]],[\"invoke\",41,0,\"f64.load\",[]],[\"invoke\",42,0,\"reset\",[]],[\"invoke\",43,0,\"i64.load\",[]],[\"invoke\",44,0,\"f64.load\",[]],[\"invoke\",45,0,\"f64.store\",[]],[\"invoke\",46,0,\"i64.load\",[]],[\"invoke\",47,0,\"f64.load\",[]],[\"invoke\",48,0,\"reset\",[]],[\"invoke\",49,0,\"i64.load\",[]],[\"invoke\",50,0,\"f64.load\",[]],[\"invoke\",51,0,\"i64.store\",[]],[\"invoke\",52,0,\"i64.load\",[]],[\"invoke\",53,0,\"f64.load\",[]],[\"module\",57,32],[\"invoke\",67,0,\"i32.load\",[]],[\"invoke\",68,0,\"f32.load\",[]],[\"invoke\",69,0,\"reset\",[]],[\"invoke\",70,0,\"i32.load\",[]],[\"invoke\",71,0,\"f32.load\",[]],[\"invoke\",72,0,\"f32.store\",[]],[\"invoke\",73,0,\"i32.load\",[]],[\"invoke\",74,0,\"f32.load\",[]],[\"invoke\",75,0,\"reset\",[]],[\"invoke\",76,0,\"i32.load\",[]],[\"invoke\",77,0,\"f32.load\",[]],[\"invoke\",78,0,\"i32.store\",[]],[\"invoke\",79,0,\"i32.load\",[]],[\"invoke\",80,0,\"f32.load\",[]],[\"module\",82,33],[\"invoke\",92,0,\"i64.load\",[]],[\"invoke\",93,0,\"f64.load\",[]],[\"invoke\",94,0,\"reset\",[]],[\"invoke\",95,0,\"i64.load\",[]],[\"invoke\",96,0,\"f64.load\",[]],[\"invoke\",97,0,\"f64.store\",[]],[\"invoke\",98,0,\"i64.load\",[]],[\"invoke\",99,0,\"f64.load\",[]],[\"invoke\",100,0,\"reset\",[]],[\"invoke\",101,0,\"i64.load\",[]],[\"invoke\",102,0,\"f64.load\",[]],[\"invoke\",103,0,\"i64.store\",[]],[\"invoke\",104,0,\"i64.load\",[]],[\"invoke\",105,0,\"f64.load\",[]],[\"module\",109,34],[\"invoke\",119,0,\"i32.load\",[]],[\"invoke\",120,0,\"f32.load\",[]],[\"invoke\",121,0,\"reset\",[]],[\"invoke\",122,0,\"i32.load\",[]],[\"invoke\",123,0,\"f32.load\",[]],[\"invoke\",124,0,\"f32.store\",[]],[\"invoke\",125,0,\"i32.load\",[]],[\"invoke\",126,0,\"f32.load\",[]],[\"invoke\",127,0,\"reset\",[]],[\"invoke\",128,0,\"i32.load\",[]],[\"invoke\",129,0,\"f32.load\",[]],[\"invoke\",130,0,\"i32.store\",[]],[\"invoke\",131,0,\"i32.load\",[]],[\"invoke\",132,0,\"f32.load\",[]],[\"module\",134,35],[\"invoke\",144,0,\"i64.load\",[]],[\"invoke\",145,0,\"f64.load\",[]],[\"invoke\",146,0,\"reset\",[]],[\"invoke\",147,0,\"i64.load\",[]],[\"invoke\",148,0,\"f64.load\",[]],[\"invoke\",149,0,\"f64.store\",[]],[\"invoke\",150,0,\"i64.load\",[]],[\"invoke\",151,0,\"f64.load\",[]],[\"invoke\",152,0,\"reset\",[]],[\"invoke\",153,0,\"i64.load\",[]],[\"invoke\",154,0,\"f64.load\",[]],[\"invoke\",155,0,\"i64.store\",[]],[\"invoke\",156,0,\"i64.load\",[]],[\"invoke\",157,0,\"f64.load\",[]],[\"file\",\"load.wast\"],[\"module\",3,36],[\"invoke\",161,0,\"as-br-value\",[]],[\"invoke\",163,0,\"as-br_if-cond\",[]],[\"invoke\",164,0,\"as-br_if-value\",[]],[\"invoke\",165,0,\"as-br_if-value-cond\",[]],[\"invoke\",167,0,\"as-br_table-index\",[]],[\"invoke\",168,0,\"as-br_table-value\",[]],[\"invoke\",169,0,\"as-br_table-value-index\",[]],[\"invoke\",171,0,\"as-return-value\",[]],[\"invoke\",173,0,\"as-if-cond\",[]],[\"invoke\",174,0,\"as-if-then\",[]],[\"invoke\",175,0,\"as-if-else\",[]],[\"invoke\",177,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",178,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",179,0,\"as-select-cond\",[]],[\"invoke\",181,0,\"as-call-first\",[]],[\"invoke\",182,0,\"as-call-mid\",[]],[\"invoke\",183,0,\"as-call-last\",[]],[\"invoke\",185,0,\"as-call_indirect-first\",[]],[\"invoke\",186,0,\"as-call_indirect-mid\",[]],[\"invoke\",187,0,\"as-call_indirect-last\",[]],[\"invoke\",188,0,\"as-call_indirect-index\",[]],[\"invoke\",190,0,\"as-local.set-value\",[]],[\"invoke\",191,0,\"as-local.tee-value\",[]],[\"invoke\",192,0,\"as-global.set-value\",[]],[\"invoke\",194,0,\"as-load-address\",[]],[\"invoke\",195,0,\"as-loadN-address\",[]],[\"invoke\",196,0,\"as-store-address\",[]],[\"invoke\",197,0,\"as-store-value\",[]],[\"invoke\",198,0,\"as-storeN-address\",[]],[\"invoke\",199,0,\"as-storeN-value\",[]],[\"invoke\",201,0,\"as-unary-operand\",[]],[\"invoke\",203,0,\"as-binary-left\",[]],[\"invoke\",204,0,\"as-binary-right\",[]],[\"invoke\",206,0,\"as-test-operand\",[]],[\"invoke\",208,0,\"as-compare-left\",[]],[\"invoke\",209,0,\"as-compare-right\",[]],[\"invoke\",211,0,\"as-memory.grow-size\",[]],[\"file\",\"store.wast\"],[\"module\",3,37],[\"invoke\",44,0,\"as-block-value\",[]],[\"invoke\",45,0,\"as-loop-value\",[]],[\"invoke\",47,0,\"as-br-value\",[]],[\"invoke\",48,0,\"as-br_if-value\",[]],[\"invoke\",49,0,\"as-br_if-value-cond\",[]],[\"invoke\",50,0,\"as-br_table-value\",[]],[\"invoke\",52,0,\"as-return-value\",[]],[\"invoke\",54,0,\"as-if-then\",[]],[\"invoke\",55,0,\"as-if-else\",[]],[\"file\",\"memory.wast\"],[\"module\",3,38],[\"module\",4,39],[\"module\",5,40],[\"module\",6,41],[\"module\",7,42],[\"module\",8,43],[\"module\",13,44],[\"invoke\",14,0,\"memsize\",[]],[\"module\",15,44],[\"invoke\",16,0,\"memsize\",[]],[\"module\",17,45],[\"invoke\",18,0,\"memsize\",[]],[\"module\",92,46],[\"invoke\",180,0,\"data\",[]],[\"invoke\",181,0,\"cast\",[]],[\"invoke\",183,0,\"i32_load8_s\",[\"i32:4294967295\"]],[\"invoke\",184,0,\"i32_load8_u\",[\"i32:4294967295\"]],[\"invoke\",185,0,\"i32_load16_s\",[\"i32:4294967295\"]],[\"invoke\",186,0,\"i32_load16_u\",[\"i32:4294967295\"]],[\"invoke\",188,0,\"i32_load8_s\",[\"i32:100\"]],[\"invoke\",189,0,\"i32_load8_u\",[\"i32:200\"]],[\"invoke\",190,0,\"i32_load16_s\",[\"i32:20000\"]],[\"invoke\",191,0,\"i32_load16_u\",[\"i32:40000\"]],[\"invoke\",193,0,\"i32_load8_s\",[\"i32:4275856707\"]],[\"invoke\",194,0,\"i32_load8_s\",[\"i32:878104047\"]],[\"invoke\",195,0,\"i32_load8_u\",[\"i32:4275856707\"]],[\"invoke\",196,0,\"i32_load8_u\",[\"i32:878104047\"]],[\"invoke\",197,0,\"i32_load16_s\",[\"i32:4275856707\"]],[\"invoke\",198,0,\"i32_load16_s\",[\"i32:878104047\"]],[\"invoke\",199,0,\"i32_load16_u\",[\"i32:4275856707\"]],[\"invoke\",200,0,\"i32_load16_u\",[\"i32:878104047\"]],[\"invoke\",202,0,\"i64_load8_s\",[\"i64:18446744073709551615\"]],[\"invoke\",203,0,\"i64_load8_u\",[\"i64:18446744073709551615\"]],[\"invoke\",204,0,\"i64_load16_s\",[\"i64:18446744073709551615\"]],[\"invoke\",205,0,\"i64_load16_u\",[\"i64:18446744073709551615\"]],[\"invoke\",206,0,\"i64_load32_s\",[\"i64:18446744073709551615\"]],[\"invoke\",207,0,\"i64_load32_u\",[\"i64:18446744073709551615\"]],[\"invoke\",209,0,\"i64_load8_s\",[\"i64:100\"]],[\"invoke\",210,0,\"i64_load8_u\",[\"i64:200\"]],[\"invoke\",211,0,\"i64_load16_s\",[\"i64:20000\"]],[\"invoke\",212,0,\"i64_load16_u\",[\"i64:40000\"]],[\"invoke\",213,0,\"i64_load32_s\",[\"i64:20000\"]],[\"invoke\",214,0,\"i64_load32_u\",[\"i64:40000\"]],[\"invoke\",216,0,\"i64_load8_s\",[\"i64:18364758543954109763\"]],[\"invoke\",217,0,\"i64_load8_s\",[\"i64:3771275841602506223\"]],[\"invoke\",218,0,\"i64_load8_u\",[\"i64:18364758543954109763\"]],[\"invoke\",219,0,\"i64_load8_u\",[\"i64:3771275841602506223\"]],[\"invoke\",220,0,\"i64_load16_s\",[\"i64:18364758543954109763\"]],[\"invoke\",221,0,\"i64_load16_s\",[\"i64:3771275841602506223\"]],[\"invoke\",222,0,\"i64_load16_u\",[\"i64:18364758543954109763\"]],[\"invoke\",223,0,\"i64_load16_u\",[\"i64:3771275841602506223\"]],[\"invoke\",224,0,\"i64_load32_s\",[\"i64:18364758543954109763\"]],[\"invoke\",225,0,\"i64_load32_s\",[\"i64:3771275841602506223\"]],[\"invoke\",226,0,\"i64_load32_u\",[\"i64:18364758543954109763\"]],[\"invoke\",227,0,\"i64_load32_u\",[\"i64:3771275841602506223\"]],[\"module\",246,47],[\"invoke\",260,0,\"load\",[\"i32:0\"]],[\"invoke\",261,0,\"load\",[\"i32:10000\"]],[\"invoke\",262,0,\"load\",[\"i32:20000\"]],[\"invoke\",263,0,\"load\",[\"i32:30000\"]],[\"invoke\",264,0,\"load\",[\"i32:40000\"]],[\"invoke\",265,0,\"load\",[\"i32:50000\"]],[\"invoke\",266,0,\"load\",[\"i32:60000\"]],[\"invoke\",267,0,\"load\",[\"i32:65535\"]],[\"file\",\"memory_grow.wast\"],[\"module\",1,48],[\"invoke\",14,0,\"size\",[]],[\"invoke\",15,0,\"store_at_zero\",[]],[\"invoke\",16,0,\"load_at_zero\",[]],[\"invoke\",17,0,\"store_at_page_size\",[]],[\"invoke\",18,0,\"load_at_page_size\",[]],[\"invoke\",19,0,\"grow\",[\"i32:1\"]],[\"invoke\",20,0,\"size\",[]],[\"invoke\",21,0,\"load_at_zero\",[]],[\"invoke\",22,0,\"store_at_zero\",[]],[\"invoke\",23,0,\"load_at_zero\",[]],[\"invoke\",24,0,\"store_at_page_size\",[]],[\"invoke\",25,0,\"load_at_page_size\",[]],[\"invoke\",26,0,\"grow\",[\"i32:4\"]],[\"invoke\",27,0,\"size\",[]],[\"invoke\",28,0,\"load_at_zero\",[]],[\"invoke\",29,0,\"store_at_zero\",[]],[\"invoke\",30,0,\"load_at_zero\",[]],[\"invoke\",31,0,\"load_at_page_size\",[]],[\"invoke\",32,0,\"store_at_page_size\",[]],[\"invoke\",33,0,\"load_at_page_size\",[]],[\"module\",36,49],[\"invoke\",41,0,\"grow\",[\"i32:0\"]],[\"invoke\",42,0,\"grow\",[\"i32:1\"]],[\"invoke\",43,0,\"grow\",[\"i32:0\"]],[\"invoke\",44,0,\"grow\",[\"i32:2\"]],[\"invoke\",45,0,\"grow\",[\"i32:800\"]],[\"invoke\",46,0,\"grow\",[\"i32:65536\"]],[\"invoke\",47,0,\"grow\",[\"i32:64736\"]],[\"invoke\",48,0,\"grow\",[\"i32:1\"]],[\"module\",50,50],[\"invoke\",55,0,\"grow\",[\"i32:0\"]],[\"invoke\",56,0,\"grow\",[\"i32:1\"]],[\"invoke\",57,0,\"grow\",[\"i32:1\"]],[\"invoke\",58,0,\"grow\",[\"i32:2\"]],[\"invoke\",59,0,\"grow\",[\"i32:6\"]],[\"invoke\",60,0,\"grow\",[\"i32:0\"]],[\"invoke\",61,0,\"grow\",[\"i32:1\"]],[\"invoke\",62,0,\"grow\",[\"i32:65536\"]],[\"module\",66,51],[\"invoke\",87,0,\"check-memory-zero\",[\"i32:0\",\"i32:65535\"]],[\"invoke\",88,0,\"grow\",[\"i32:1\"]],[\"invoke\",89,0,\"check-memory-zero\",[\"i32:65536\",\"i32:131071\"]],[\"invoke\",90,0,\"grow\",[\"i32:1\"]],[\"invoke\",91,0,\"check-memory-zero\",[\"i32:131072\",\"i32:196607\"]],[\"invoke\",92,0,\"grow\",[\"i32:1\"]],[\"invoke\",93,0,\"check-memory-zero\",[\"i32:196608\",\"i32:262143\"]],[\"invoke\",94,0,\"grow\",[\"i32:1\"]],[\"invoke\",95,0,\"check-memory-zero\",[\"i32:262144\",\"i32:327679\"]],[\"invoke\",96,0,\"grow\",[\"i32:1\"]],[\"invoke\",97,0,\"check-memory-zero\",[\"i32:327680\",\"i32:393215\"]],[\"module\",101,52],[\"invoke\",259,0,\"as-br-value\",[]],[\"invoke\",261,0,\"as-br_if-cond\",[]],[\"invoke\",262,0,\"as-br_if-value\",[]],[\"invoke\",263,0,\"as-br_if-value-cond\",[]],[\"invoke\",265,0,\"as-br_table-index\",[]],[\"invoke\",266,0,\"as-br_table-value\",[]],[\"invoke\",267,0,\"as-br_table-value-index\",[]],[\"invoke\",269,0,\"as-return-value\",[]],[\"invoke\",271,0,\"as-if-cond\",[]],[\"invoke\",272,0,\"as-if-then\",[]],[\"invoke\",273,0,\"as-if-else\",[]],[\"invoke\",275,0,\"as-select-first\",[\"i32:0\",\"i32:1\"]],[\"invoke\",276,0,\"as-select-second\",[\"i32:0\",\"i32:0\"]],[\"invoke\",277,0,\"as-select-cond\",[]],[\"invoke\",279,0,\"as-call-first\",[]],[\"invoke\",280,0,\"as-call-mid\",[]],[\"invoke\",281,0,\"as-call-last\",[]],[\"invoke\",283,0,\"as-call_indirect-first\",[]],[\"invoke\",284,0,\"as-call_indirect-mid\",[]],[\"invoke\",285,0,\"as-call_indirect-last\",[]],[\"invoke\",286,0,\"as-call_indirect-index\",[]],[\"invoke\",288,0,\"as-local.set-value\",[]],[\"invoke\",289,0,\"as-local.tee-value\",[]],[\"invoke\",290,0,\"as-global.set-value\",[]],[\"invoke\",292,0,\"as-load-address\",[]],[\"invoke\",293,0,\"as-loadN-address\",[]],[\"invoke\",294,0,\"as-store-address\",[]],[\"invoke\",295,0,\"as-store-value\",[]],[\"invoke\",296,0,\"as-storeN-address\",[]],[\"invoke\",297,0,\"as-storeN-value\",[]],[\"invoke\",299,0,\"as-unary-operand\",[]],[\"invoke\",301,0,\"as-binary-left\",[]],[\"invoke\",302,0,\"as-binary-right\",[]],[\"invoke\",304,0,\"as-test-operand\",[]],[\"invoke\",306,0,\"as-compare-left\",[]],[\"invoke\",307,0,\"as-compare-right\",[]],[\"invoke\",309,0,\"as-memory.grow-size\",[]],[\"module\",312,53,\"$Mgm\"],[\"register\",\"grown-memory\",\"$Mgm\"],[\"invoke\",317,\"$Mgm\",\"grow\",[]],[\"module\",318,54,\"$Mgim1\"],[\"register\",\"grown-imported-memory\",\"$Mgim1\"],[\"invoke\",324,\"$Mgim1\",\"grow\",[]],[\"module\",325,55,\"$Mgim2\"],[\"invoke\",330,\"$Mgim2\",\"size\",[]],[\"file\",\"memory_redundancy.wast\"],[\"module\",5,56],[\"invoke\",59,0,\"test_store_to_load\",[]],[\"invoke\",60,0,\"zero_everything\",[]],[\"invoke\",61,0,\"test_redundant_load\",[]],[\"invoke\",62,0,\"zero_everything\",[]],[\"invoke\",63,0,\"test_dead_store\",[]],[\"invoke\",64,0,\"zero_everything\",[]],[\"invoke\",65,0,\"malloc_aliasing\",[]],[\"file\",\"memory_size.wast\"],[\"module\",1,57],[\"invoke\",7,0,\"size\",[]],[\"invoke\",8,0,\"grow\",[\"i32:1\"]],[\"invoke\",9,0,\"size\",[]],[\"invoke\",10,0,\"grow\",[\"i32:4\"]],[\"invoke\",11,0,\"size\",[]],[\"invoke\",12,0,\"grow\",[\"i32:0\"]],[\"invoke\",13,0,\"size\",[]],[\"module\",15,58],[\"invoke\",21,0,\"size\",[]],[\"invoke\",22,0,\"grow\",[\"i32:1\"]],[\"invoke\",23,0,\"size\",[]],[\"invoke\",24,0,\"grow\",[\"i32:4\"]],[\"invoke\",25,0,\"size\",[]],[\"invoke\",26,0,\"grow\",[\"i32:0\"]],[\"invoke\",27,0,\"size\",[]],[\"module\",29,59],[\"invoke\",35,0,\"size\",[]],[\"invoke\",36,0,\"grow\",[\"i32:3\"]],[\"invoke\",37,0,\"size\",[]],[\"invoke\",38,0,\"grow\",[\"i32:1\"]],[\"invoke\",39,0,\"size\",[]],[\"invoke\",40,0,\"grow\",[\"i32:0\"]],[\"invoke\",41,0,\"size\",[]],[\"invoke\",42,0,\"grow\",[\"i32:4\"]],[\"invoke\",43,0,\"size\",[]],[\"invoke\",44,0,\"grow\",[\"i32:1\"]],[\"invoke\",45,0,\"size\",[]],[\"module\",47,60],[\"invoke\",53,0,\"size\",[]],[\"invoke\",54,0,\"grow\",[\"i32:1\"]],[\"invoke\",55,0,\"size\",[]],[\"invoke\",56,0,\"grow\",[\"i32:3\"]],[\"invoke\",57,0,\"size\",[]],[\"invoke\",58,0,\"grow\",[\"i32:0\"]],[\"invoke\",59,0,\"size\",[]],[\"invoke\",60,0,\"grow\",[\"i32:2\"]],[\"invoke\",61,0,\"size\",[]],[\"invoke\",62,0,\"grow\",[\"i32:1\"]],[\"invoke\",63,0,\"size\",[]],[\"file\",\"memory_trap.wast\"],[\"module\",1,61],[\"invoke\",21,0,\"store\",[\"i32:4294967292\",\"i32:42\"]],[\"invoke\",22,0,\"load\",[\"i32:4294967292\"]],[\"invoke\",23,0,\"store\",[\"i32:4294967293\",\"i32:305419896\"]],[\"invoke\",24,0,\"load\",[\"i32:4294967293\"]],[\"invoke\",25,0,\"store\",[\"i32:4294967294\",\"i32:13\"]],[\"invoke\",26,0,\"load\",[\"i32:4294967294\"]],[\"invoke\",27,0,\"store\",[\"i32:4294967295\",\"i32:13\"]],[\"invoke\",28,0,\"load\",[\"i32:4294967295\"]],[\"invoke\",29,0,\"store\",[\"i32:0\",\"i32:13\"]],[\"invoke\",30,0,\"load\",[\"i32:0\"]],[\"invoke\",31,0,\"store\",[\"i32:2147483648\",\"i32:13\"]],[\"invoke\",32,0,\"load\",[\"i32:2147483648\"]],[\"invoke\",33,0,\"memory.grow\",[\"i32:65537\"]],[\"module\",35,62],[\"invoke\",111,0,\"i32.store\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",112,0,\"i32.store\",[\"i32:65535\",\"i32:0\"]],[\"invoke\",113,0,\"i32.store\",[\"i32:65534\",\"i32:0\"]],[\"invoke\",114,0,\"i32.store\",[\"i32:65533\",\"i32:0\"]],[\"invoke\",115,0,\"i32.store\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",116,0,\"i32.store\",[\"i32:4294967294\",\"i32:0\"]],[\"invoke\",117,0,\"i32.store\",[\"i32:4294967293\",\"i32:0\"]],[\"invoke\",118,0,\"i32.store\",[\"i32:4294967292\",\"i32:0\"]],[\"invoke\",119,0,\"i64.store\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",120,0,\"i64.store\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",121,0,\"i64.store\",[\"i32:65534\",\"i64:0\"]],[\"invoke\",122,0,\"i64.store\",[\"i32:65533\",\"i64:0\"]],[\"invoke\",123,0,\"i64.store\",[\"i32:65532\",\"i64:0\"]],[\"invoke\",124,0,\"i64.store\",[\"i32:65531\",\"i64:0\"]],[\"invoke\",125,0,\"i64.store\",[\"i32:65530\",\"i64:0\"]],[\"invoke\",126,0,\"i64.store\",[\"i32:65529\",\"i64:0\"]],[\"invoke\",127,0,\"i64.store\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",128,0,\"i64.store\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",129,0,\"i64.store\",[\"i32:4294967293\",\"i64:0\"]],[\"invoke\",130,0,\"i64.store\",[\"i32:4294967292\",\"i64:0\"]],[\"invoke\",131,0,\"i64.store\",[\"i32:4294967291\",\"i64:0\"]],[\"invoke\",132,0,\"i64.store\",[\"i32:4294967290\",\"i64:0\"]],[\"invoke\",133,0,\"i64.store\",[\"i32:4294967289\",\"i64:0\"]],[\"invoke\",134,0,\"i64.store\",[\"i32:4294967288\",\"i64:0\"]],[\"invoke\",135,0,\"f32.store\",[\"i32:65536\",\"f32:0\"]],[\"invoke\",136,0,\"f32.store\",[\"i32:65535\",\"f32:0\"]],[\"invoke\",137,0,\"f32.store\",[\"i32:65534\",\"f32:0\"]],[\"invoke\",138,0,\"f32.store\",[\"i32:65533\",\"f32:0\"]],[\"invoke\",139,0,\"f32.store\",[\"i32:4294967295\",\"f32:0\"]],[\"invoke\",140,0,\"f32.store\",[\"i32:4294967294\",\"f32:0\"]],[\"invoke\",141,0,\"f32.store\",[\"i32:4294967293\",\"f32:0\"]],[\"invoke\",142,0,\"f32.store\",[\"i32:4294967292\",\"f32:0\"]],[\"invoke\",143,0,\"f64.store\",[\"i32:65536\",\"f64:0\"]],[\"invoke\",144,0,\"f64.store\",[\"i32:65535\",\"f64:0\"]],[\"invoke\",145,0,\"f64.store\",[\"i32:65534\",\"f64:0\"]],[\"invoke\",146,0,\"f64.store\",[\"i32:65533\",\"f64:0\"]],[\"invoke\",147,0,\"f64.store\",[\"i32:65532\",\"f64:0\"]],[\"invoke\",148,0,\"f64.store\",[\"i32:65531\",\"f64:0\"]],[\"invoke\",149,0,\"f64.store\",[\"i32:65530\",\"f64:0\"]],[\"invoke\",150,0,\"f64.store\",[\"i32:65529\",\"f64:0\"]],[\"invoke\",151,0,\"f64.store\",[\"i32:4294967295\",\"f64:0\"]],[\"invoke\",152,0,\"f64.store\",[\"i32:4294967294\",\"f64:0\"]],[\"invoke\",153,0,\"f64.store\",[\"i32:4294967293\",\"f64:0\"]],[\"invoke\",154,0,\"f64.store\",[\"i32:4294967292\",\"f64:0\"]],[\"invoke\",155,0,\"f64.store\",[\"i32:4294967291\",\"f64:0\"]],[\"invoke\",156,0,\"f64.store\",[\"i32:4294967290\",\"f64:0\"]],[\"invoke\",157,0,\"f64.store\",[\"i32:4294967289\",\"f64:0\"]],[\"invoke\",158,0,\"f64.store\",[\"i32:4294967288\",\"f64:0\"]],[\"invoke\",159,0,\"i32.store8\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",160,0,\"i32.store8\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",161,0,\"i32.store16\",[\"i32:65536\",\"i32:0\"]],[\"invoke\",162,0,\"i32.store16\",[\"i32:65535\",\"i32:0\"]],[\"invoke\",163,0,\"i32.store16\",[\"i32:4294967295\",\"i32:0\"]],[\"invoke\",164,0,\"i32.store16\",[\"i32:4294967294\",\"i32:0\"]],[\"invoke\",165,0,\"i64.store8\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",166,0,\"i64.store8\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",167,0,\"i64.store16\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",168,0,\"i64.store16\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",169,0,\"i64.store16\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",170,0,\"i64.store16\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",171,0,\"i64.store32\",[\"i32:65536\",\"i64:0\"]],[\"invoke\",172,0,\"i64.store32\",[\"i32:65535\",\"i64:0\"]],[\"invoke\",173,0,\"i64.store32\",[\"i32:65534\",\"i64:0\"]],[\"invoke\",174,0,\"i64.store32\",[\"i32:65533\",\"i64:0\"]],[\"invoke\",175,0,\"i64.store32\",[\"i32:4294967295\",\"i64:0\"]],[\"invoke\",176,0,\"i64.store32\",[\"i32:4294967294\",\"i64:0\"]],[\"invoke\",177,0,\"i64.store32\",[\"i32:4294967293\",\"i64:0\"]],[\"invoke\",178,0,\"i64.store32\",[\"i32:4294967292\",\"i64:0\"]],[\"invoke\",179,0,\"i32.load\",[\"i32:65536\"]],[\"invoke\",180,0,\"i32.load\",[\"i32:65535\"]],[\"invoke\",181,0,\"i32.load\",[\"i32:65534\"]],[\"invoke\",182,0,\"i32.load\",[\"i32:65533\"]],[\"invoke\",183,0,\"i32.load\",[\"i32:4294967295\"]],[\"invoke\",184,0,\"i32.load\",[\"i32:4294967294\"]],[\"invoke\",185,0,\"i32.load\",[\"i32:4294967293\"]],[\"invoke\",186,0,\"i32.load\",[\"i32:4294967292\"]],[\"invoke\",187,0,\"i64.load\",[\"i32:65536\"]],[\"invoke\",188,0,\"i64.load\",[\"i32:65535\"]],[\"invoke\",189,0,\"i64.load\",[\"i32:65534\"]],[\"invoke\",190,0,\"i64.load\",[\"i32:65533\"]],[\"invoke\",191,0,\"i64.load\",[\"i32:65532\"]],[\"invoke\",192,0,\"i64.load\",[\"i32:65531\"]],[\"invoke\",193,0,\"i64.load\",[\"i32:65530\"]],[\"invoke\",194,0,\"i64.load\",[\"i32:65529\"]],[\"invoke\",195,0,\"i64.load\",[\"i32:4294967295\"]],[\"invoke\",196,0,\"i64.load\",[\"i32:4294967294\"]],[\"invoke\",197,0,\"i64.load\",[\"i32:4294967293\"]],[\"invoke\",198,0,\"i64.load\",[\"i32:4294967292\"]],[\"invoke\",199,0,\"i64.load\",[\"i32:4294967291\"]],[\"invoke\",200,0,\"i64.load\",[\"i32:4294967290\"]],[\"invoke\",201,0,\"i64.load\",[\"i32:4294967289\"]],[\"invoke\",202,0,\"i64.load\",[\"i32:4294967288\"]],[\"invoke\",203,0,\"f32.load\",[\"i32:65536\"]],[\"invoke\",204,0,\"f32.load\",[\"i32:65535\"]],[\"invoke\",205,0,\"f32.load\",[\"i32:65534\"]],[\"invoke\",206,0,\"f32.load\",[\"i32:65533\"]],[\"invoke\",207,0,\"f32.load\",[\"i32:4294967295\"]],[\"invoke\",208,0,\"f32.load\",[\"i32:4294967294\"]],[\"invoke\",209,0,\"f32.load\",[\"i32:4294967293\"]],[\"invoke\",210,0,\"f32.load\",[\"i32:4294967292\"]],[\"invoke\",211,0,\"f64.load\",[\"i32:65536\"]],[\"invoke\",212,0,\"f64.load\",[\"i32:65535\"]],[\"invoke\",213,0,\"f64.load\",[\"i32:65534\"]],[\"invoke\",214,0,\"f64.load\",[\"i32:65533\"]],[\"invoke\",215,0,\"f64.load\",[\"i32:65532\"]],[\"invoke\",216,0,\"f64.load\",[\"i32:65531\"]],[\"invoke\",217,0,\"f64.load\",[\"i32:65530\"]],[\"invoke\",218,0,\"f64.load\",[\"i32:65529\"]],[\"invoke\",219,0,\"f64.load\",[\"i32:4294967295\"]],[\"invoke\",220,0,\"f64.load\",[\"i32:4294967294\"]],[\"invoke\",221,0,\"f64.load\",[\"i32:4294967293\"]],[\"invoke\",222,0,\"f64.load\",[\"i32:4294967292\"]],[\"invoke\",223,0,\"f64.load\",[\"i32:4294967291\"]],[\"invoke\",224,0,\"f64.load\",[\"i32:4294967290\"]],[\"invoke\",225,0,\"f64.load\",[\"i32:4294967289\"]],[\"invoke\",226,0,\"f64.load\",[\"i32:4294967288\"]],[\"invoke\",227,0,\"i32.load8_s\",[\"i32:65536\"]],[\"invoke\",228,0,\"i32.load8_s\",[\"i32:4294967295\"]],[\"invoke\",229,0,\"i32.load8_u\",[\"i32:65536\"]],[\"invoke\",230,0,\"i32.load8_u\",[\"i32:4294967295\"]],[\"invoke\",231,0,\"i32.load16_s\",[\"i32:65536\"]],[\"invoke\",232,0,\"i32.load16_s\",[\"i32:65535\"]],[\"invoke\",233,0,\"i32.load16_s\",[\"i32:4294967295\"]],[\"invoke\",234,0,\"i32.load16_s\",[\"i32:4294967294\"]],[\"invoke\",235,0,\"i32.load16_u\",[\"i32:65536\"]],[\"invoke\",236,0,\"i32.load16_u\",[\"i32:65535\"]],[\"invoke\",237,0,\"i32.load16_u\",[\"i32:4294967295\"]],[\"invoke\",238,0,\"i32.load16_u\",[\"i32:4294967294\"]],[\"invoke\",239,0,\"i64.load8_s\",[\"i32:65536\"]],[\"invoke\",240,0,\"i64.load8_s\",[\"i32:4294967295\"]],[\"invoke\",241,0,\"i64.load8_u\",[\"i32:65536\"]],[\"invoke\",242,0,\"i64.load8_u\",[\"i32:4294967295\"]],[\"invoke\",243,0,\"i64.load16_s\",[\"i32:65536\"]],[\"invoke\",244,0,\"i64.load16_s\",[\"i32:65535\"]],[\"invoke\",245,0,\"i64.load16_s\",[\"i32:4294967295\"]],[\"invoke\",246,0,\"i64.load16_s\",[\"i32:4294967294\"]],[\"invoke\",247,0,\"i64.load16_u\",[\"i32:65536\"]],[\"invoke\",248,0,\"i64.load16_u\",[\"i32:65535\"]],[\"invoke\",249,0,\"i64.load16_u\",[\"i32:4294967295\"]],[\"invoke\",250,0,\"i64.load16_u\",[\"i32:4294967294\"]],[\"invoke\",251,0,\"i64.load32_s\",[\"i32:65536\"]],[\"invoke\",252,0,\"i64.load32_s\",[\"i32:65535\"]],[\"invoke\",253,0,\"i64.load32_s\",[\"i32:65534\"]],[\"invoke\",254,0,\"i64.load32_s\",[\"i32:65533\"]],[\"invoke\",255,0,\"i64.load32_s\",[\"i32:4294967295\"]],[\"invoke\",256,0,\"i64.load32_s\",[\"i32:4294967294\"]],[\"invoke\",257,0,\"i64.load32_s\",[\"i32:4294967293\"]],[\"invoke\",258,0,\"i64.load32_s\",[\"i32:4294967292\"]],[\"invoke\",259,0,\"i64.load32_u\",[\"i32:65536\"]],[\"invoke\",260,0,\"i64.load32_u\",[\"i32:65535\"]],[\"invoke\",261,0,\"i64.load32_u\",[\"i32:65534\"]],[\"invoke\",262,0,\"i64.load32_u\",[\"i32:65533\"]],[\"invoke\",263,0,\"i64.load32_u\",[\"i32:4294967295\"]],[\"invoke\",264,0,\"i64.load32_u\",[\"i32:4294967294\"]],[\"invoke\",265,0,\"i64.load32_u\",[\"i32:4294967293\"]],[\"invoke\",266,0,\"i64.load32_u\",[\"i32:4294967292\"]],[\"invoke\",269,0,\"i64.load\",[\"i32:65528\"]],[\"invoke\",270,0,\"i64.load\",[\"i32:0\"]],[\"invoke\",274,0,\"i64.store\",[\"i32:65528\",\"i64:0\"]],[\"invoke\",275,0,\"i32.store\",[\"i32:65533\",\"i32:305419896\"]],[\"invoke\",276,0,\"i32.load\",[\"i32:65532\"]],[\"invoke\",277,0,\"i64.store\",[\"i32:65529\",\"i64:1311768467294899695\"]],[\"invoke\",278,0,\"i64.load\",[\"i32:65528\"]],[\"invoke\",279,0,\"f32.store\",[\"i32:65533\",\"f32:1301390004\"]],[\"invoke\",280,0,\"f32.load\",[\"i32:65532\"]],[\"invoke\",281,0,\"f64.store\",[\"i32:65529\",\"f64:4878018892390247374\"]],[\"invoke\",282,0,\"f64.load\",[\"i32:65528\"]],[\"file\",\"bulk.wast\"],[\"module\",2,63],[\"module\",6,64],[\"module\",13,65],[\"invoke\",27,0,\"fill\",[\"i32:1\",\"i32:255\",\"i32:3\"]],[\"invoke\",28,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",29,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",30,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",31,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",32,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",35,0,\"fill\",[\"i32:0\",\"i32:48042\",\"i32:2\"]],[\"invoke\",36,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",37,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",40,0,\"fill\",[\"i32:0\",\"i32:0\",\"i32:65536\"]],[\"invoke\",43,0,\"fill\",[\"i32:65280\",\"i32:1\",\"i32:257\"]],[\"invoke\",45,0,\"load8_u\",[\"i32:65280\"]],[\"invoke\",46,0,\"load8_u\",[\"i32:65535\"]],[\"invoke\",49,0,\"fill\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",52,0,\"fill\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"module\",57,66],[\"invoke\",71,0,\"copy\",[\"i32:10\",\"i32:0\",\"i32:4\"]],[\"invoke\",73,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",74,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",75,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",76,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",77,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",78,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",81,0,\"copy\",[\"i32:8\",\"i32:10\",\"i32:4\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",83,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",84,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",85,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",86,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",87,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",90,0,\"copy\",[\"i32:10\",\"i32:7\",\"i32:6\"]],[\"invoke\",91,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",92,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",93,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",94,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",95,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",96,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",97,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",100,0,\"copy\",[\"i32:65280\",\"i32:0\",\"i32:256\"]],[\"invoke\",101,0,\"copy\",[\"i32:65024\",\"i32:65280\",\"i32:256\"]],[\"invoke\",104,0,\"copy\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",105,0,\"copy\",[\"i32:0\",\"i32:65536\",\"i32:0\"]],[\"invoke\",108,0,\"copy\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"invoke\",110,0,\"copy\",[\"i32:0\",\"i32:65537\",\"i32:0\"]],[\"module\",115,67],[\"invoke\",129,0,\"init\",[\"i32:0\",\"i32:1\",\"i32:2\"]],[\"invoke\",130,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",131,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",132,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",135,0,\"init\",[\"i32:65532\",\"i32:0\",\"i32:4\"]],[\"invoke\",138,0,\"init\",[\"i32:65534\",\"i32:0\",\"i32:3\"]],[\"invoke\",140,0,\"load8_u\",[\"i32:65534\"]],[\"invoke\",141,0,\"load8_u\",[\"i32:65535\"]],[\"invoke\",144,0,\"init\",[\"i32:65536\",\"i32:0\",\"i32:0\"]],[\"invoke\",145,0,\"init\",[\"i32:0\",\"i32:4\",\"i32:0\"]],[\"invoke\",148,0,\"init\",[\"i32:65537\",\"i32:0\",\"i32:0\"]],[\"invoke\",150,0,\"init\",[\"i32:0\",\"i32:5\",\"i32:0\"]],[\"module\",154,68],[\"invoke\",168,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",169,0,\"drop_passive\",[]],[\"invoke\",170,0,\"drop_passive\",[]],[\"invoke\",171,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",172,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",173,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",174,0,\"drop_active\",[]],[\"invoke\",175,0,\"init_active\",[\"i32:0\"]],[\"invoke\",176,0,\"init_active\",[\"i32:1\"]],[\"invoke\",177,0,\"init_active\",[\"i32:0\"]],[\"module\",181,69],[\"module\",196,70],[\"module\",199,71],[\"invoke\",219,0,\"init\",[\"i32:2\",\"i32:0\",\"i32:2\"]],[\"invoke\",221,0,\"call\",[\"i32:2\"]],[\"invoke\",224,0,\"init\",[\"i32:0\",\"i32:1\",\"i32:2\"]],[\"invoke\",225,0,\"call\",[\"i32:0\"]],[\"invoke\",226,0,\"call\",[\"i32:1\"]],[\"invoke\",227,0,\"call\",[\"i32:2\"]],[\"invoke\",230,0,\"init\",[\"i32:1\",\"i32:2\",\"i32:2\"]],[\"invoke\",233,0,\"init\",[\"i32:3\",\"i32:0\",\"i32:0\"]],[\"invoke\",234,0,\"init\",[\"i32:0\",\"i32:4\",\"i32:0\"]],[\"invoke\",237,0,\"init\",[\"i32:4\",\"i32:0\",\"i32:0\"]],[\"invoke\",239,0,\"init\",[\"i32:0\",\"i32:5\",\"i32:0\"]],[\"module\",244,72],[\"invoke\",261,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",262,0,\"drop_passive\",[]],[\"invoke\",263,0,\"drop_passive\",[]],[\"invoke\",264,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",265,0,\"init_passive\",[\"i32:1\"]],[\"invoke\",266,0,\"init_passive\",[\"i32:0\"]],[\"invoke\",267,0,\"drop_active\",[]],[\"invoke\",268,0,\"init_active\",[\"i32:0\"]],[\"invoke\",269,0,\"init_active\",[\"i32:1\"]],[\"invoke\",270,0,\"init_active\",[\"i32:0\"]],[\"module\",274,73],[\"module\",297,74],[\"module\",300,75],[\"invoke\",319,0,\"copy\",[\"i32:3\",\"i32:0\",\"i32:3\"]],[\"invoke\",321,0,\"call\",[\"i32:3\"]],[\"invoke\",322,0,\"call\",[\"i32:4\"]],[\"invoke\",323,0,\"call\",[\"i32:5\"]],[\"invoke\",326,0,\"copy\",[\"i32:0\",\"i32:1\",\"i32:3\"]],[\"invoke\",328,0,\"call\",[\"i32:0\"]],[\"invoke\",329,0,\"call\",[\"i32:1\"]],[\"invoke\",330,0,\"call\",[\"i32:2\"]],[\"invoke\",333,0,\"copy\",[\"i32:2\",\"i32:0\",\"i32:3\"]],[\"invoke\",335,0,\"call\",[\"i32:2\"]],[\"invoke\",336,0,\"call\",[\"i32:3\"]],[\"invoke\",337,0,\"call\",[\"i32:4\"]],[\"invoke\",340,0,\"copy\",[\"i32:6\",\"i32:8\",\"i32:2\"]],[\"invoke\",341,0,\"copy\",[\"i32:8\",\"i32:6\",\"i32:2\"]],[\"invoke\",344,0,\"copy\",[\"i32:10\",\"i32:0\",\"i32:0\"]],[\"invoke\",345,0,\"copy\",[\"i32:0\",\"i32:10\",\"i32:0\"]],[\"invoke\",348,0,\"copy\",[\"i32:11\",\"i32:0\",\"i32:0\"]],[\"invoke\",350,0,\"copy\",[\"i32:0\",\"i32:11\",\"i32:0\"]],[\"file\",\"memory_copy.wast\"],[\"module\",6,76],[\"invoke\",15,0,\"test\",[]],[\"invoke\",27,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",38,0,\"load8_u\",[\"i32:21\"]],[\"module\",48,77],[\"invoke\",60,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",71,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:23\"]],[\"module\",90,78],[\"invoke\",104,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",115,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",126,0,\"load8_u\",[\"i32:25\"]],[\"module\",132,79],[\"invoke\",147,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",158,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",169,0,\"load8_u\",[\"i32:26\"]],[\"module\",174,80],[\"invoke\",191,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",202,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",213,0,\"load8_u\",[\"i32:28\"]],[\"module\",216,81],[\"invoke\",235,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",246,0,\"load8_u\",[\"i32:19\"]],[\"module\",258,82],[\"invoke\",267,0,\"test\",[]],[\"invoke\",278,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",289,0,\"load8_u\",[\"i32:20\"]],[\"module\",300,83],[\"invoke\",311,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",322,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",333,0,\"load8_u\",[\"i32:22\"]],[\"module\",342,84],[\"invoke\",355,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",366,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",377,0,\"load8_u\",[\"i32:1014\"]],[\"invoke\",387,0,\"load8_u\",[\"i32:3004\"]],[\"invoke\",398,0,\"load8_u\",[\"i32:5193\"]],[\"invoke\",409,0,\"load8_u\",[\"i32:7382\"]],[\"invoke\",420,0,\"load8_u\",[\"i32:9571\"]],[\"invoke\",431,0,\"load8_u\",[\"i32:11760\"]],[\"invoke\",442,0,\"load8_u\",[\"i32:13949\"]],[\"invoke\",453,0,\"load8_u\",[\"i32:16138\"]],[\"invoke\",464,0,\"load8_u\",[\"i32:18327\"]],[\"invoke\",475,0,\"load8_u\",[\"i32:20516\"]],[\"invoke\",485,0,\"load8_u\",[\"i32:22506\"]],[\"invoke\",496,0,\"load8_u\",[\"i32:24695\"]],[\"invoke\",507,0,\"load8_u\",[\"i32:26884\"]],[\"invoke\",518,0,\"load8_u\",[\"i32:29073\"]],[\"invoke\",529,0,\"load8_u\",[\"i32:31262\"]],[\"invoke\",540,0,\"load8_u\",[\"i32:33451\"]],[\"invoke\",551,0,\"load8_u\",[\"i32:35640\"]],[\"invoke\",562,0,\"load8_u\",[\"i32:37829\"]],[\"invoke\",572,0,\"load8_u\",[\"i32:39819\"]],[\"invoke\",583,0,\"load8_u\",[\"i32:42008\"]],[\"invoke\",594,0,\"load8_u\",[\"i32:44197\"]],[\"invoke\",605,0,\"load8_u\",[\"i32:46386\"]],[\"invoke\",616,0,\"load8_u\",[\"i32:48575\"]],[\"invoke\",627,0,\"load8_u\",[\"i32:50764\"]],[\"invoke\",638,0,\"load8_u\",[\"i32:52953\"]],[\"invoke\",649,0,\"load8_u\",[\"i32:55142\"]],[\"invoke\",660,0,\"load8_u\",[\"i32:57331\"]],[\"invoke\",670,0,\"load8_u\",[\"i32:59321\"]],[\"invoke\",681,0,\"load8_u\",[\"i32:61510\"]],[\"invoke\",692,0,\"load8_u\",[\"i32:63699\"]],[\"module\",703,85],[\"invoke\",714,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",725,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",736,0,\"load8_u\",[\"i32:418\"]],[\"invoke\",747,0,\"load8_u\",[\"i32:2607\"]],[\"invoke\",758,0,\"load8_u\",[\"i32:4796\"]],[\"invoke\",768,0,\"load8_u\",[\"i32:6786\"]],[\"invoke\",779,0,\"load8_u\",[\"i32:8975\"]],[\"invoke\",790,0,\"load8_u\",[\"i32:11164\"]],[\"invoke\",801,0,\"load8_u\",[\"i32:13353\"]],[\"invoke\",812,0,\"load8_u\",[\"i32:15542\"]],[\"invoke\",823,0,\"load8_u\",[\"i32:17731\"]],[\"invoke\",834,0,\"load8_u\",[\"i32:19920\"]],[\"invoke\",845,0,\"load8_u\",[\"i32:22109\"]],[\"invoke\",856,0,\"load8_u\",[\"i32:24298\"]],[\"invoke\",866,0,\"load8_u\",[\"i32:26288\"]],[\"invoke\",877,0,\"load8_u\",[\"i32:28477\"]],[\"invoke\",888,0,\"load8_u\",[\"i32:30666\"]],[\"invoke\",899,0,\"load8_u\",[\"i32:32855\"]],[\"invoke\",910,0,\"load8_u\",[\"i32:35044\"]],[\"invoke\",921,0,\"load8_u\",[\"i32:37233\"]],[\"invoke\",932,0,\"load8_u\",[\"i32:39422\"]],[\"invoke\",943,0,\"load8_u\",[\"i32:41611\"]],[\"invoke\",953,0,\"load8_u\",[\"i32:43601\"]],[\"invoke\",964,0,\"load8_u\",[\"i32:45790\"]],[\"invoke\",975,0,\"load8_u\",[\"i32:47979\"]],[\"invoke\",986,0,\"load8_u\",[\"i32:50168\"]],[\"invoke\",997,0,\"load8_u\",[\"i32:52357\"]],[\"invoke\",1008,0,\"load8_u\",[\"i32:54546\"]],[\"invoke\",1019,0,\"load8_u\",[\"i32:56735\"]],[\"invoke\",1030,0,\"load8_u\",[\"i32:58924\"]],[\"invoke\",1041,0,\"load8_u\",[\"i32:61113\"]],[\"invoke\",1051,0,\"load8_u\",[\"i32:63103\"]],[\"invoke\",1062,0,\"load8_u\",[\"i32:65292\"]],[\"module\",1065,86],[\"invoke\",1084,0,\"load8_u\",[\"i32:1790\"]],[\"invoke\",1095,0,\"load8_u\",[\"i32:3979\"]],[\"invoke\",1106,0,\"load8_u\",[\"i32:6168\"]],[\"invoke\",1117,0,\"load8_u\",[\"i32:8357\"]],[\"invoke\",1128,0,\"load8_u\",[\"i32:10546\"]],[\"invoke\",1139,0,\"load8_u\",[\"i32:12735\"]],[\"invoke\",1149,0,\"load8_u\",[\"i32:14725\"]],[\"invoke\",1160,0,\"load8_u\",[\"i32:16914\"]],[\"invoke\",1171,0,\"load8_u\",[\"i32:19103\"]],[\"invoke\",1182,0,\"load8_u\",[\"i32:21292\"]],[\"invoke\",1193,0,\"load8_u\",[\"i32:23481\"]],[\"invoke\",1204,0,\"load8_u\",[\"i32:25670\"]],[\"invoke\",1215,0,\"load8_u\",[\"i32:27859\"]],[\"invoke\",1226,0,\"load8_u\",[\"i32:30048\"]],[\"invoke\",1237,0,\"load8_u\",[\"i32:32237\"]],[\"invoke\",1247,0,\"load8_u\",[\"i32:34227\"]],[\"invoke\",1258,0,\"load8_u\",[\"i32:36416\"]],[\"invoke\",1269,0,\"load8_u\",[\"i32:38605\"]],[\"invoke\",1280,0,\"load8_u\",[\"i32:40794\"]],[\"invoke\",1291,0,\"load8_u\",[\"i32:42983\"]],[\"invoke\",1302,0,\"load8_u\",[\"i32:45172\"]],[\"invoke\",1313,0,\"load8_u\",[\"i32:47361\"]],[\"invoke\",1324,0,\"load8_u\",[\"i32:49550\"]],[\"invoke\",1334,0,\"load8_u\",[\"i32:51540\"]],[\"invoke\",1345,0,\"load8_u\",[\"i32:53729\"]],[\"invoke\",1356,0,\"load8_u\",[\"i32:55918\"]],[\"invoke\",1367,0,\"load8_u\",[\"i32:58107\"]],[\"invoke\",1378,0,\"load8_u\",[\"i32:60296\"]],[\"invoke\",1389,0,\"load8_u\",[\"i32:62485\"]],[\"invoke\",1400,0,\"load8_u\",[\"i32:64674\"]],[\"invoke\",1411,0,\"load8_u\",[\"i32:65522\"]],[\"invoke\",1422,0,\"load8_u\",[\"i32:65533\"]],[\"module\",1426,87],[\"invoke\",1443,0,\"load8_u\",[\"i32:1392\"]],[\"invoke\",1454,0,\"load8_u\",[\"i32:3581\"]],[\"invoke\",1465,0,\"load8_u\",[\"i32:5770\"]],[\"invoke\",1476,0,\"load8_u\",[\"i32:7959\"]],[\"invoke\",1487,0,\"load8_u\",[\"i32:10148\"]],[\"invoke\",1498,0,\"load8_u\",[\"i32:12337\"]],[\"invoke\",1509,0,\"load8_u\",[\"i32:14526\"]],[\"invoke\",1520,0,\"load8_u\",[\"i32:16715\"]],[\"invoke\",1530,0,\"load8_u\",[\"i32:18705\"]],[\"invoke\",1541,0,\"load8_u\",[\"i32:20894\"]],[\"invoke\",1552,0,\"load8_u\",[\"i32:23083\"]],[\"invoke\",1563,0,\"load8_u\",[\"i32:25272\"]],[\"invoke\",1574,0,\"load8_u\",[\"i32:27461\"]],[\"invoke\",1585,0,\"load8_u\",[\"i32:29650\"]],[\"invoke\",1596,0,\"load8_u\",[\"i32:31839\"]],[\"invoke\",1607,0,\"load8_u\",[\"i32:34028\"]],[\"invoke\",1618,0,\"load8_u\",[\"i32:36217\"]],[\"invoke\",1628,0,\"load8_u\",[\"i32:38207\"]],[\"invoke\",1639,0,\"load8_u\",[\"i32:40396\"]],[\"invoke\",1650,0,\"load8_u\",[\"i32:42585\"]],[\"invoke\",1661,0,\"load8_u\",[\"i32:44774\"]],[\"invoke\",1672,0,\"load8_u\",[\"i32:46963\"]],[\"invoke\",1683,0,\"load8_u\",[\"i32:49152\"]],[\"invoke\",1694,0,\"load8_u\",[\"i32:51341\"]],[\"invoke\",1705,0,\"load8_u\",[\"i32:53530\"]],[\"invoke\",1715,0,\"load8_u\",[\"i32:55520\"]],[\"invoke\",1726,0,\"load8_u\",[\"i32:57709\"]],[\"invoke\",1737,0,\"load8_u\",[\"i32:59898\"]],[\"invoke\",1748,0,\"load8_u\",[\"i32:62087\"]],[\"invoke\",1759,0,\"load8_u\",[\"i32:64276\"]],[\"invoke\",1770,0,\"load8_u\",[\"i32:65519\"]],[\"invoke\",1781,0,\"load8_u\",[\"i32:65530\"]],[\"module\",1788,88],[\"invoke\",1803,0,\"load8_u\",[\"i32:994\"]],[\"invoke\",1814,0,\"load8_u\",[\"i32:3183\"]],[\"invoke\",1824,0,\"load8_u\",[\"i32:5173\"]],[\"invoke\",1835,0,\"load8_u\",[\"i32:7362\"]],[\"invoke\",1846,0,\"load8_u\",[\"i32:9551\"]],[\"invoke\",1857,0,\"load8_u\",[\"i32:11740\"]],[\"invoke\",1868,0,\"load8_u\",[\"i32:13929\"]],[\"invoke\",1879,0,\"load8_u\",[\"i32:16118\"]],[\"invoke\",1890,0,\"load8_u\",[\"i32:18307\"]],[\"invoke\",1901,0,\"load8_u\",[\"i32:20496\"]],[\"invoke\",1911,0,\"load8_u\",[\"i32:22486\"]],[\"invoke\",1922,0,\"load8_u\",[\"i32:24675\"]],[\"invoke\",1933,0,\"load8_u\",[\"i32:26864\"]],[\"invoke\",1944,0,\"load8_u\",[\"i32:29053\"]],[\"invoke\",1955,0,\"load8_u\",[\"i32:31242\"]],[\"invoke\",1966,0,\"load8_u\",[\"i32:33431\"]],[\"invoke\",1977,0,\"load8_u\",[\"i32:35620\"]],[\"invoke\",1988,0,\"load8_u\",[\"i32:37809\"]],[\"invoke\",1999,0,\"load8_u\",[\"i32:39998\"]],[\"invoke\",2009,0,\"load8_u\",[\"i32:41988\"]],[\"invoke\",2020,0,\"load8_u\",[\"i32:44177\"]],[\"invoke\",2031,0,\"load8_u\",[\"i32:46366\"]],[\"invoke\",2042,0,\"load8_u\",[\"i32:48555\"]],[\"invoke\",2053,0,\"load8_u\",[\"i32:50744\"]],[\"invoke\",2064,0,\"load8_u\",[\"i32:52933\"]],[\"invoke\",2075,0,\"load8_u\",[\"i32:55122\"]],[\"invoke\",2086,0,\"load8_u\",[\"i32:57311\"]],[\"invoke\",2096,0,\"load8_u\",[\"i32:59301\"]],[\"invoke\",2107,0,\"load8_u\",[\"i32:61490\"]],[\"invoke\",2118,0,\"load8_u\",[\"i32:63679\"]],[\"invoke\",2129,0,\"load8_u\",[\"i32:65487\"]],[\"invoke\",2140,0,\"load8_u\",[\"i32:65498\"]],[\"module\",2149,86],[\"invoke\",2162,0,\"load8_u\",[\"i32:596\"]],[\"invoke\",2173,0,\"load8_u\",[\"i32:2785\"]],[\"invoke\",2184,0,\"load8_u\",[\"i32:4974\"]],[\"invoke\",2195,0,\"load8_u\",[\"i32:7163\"]],[\"invoke\",2205,0,\"load8_u\",[\"i32:9153\"]],[\"invoke\",2216,0,\"load8_u\",[\"i32:11342\"]],[\"invoke\",2227,0,\"load8_u\",[\"i32:13531\"]],[\"invoke\",2238,0,\"load8_u\",[\"i32:15720\"]],[\"invoke\",2249,0,\"load8_u\",[\"i32:17909\"]],[\"invoke\",2260,0,\"load8_u\",[\"i32:20098\"]],[\"invoke\",2271,0,\"load8_u\",[\"i32:22287\"]],[\"invoke\",2282,0,\"load8_u\",[\"i32:24476\"]],[\"invoke\",2292,0,\"load8_u\",[\"i32:26466\"]],[\"invoke\",2303,0,\"load8_u\",[\"i32:28655\"]],[\"invoke\",2314,0,\"load8_u\",[\"i32:30844\"]],[\"invoke\",2325,0,\"load8_u\",[\"i32:33033\"]],[\"invoke\",2336,0,\"load8_u\",[\"i32:35222\"]],[\"invoke\",2347,0,\"load8_u\",[\"i32:37411\"]],[\"invoke\",2358,0,\"load8_u\",[\"i32:39600\"]],[\"invoke\",2369,0,\"load8_u\",[\"i32:41789\"]],[\"invoke\",2380,0,\"load8_u\",[\"i32:43978\"]],[\"invoke\",2390,0,\"load8_u\",[\"i32:45968\"]],[\"invoke\",2401,0,\"load8_u\",[\"i32:48157\"]],[\"invoke\",2412,0,\"load8_u\",[\"i32:50346\"]],[\"invoke\",2423,0,\"load8_u\",[\"i32:52535\"]],[\"invoke\",2434,0,\"load8_u\",[\"i32:54724\"]],[\"invoke\",2445,0,\"load8_u\",[\"i32:56913\"]],[\"invoke\",2456,0,\"load8_u\",[\"i32:59102\"]],[\"invoke\",2467,0,\"load8_u\",[\"i32:61291\"]],[\"invoke\",2477,0,\"load8_u\",[\"i32:63281\"]],[\"invoke\",2488,0,\"load8_u\",[\"i32:65470\"]],[\"invoke\",2499,0,\"load8_u\",[\"i32:65526\"]],[\"module\",2510,89],[\"invoke\",2521,0,\"load8_u\",[\"i32:198\"]],[\"invoke\",2532,0,\"load8_u\",[\"i32:2387\"]],[\"invoke\",2543,0,\"load8_u\",[\"i32:4576\"]],[\"invoke\",2554,0,\"load8_u\",[\"i32:6765\"]],[\"invoke\",2565,0,\"load8_u\",[\"i32:8954\"]],[\"invoke\",2576,0,\"load8_u\",[\"i32:11143\"]],[\"invoke\",2586,0,\"load8_u\",[\"i32:13133\"]],[\"invoke\",2597,0,\"load8_u\",[\"i32:15322\"]],[\"invoke\",2608,0,\"load8_u\",[\"i32:17511\"]],[\"invoke\",2619,0,\"load8_u\",[\"i32:19700\"]],[\"invoke\",2630,0,\"load8_u\",[\"i32:21889\"]],[\"invoke\",2641,0,\"load8_u\",[\"i32:24078\"]],[\"invoke\",2652,0,\"load8_u\",[\"i32:26267\"]],[\"invoke\",2663,0,\"load8_u\",[\"i32:28456\"]],[\"invoke\",2673,0,\"load8_u\",[\"i32:30446\"]],[\"invoke\",2684,0,\"load8_u\",[\"i32:32635\"]],[\"invoke\",2695,0,\"load8_u\",[\"i32:34824\"]],[\"invoke\",2706,0,\"load8_u\",[\"i32:37013\"]],[\"invoke\",2717,0,\"load8_u\",[\"i32:39202\"]],[\"invoke\",2728,0,\"load8_u\",[\"i32:41391\"]],[\"invoke\",2739,0,\"load8_u\",[\"i32:43580\"]],[\"invoke\",2750,0,\"load8_u\",[\"i32:45769\"]],[\"invoke\",2761,0,\"load8_u\",[\"i32:47958\"]],[\"invoke\",2771,0,\"load8_u\",[\"i32:49948\"]],[\"invoke\",2782,0,\"load8_u\",[\"i32:52137\"]],[\"invoke\",2793,0,\"load8_u\",[\"i32:54326\"]],[\"invoke\",2804,0,\"load8_u\",[\"i32:56515\"]],[\"invoke\",2815,0,\"load8_u\",[\"i32:58704\"]],[\"invoke\",2826,0,\"load8_u\",[\"i32:60893\"]],[\"invoke\",2837,0,\"load8_u\",[\"i32:63082\"]],[\"invoke\",2848,0,\"load8_u\",[\"i32:65271\"]],[\"invoke\",2858,0,\"load8_u\",[\"i32:65514\"]],[\"invoke\",2869,0,\"load8_u\",[\"i32:65525\"]],[\"module\",2871,86],[\"invoke\",2891,0,\"load8_u\",[\"i32:1989\"]],[\"invoke\",2902,0,\"load8_u\",[\"i32:4178\"]],[\"invoke\",2913,0,\"load8_u\",[\"i32:6367\"]],[\"invoke\",2924,0,\"load8_u\",[\"i32:8556\"]],[\"invoke\",2935,0,\"load8_u\",[\"i32:10745\"]],[\"invoke\",2946,0,\"load8_u\",[\"i32:12934\"]],[\"invoke\",2957,0,\"load8_u\",[\"i32:15123\"]],[\"invoke\",2967,0,\"load8_u\",[\"i32:17113\"]],[\"invoke\",2978,0,\"load8_u\",[\"i32:19302\"]],[\"invoke\",2989,0,\"load8_u\",[\"i32:21491\"]],[\"invoke\",3000,0,\"load8_u\",[\"i32:23680\"]],[\"invoke\",3011,0,\"load8_u\",[\"i32:25869\"]],[\"invoke\",3022,0,\"load8_u\",[\"i32:28058\"]],[\"invoke\",3033,0,\"load8_u\",[\"i32:30247\"]],[\"invoke\",3044,0,\"load8_u\",[\"i32:32436\"]],[\"invoke\",3054,0,\"load8_u\",[\"i32:34426\"]],[\"invoke\",3065,0,\"load8_u\",[\"i32:36615\"]],[\"invoke\",3076,0,\"load8_u\",[\"i32:38804\"]],[\"invoke\",3087,0,\"load8_u\",[\"i32:40993\"]],[\"invoke\",3098,0,\"load8_u\",[\"i32:43182\"]],[\"invoke\",3109,0,\"load8_u\",[\"i32:45371\"]],[\"invoke\",3120,0,\"load8_u\",[\"i32:47560\"]],[\"invoke\",3131,0,\"load8_u\",[\"i32:49749\"]],[\"invoke\",3142,0,\"load8_u\",[\"i32:51938\"]],[\"invoke\",3152,0,\"load8_u\",[\"i32:53928\"]],[\"invoke\",3163,0,\"load8_u\",[\"i32:56117\"]],[\"invoke\",3174,0,\"load8_u\",[\"i32:58306\"]],[\"invoke\",3185,0,\"load8_u\",[\"i32:60495\"]],[\"invoke\",3196,0,\"load8_u\",[\"i32:62684\"]],[\"invoke\",3207,0,\"load8_u\",[\"i32:64873\"]],[\"invoke\",3218,0,\"load8_u\",[\"i32:65523\"]],[\"invoke\",3229,0,\"load8_u\",[\"i32:65534\"]],[\"module\",3232,86],[\"invoke\",3250,0,\"load8_u\",[\"i32:1591\"]],[\"invoke\",3261,0,\"load8_u\",[\"i32:3780\"]],[\"invoke\",3272,0,\"load8_u\",[\"i32:5969\"]],[\"invoke\",3283,0,\"load8_u\",[\"i32:8158\"]],[\"invoke\",3294,0,\"load8_u\",[\"i32:10347\"]],[\"invoke\",3305,0,\"load8_u\",[\"i32:12536\"]],[\"invoke\",3316,0,\"load8_u\",[\"i32:14725\"]],[\"invoke\",3327,0,\"load8_u\",[\"i32:16914\"]],[\"invoke\",3338,0,\"load8_u\",[\"i32:19103\"]],[\"invoke\",3348,0,\"load8_u\",[\"i32:21093\"]],[\"invoke\",3359,0,\"load8_u\",[\"i32:23282\"]],[\"invoke\",3370,0,\"load8_u\",[\"i32:25471\"]],[\"invoke\",3381,0,\"load8_u\",[\"i32:27660\"]],[\"invoke\",3392,0,\"load8_u\",[\"i32:29849\"]],[\"invoke\",3403,0,\"load8_u\",[\"i32:32038\"]],[\"invoke\",3414,0,\"load8_u\",[\"i32:34227\"]],[\"invoke\",3425,0,\"load8_u\",[\"i32:36416\"]],[\"invoke\",3435,0,\"load8_u\",[\"i32:38406\"]],[\"invoke\",3446,0,\"load8_u\",[\"i32:40595\"]],[\"invoke\",3457,0,\"load8_u\",[\"i32:42784\"]],[\"invoke\",3468,0,\"load8_u\",[\"i32:44973\"]],[\"invoke\",3479,0,\"load8_u\",[\"i32:47162\"]],[\"invoke\",3490,0,\"load8_u\",[\"i32:49351\"]],[\"invoke\",3501,0,\"load8_u\",[\"i32:51540\"]],[\"invoke\",3512,0,\"load8_u\",[\"i32:53729\"]],[\"invoke\",3523,0,\"load8_u\",[\"i32:55918\"]],[\"invoke\",3533,0,\"load8_u\",[\"i32:57908\"]],[\"invoke\",3544,0,\"load8_u\",[\"i32:60097\"]],[\"invoke\",3555,0,\"load8_u\",[\"i32:62286\"]],[\"invoke\",3566,0,\"load8_u\",[\"i32:64475\"]],[\"invoke\",3577,0,\"load8_u\",[\"i32:65521\"]],[\"invoke\",3588,0,\"load8_u\",[\"i32:65532\"]],[\"module\",3593,90],[\"invoke\",3610,0,\"load8_u\",[\"i32:1392\"]],[\"invoke\",3621,0,\"load8_u\",[\"i32:3581\"]],[\"invoke\",3631,0,\"load8_u\",[\"i32:5571\"]],[\"invoke\",3642,0,\"load8_u\",[\"i32:7760\"]],[\"invoke\",3653,0,\"load8_u\",[\"i32:9949\"]],[\"invoke\",3664,0,\"load8_u\",[\"i32:12138\"]],[\"invoke\",3675,0,\"load8_u\",[\"i32:14327\"]],[\"invoke\",3686,0,\"load8_u\",[\"i32:16516\"]],[\"invoke\",3697,0,\"load8_u\",[\"i32:18705\"]],[\"invoke\",3708,0,\"load8_u\",[\"i32:20894\"]],[\"invoke\",3719,0,\"load8_u\",[\"i32:23083\"]],[\"invoke\",3729,0,\"load8_u\",[\"i32:25073\"]],[\"invoke\",3740,0,\"load8_u\",[\"i32:27262\"]],[\"invoke\",3751,0,\"load8_u\",[\"i32:29451\"]],[\"invoke\",3762,0,\"load8_u\",[\"i32:31640\"]],[\"invoke\",3773,0,\"load8_u\",[\"i32:33829\"]],[\"invoke\",3784,0,\"load8_u\",[\"i32:36018\"]],[\"invoke\",3795,0,\"load8_u\",[\"i32:38207\"]],[\"invoke\",3806,0,\"load8_u\",[\"i32:40396\"]],[\"invoke\",3816,0,\"load8_u\",[\"i32:42386\"]],[\"invoke\",3827,0,\"load8_u\",[\"i32:44575\"]],[\"invoke\",3838,0,\"load8_u\",[\"i32:46764\"]],[\"invoke\",3849,0,\"load8_u\",[\"i32:48953\"]],[\"invoke\",3860,0,\"load8_u\",[\"i32:51142\"]],[\"invoke\",3871,0,\"load8_u\",[\"i32:53331\"]],[\"invoke\",3882,0,\"load8_u\",[\"i32:55520\"]],[\"invoke\",3893,0,\"load8_u\",[\"i32:57709\"]],[\"invoke\",3904,0,\"load8_u\",[\"i32:59898\"]],[\"invoke\",3914,0,\"load8_u\",[\"i32:61888\"]],[\"invoke\",3925,0,\"load8_u\",[\"i32:64077\"]],[\"invoke\",3936,0,\"load8_u\",[\"i32:65519\"]],[\"invoke\",3947,0,\"load8_u\",[\"i32:65530\"]],[\"module\",3954,91],[\"invoke\",3969,0,\"load8_u\",[\"i32:994\"]],[\"invoke\",3980,0,\"load8_u\",[\"i32:3183\"]],[\"invoke\",3991,0,\"load8_u\",[\"i32:5372\"]],[\"invoke\",4002,0,\"load8_u\",[\"i32:7561\"]],[\"invoke\",4012,0,\"load8_u\",[\"i32:9551\"]],[\"invoke\",4023,0,\"load8_u\",[\"i32:11740\"]],[\"invoke\",4034,0,\"load8_u\",[\"i32:13929\"]],[\"invoke\",4045,0,\"load8_u\",[\"i32:16118\"]],[\"invoke\",4056,0,\"load8_u\",[\"i32:18307\"]],[\"invoke\",4067,0,\"load8_u\",[\"i32:20496\"]],[\"invoke\",4078,0,\"load8_u\",[\"i32:22685\"]],[\"invoke\",4089,0,\"load8_u\",[\"i32:24874\"]],[\"invoke\",4100,0,\"load8_u\",[\"i32:27063\"]],[\"invoke\",4110,0,\"load8_u\",[\"i32:29053\"]],[\"invoke\",4121,0,\"load8_u\",[\"i32:31242\"]],[\"invoke\",4132,0,\"load8_u\",[\"i32:33431\"]],[\"invoke\",4143,0,\"load8_u\",[\"i32:35620\"]],[\"invoke\",4154,0,\"load8_u\",[\"i32:37809\"]],[\"invoke\",4165,0,\"load8_u\",[\"i32:39998\"]],[\"invoke\",4176,0,\"load8_u\",[\"i32:42187\"]],[\"invoke\",4187,0,\"load8_u\",[\"i32:44376\"]],[\"invoke\",4197,0,\"load8_u\",[\"i32:46366\"]],[\"invoke\",4208,0,\"load8_u\",[\"i32:48555\"]],[\"invoke\",4219,0,\"load8_u\",[\"i32:50744\"]],[\"invoke\",4230,0,\"load8_u\",[\"i32:52933\"]],[\"invoke\",4241,0,\"load8_u\",[\"i32:55122\"]],[\"invoke\",4252,0,\"load8_u\",[\"i32:57311\"]],[\"invoke\",4263,0,\"load8_u\",[\"i32:59500\"]],[\"invoke\",4274,0,\"load8_u\",[\"i32:61441\"]],[\"invoke\",4285,0,\"load8_u\",[\"i32:61452\"]],[\"invoke\",4295,0,\"load8_u\",[\"i32:61908\"]],[\"invoke\",4306,0,\"load8_u\",[\"i32:64097\"]],[\"module\",4763,92],[\"invoke\",4786,0,\"checkRange\",[\"i32:20\",\"i32:65536\",\"i32:0\"]],[\"module\",4789,93],[\"module\",4815,94],[\"module\",4821,95],[\"module\",4827,96],[\"module\",4833,97],[\"module\",4839,98],[\"invoke\",4861,0,\"checkRange\",[\"i32:32768\",\"i32:65536\",\"i32:170\"]],[\"module\",4863,99],[\"module\",4869,100],[\"module\",4875,101],[\"module\",4881,102],[\"module\",4887,103],[\"module\",4893,104],[\"module\",4899,105],[\"invoke\",5123,0,\"checkRange\",[\"i32:2132\",\"i32:2827\",\"i32:10\"]],[\"invoke\",5145,0,\"checkRange\",[\"i32:5691\",\"i32:6552\",\"i32:83\"]],[\"invoke\",5167,0,\"checkRange\",[\"i32:10087\",\"i32:10206\",\"i32:5\"]],[\"invoke\",5189,0,\"checkRange\",[\"i32:13114\",\"i32:14133\",\"i32:59\"]],[\"invoke\",5209,0,\"checkRange\",[\"i32:15871\",\"i32:15998\",\"i32:95\"]],[\"invoke\",5231,0,\"checkRange\",[\"i32:18553\",\"i32:18744\",\"i32:42\"]],[\"invoke\",5253,0,\"checkRange\",[\"i32:21468\",\"i32:21492\",\"i32:93\"]],[\"invoke\",5275,0,\"checkRange\",[\"i32:24348\",\"i32:24515\",\"i32:92\"]],[\"invoke\",5297,0,\"checkRange\",[\"i32:27503\",\"i32:27573\",\"i32:77\"]],[\"invoke\",5319,0,\"checkRange\",[\"i32:31379\",\"i32:31403\",\"i32:57\"]],[\"invoke\",5341,0,\"checkRange\",[\"i32:33056\",\"i32:33660\",\"i32:89\"]],[\"invoke\",5363,0,\"checkRange\",[\"i32:35648\",\"i32:36547\",\"i32:83\"]],[\"invoke\",5385,0,\"checkRange\",[\"i32:39187\",\"i32:39859\",\"i32:92\"]],[\"invoke\",5405,0,\"checkRange\",[\"i32:42945\",\"i32:43115\",\"i32:77\"]],[\"invoke\",5427,0,\"checkRange\",[\"i32:45575\",\"i32:45640\",\"i32:77\"]],[\"invoke\",5449,0,\"checkRange\",[\"i32:47951\",\"i32:48632\",\"i32:100\"]],[\"invoke\",5471,0,\"checkRange\",[\"i32:50370\",\"i32:51358\",\"i32:92\"]],[\"invoke\",5493,0,\"checkRange\",[\"i32:55198\",\"i32:55389\",\"i32:42\"]],[\"invoke\",5515,0,\"checkRange\",[\"i32:58516\",\"i32:58835\",\"i32:95\"]],[\"invoke\",5537,0,\"checkRange\",[\"i32:60777\",\"i32:60834\",\"i32:76\"]],[\"invoke\",5559,0,\"checkRange\",[\"i32:63525\",\"i32:63605\",\"i32:74\"]],[\"file\",\"memory_fill.wast\"],[\"module\",6,106],[\"invoke\",22,0,\"test\",[]],[\"invoke\",24,0,\"checkRange\",[\"i32:0\",\"i32:65280\",\"i32:0\"]],[\"invoke\",26,0,\"checkRange\",[\"i32:65280\",\"i32:65536\",\"i32:85\"]],[\"module\",28,107],[\"invoke\",44,0,\"test\",[]],[\"module\",46,108],[\"invoke\",62,0,\"test\",[]],[\"module\",64,109],[\"invoke\",80,0,\"test\",[]],[\"invoke\",82,0,\"checkRange\",[\"i32:0\",\"i32:65536\",\"i32:0\"]],[\"module\",84,110],[\"invoke\",100,0,\"test\",[]],[\"module\",102,111],[\"invoke\",118,0,\"test\",[]],[\"module\",120,112],[\"invoke\",136,0,\"test\",[]],[\"invoke\",138,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"invoke\",140,0,\"checkRange\",[\"i32:1\",\"i32:65535\",\"i32:170\"]],[\"invoke\",142,0,\"checkRange\",[\"i32:65535\",\"i32:65536\",\"i32:0\"]],[\"module\",145,113],[\"invoke\",162,0,\"test\",[]],[\"invoke\",164,0,\"checkRange\",[\"i32:0\",\"i32:18\",\"i32:0\"]],[\"invoke\",166,0,\"checkRange\",[\"i32:18\",\"i32:21\",\"i32:85\"]],[\"invoke\",168,0,\"checkRange\",[\"i32:21\",\"i32:25\",\"i32:170\"]],[\"invoke\",170,0,\"checkRange\",[\"i32:25\",\"i32:28\",\"i32:85\"]],[\"invoke\",172,0,\"checkRange\",[\"i32:28\",\"i32:65536\",\"i32:0\"]],[\"module\",621,114],[\"invoke\",638,0,\"run\",[\"i32:65280\",\"i32:37\",\"i32:512\"]],[\"invoke\",641,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",643,114],[\"invoke\",660,0,\"run\",[\"i32:65279\",\"i32:37\",\"i32:514\"]],[\"invoke\",663,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",665,114],[\"invoke\",682,0,\"run\",[\"i32:65279\",\"i32:37\",\"i32:4294967295\"]],[\"invoke\",685,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"file\",\"memory_init.wast\"],[\"module\",6,115],[\"invoke\",17,0,\"test\",[]],[\"invoke\",19,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",20,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",21,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",22,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",23,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",24,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",25,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",26,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",27,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",28,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",29,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",30,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",31,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",32,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",33,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",34,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",35,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",36,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",37,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",38,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",39,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",40,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",41,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",42,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",43,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",44,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",45,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",46,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",47,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",48,0,\"load8_u\",[\"i32:29\"]],[\"module\",50,116],[\"invoke\",61,0,\"test\",[]],[\"invoke\",63,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",64,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",65,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",66,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",67,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",68,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",69,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",70,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",71,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",72,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",73,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",74,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",75,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",76,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",77,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",78,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",79,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",80,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",81,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",82,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",83,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",84,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",85,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",86,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",87,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",88,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",89,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",90,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",91,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",92,0,\"load8_u\",[\"i32:29\"]],[\"module\",94,117],[\"invoke\",105,0,\"test\",[]],[\"invoke\",107,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",108,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",109,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",110,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",111,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",112,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",113,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",114,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",115,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",116,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",117,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",118,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",119,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",120,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",121,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",122,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",123,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",124,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",125,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",126,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",127,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",128,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",129,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",130,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",131,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",132,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",133,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",134,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",135,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",136,0,\"load8_u\",[\"i32:29\"]],[\"module\",138,118],[\"invoke\",157,0,\"test\",[]],[\"invoke\",159,0,\"load8_u\",[\"i32:0\"]],[\"invoke\",160,0,\"load8_u\",[\"i32:1\"]],[\"invoke\",161,0,\"load8_u\",[\"i32:2\"]],[\"invoke\",162,0,\"load8_u\",[\"i32:3\"]],[\"invoke\",163,0,\"load8_u\",[\"i32:4\"]],[\"invoke\",164,0,\"load8_u\",[\"i32:5\"]],[\"invoke\",165,0,\"load8_u\",[\"i32:6\"]],[\"invoke\",166,0,\"load8_u\",[\"i32:7\"]],[\"invoke\",167,0,\"load8_u\",[\"i32:8\"]],[\"invoke\",168,0,\"load8_u\",[\"i32:9\"]],[\"invoke\",169,0,\"load8_u\",[\"i32:10\"]],[\"invoke\",170,0,\"load8_u\",[\"i32:11\"]],[\"invoke\",171,0,\"load8_u\",[\"i32:12\"]],[\"invoke\",172,0,\"load8_u\",[\"i32:13\"]],[\"invoke\",173,0,\"load8_u\",[\"i32:14\"]],[\"invoke\",174,0,\"load8_u\",[\"i32:15\"]],[\"invoke\",175,0,\"load8_u\",[\"i32:16\"]],[\"invoke\",176,0,\"load8_u\",[\"i32:17\"]],[\"invoke\",177,0,\"load8_u\",[\"i32:18\"]],[\"invoke\",178,0,\"load8_u\",[\"i32:19\"]],[\"invoke\",179,0,\"load8_u\",[\"i32:20\"]],[\"invoke\",180,0,\"load8_u\",[\"i32:21\"]],[\"invoke\",181,0,\"load8_u\",[\"i32:22\"]],[\"invoke\",182,0,\"load8_u\",[\"i32:23\"]],[\"invoke\",183,0,\"load8_u\",[\"i32:24\"]],[\"invoke\",184,0,\"load8_u\",[\"i32:25\"]],[\"invoke\",185,0,\"load8_u\",[\"i32:26\"]],[\"invoke\",186,0,\"load8_u\",[\"i32:27\"]],[\"invoke\",187,0,\"load8_u\",[\"i32:28\"]],[\"invoke\",188,0,\"load8_u\",[\"i32:29\"]],[\"module\",203,119],[\"invoke\",209,0,\"test\",[]],[\"module\",211,120],[\"invoke\",217,0,\"test\",[]],[\"module\",219,121],[\"invoke\",224,0,\"test\",[]],[\"module\",240,122],[\"invoke\",246,0,\"test\",[]],[\"module\",248,123],[\"invoke\",253,0,\"test\",[]],[\"module\",255,124],[\"invoke\",260,0,\"test\",[]],[\"module\",262,125],[\"invoke\",267,0,\"test\",[]],[\"module\",269,126],[\"invoke\",274,0,\"test\",[]],[\"module\",276,127],[\"invoke\",281,0,\"test\",[]],[\"module\",283,128],[\"invoke\",288,0,\"test\",[]],[\"module\",290,129],[\"invoke\",295,0,\"test\",[]],[\"module\",297,130],[\"invoke\",302,0,\"test\",[]],[\"module\",304,131],[\"invoke\",309,0,\"test\",[]],[\"module\",815,132],[\"invoke\",833,0,\"run\",[\"i32:65528\",\"i32:16\"]],[\"invoke\",836,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",838,132],[\"invoke\",856,0,\"run\",[\"i32:65527\",\"i32:16\"]],[\"invoke\",859,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",861,132],[\"invoke\",879,0,\"run\",[\"i32:65472\",\"i32:30\"]],[\"invoke\",882,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",884,132],[\"invoke\",902,0,\"run\",[\"i32:65473\",\"i32:31\"]],[\"invoke\",905,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",907,133],[\"invoke\",925,0,\"run\",[\"i32:65528\",\"i32:4294967040\"]],[\"invoke\",928,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",930,133],[\"invoke\",948,0,\"run\",[\"i32:0\",\"i32:4294967292\"]],[\"invoke\",951,0,\"checkRange\",[\"i32:0\",\"i32:1\",\"i32:0\"]],[\"module\",954,134],[\"file\",\"data.wast\"],[\"module\",5,135],[\"module\",39,136],[\"module\",43,137],[\"module\",48,138],[\"module\",56,139],[\"module\",66,140],[\"module\",71,141],[\"module\",77,140],[\"module\",82,141],[\"module\",100,142],[\"module\",105,143],[\"module\",111,144],[\"module\",116,145],[\"module\",120,146],[\"module\",125,147],[\"module\",130,148],[\"module\",135,145],[\"module\",139,146],[\"module\",144,147],[\"module\",149,149],[\"module\",154,150],[\"module\",159,151],[\"module\",165,152],[\"module\",171,153],[\"module\",176,154],[\"instantiate\",184,155],[\"instantiate\",192,156],[\"instantiate\",200,157],[\"instantiate\",207,158],[\"instantiate\",214,159],[\"instantiate\",231,160],[\"instantiate\",240,161],[\"instantiate\",247,162],[\"instantiate\",255,163],[\"instantiate\",263,164],[\"instantiate\",271,165],[\"instantiate\",278,166],[\"instantiate\",286,167],[\"instantiate\",293,168]]"; diff --git a/tests/corpus/webassembly-spec-memory/main.mjs b/tests/corpus/webassembly-spec-memory/main.mjs new file mode 100644 index 000000000..f45ccc584 --- /dev/null +++ b/tests/corpus/webassembly-spec-memory/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (16 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-spec-table/data.mjs b/tests/corpus/webassembly-spec-table/data.mjs new file mode 100644 index 000000000..d6334cb9e --- /dev/null +++ b/tests/corpus/webassembly-spec-table/data.mjs @@ -0,0 +1,205 @@ +/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * table_copy.wast, table_fill.wast, table_get.wast, table_grow.wast, table_init.wast, table_set.wast, table_size.wast, elem.wast, ref_func.wast, ref_is_null.wast, ref_null.wast, exports.wast, linking.wast. Do not edit. */ +export const modules = [ + "AGFzbQEAAAABBQFgAAF/AwYFAAAAAAAHHwUDZWYwAAADZWYxAAEDZWYyAAIDZWYzAAMDZWY0AAQKGgUEAEEACwQAQQELBABBAgsEAEEDCwQAQQQL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCi4IBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwMAAQsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BAkED/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBD0EC/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BGUED/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRRBFkEE/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBAUED/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBDEEH/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQxBCkEH/A4AAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgIBQQMLAAQBAwEEAgFBCwsABQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBAEEU/A4BAAsHACAAEQAACwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCi4IBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwMAAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BAkED/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBD0EC/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQ1BGUED/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRRBFkEE/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQRlBAUED/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBDEEH/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQxBCkEH/A4BAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwkIAAAAAAABAgIECQJwAR4ecAEeHgceAwR0ZXN0AAoIY2hlY2tfdDAACwhjaGVja190MQAMCToGAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYAQQMLBAEDAQQAQQsLBQYDAgUHCjcIBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwwAQQpBAEEU/A4AAQsHACAAEQABCwcAIAARAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEF+QQFBAvwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBBvwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQX5BAvwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQ9BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQ9BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR5BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR9BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQR5BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQR9BAPwOAAAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEF+QQFBAvwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBBvwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQX5BAvwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQRlBAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQ9BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQ9BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR5BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQR9BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQR5BAPwOAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEeHgcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQR9BAPwOAQAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEACwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ8BAEEACwkAAQIDBAUGBwgKZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw==", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEYCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ8BAEEXCwkAAQIDBAUGBwgKZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw==", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEELCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCQ4BAEEVCwgAAQIDBAUGBwpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBQFwASBAB2QSAmYwAAACZjEAAQJmMgACAmYzAAMCZjQABAJmNQAFAmY2AAYCZjcABwJmOAAIAmY5AAkDZjEwAAoDZjExAAsDZjEyAAwDZjEzAA0DZjE0AA4DZjE1AA8EdGVzdAAQA3J1bgARCREBAEEVCwsAAQIDBAUGBwgJCgpmEgQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLBABBCgsEAEELCwQAQQwLBABBDQsEAEEOCwQAQQ8LBwAgABEAAAsMACAAIAEgAvwOAAAL", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBwFwAYABgAEHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFwEAQfAACxAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIAAgASAC/A4AAAs=", + "AGFzbQEAAAABEANgAAF/YAF/AX9gA39/fwADExIAAAAAAAAAAAAAAAAAAAAAAQIEBwFwAYABgAEHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFgEAQQALEAABAgMEBQYHCAkKCwwNDg8KZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgACABIAL8DgAACw==", + "AGFzbQEAAAABDAJgA39vfwBgAX8BbwMEAwAAAQQEAW8ACgccAwRmaWxsAAALZmlsbC1hYmJyZXYAAQNnZXQAAgogAwsAIAAgASAC/BEACwsAIAAgASAC/BEACwYAIAAlAAs=", + "AGFzbQEAAAABFwVgAABgAW8AYAF/AW9gAX8BcGABfwF/AwYFAAECAwQEBwJvAAJwAAMHOAQEaW5pdAABDWdldC1leHRlcm5yZWYAAgtnZXQtZnVuY3JlZgADD2lzX251bGwtZnVuY3JlZgAECQkBAgFBAQsAAQAKKwUCAAsQAEEBIAAmAEECQQElASYBCwYAIAAlAAsGACAAJQELBwAgABAD0Qs=", + "AGFzbQEAAAABFQRgAX8Bb2ACf28AYAJ/bwF/YAABfwMGBQABAgIDBAQBbwAABykFA2dldAAAA3NldAABBGdyb3cAAgtncm93LWFiYnJldgADBHNpemUABAorBQYAIAAlAAsIACAAIAEmAAsJACABIAD8DwALCQAgASAA/A8ACwUA/BAACw==", + "AGFzbQEAAAABBQFgAAF/AwIBAAQEAXAAEAcIAQRncm93AAAJBQEDAAEACgsBCQDSAEFw/A8ACw==", + "AGFzbQEAAAABBgFgAX8BfwMCAQAEBAFvAAAHCAEEZ3JvdwAACgsBCQDQbyAA/A8ACw==", + "AGFzbQEAAAABBgFgAX8BfwMCAQAEBQFvAQAKBwgBBGdyb3cAAAoLAQkA0G8gAPwPAAs=", + "AGFzbQEAAAABDAJgAX8Bf2ACf38BcAMDAgABBAQBcAAKBxsCBGdyb3cAABBjaGVjay10YWJsZS1udWxsAAEJBQEDAAEBCj0CCQDQcCAA/A8ACzEBAXDSASECAkADQCAAJQAhAiAC0UUNASAAIAFPDQEgAEEBaiEAIAAgAU0NAAsLIAIL", + "AGFzbQEAAAABBQFgAAF/AwIBAAQEAXAAAQcQAgV0YWJsZQEABGdyb3cAAAoLAQkA0HBBAfwPAAs=", + "AGFzbQEAAAABBQFgAAF/AhcBC2dyb3duLXRhYmxlBXRhYmxlAXAAAgMCAQAHEAIFdGFibGUBAARncm93AAAKCwEJANBwQQH8DwAL", + "AGFzbQEAAAABBQFgAAF/AiABFGdyb3duLWltcG9ydGVkLXRhYmxlBXRhYmxlAXAAAwMCAQAHCAEEc2l6ZQAACgcBBQD8EAAL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgovBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEHQQBBBPwMAQALBwAgABEAAAs=", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgovBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEPQQFBA/wMAwALBwAgABEAAAs=", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCSMEAEECCwQDAQQBAQAEAgcBCABBDAsFBwUCAwYBAAUFCQIHBgpxBwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQtOAEEHQQBBBPwMAQD8DQFBD0EBQQP8DAMA/A0DQRRBD0EF/A4AAEEVQR1BAfwOAABBGEEKQQH8DgAAQQ1BC0EE/A4AAEETQRRBBfwOAAALBwAgABEAAAs=", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKLwcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBB0EAQQT8DAEBCwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKLwcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBD0EBQQP8DAMBCwcAIAARAAEL", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8CKQUBYQNlZjAAAAFhA2VmMQAAAWEDZWYyAAABYQNlZjMAAAFhA2VmNAAAAwgHAAAAAAABAgQJAnABHh5wAR4eBxACBHRlc3QACgVjaGVjawALCScEAgFBAgsABAMBBAEBAAQCBwEIAgFBDAsABQcFAgMGAQAFBQkCBwYKcQcEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLTgBBB0EAQQT8DAEB/A0BQQ9BAUED/AwDAfwNA0EUQQ9BBfwOAQFBFUEdQQH8DgEBQRhBCkEB/A4BAUENQQtBBPwOAQFBE0EUQQX8DgEBCwcAIAARAAEL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCjkLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsFAPwNAgs=", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQFBAfwMAgAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkoLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsWAEEMQQFBAfwMAQBBFUEBQQH8DAEACw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCjwLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsIAPwNAfwNAQs=", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkMLBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsPAPwNAUEMQQFBAfwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQBBBfwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQJBA/wMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEcQQFBA/wMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQRBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEMQQVBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQJBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQJBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEeQQRBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJIwQAQQILBAMBBAEBAAQCBwEIAEEMCwUHBQIDBgEABQUJAgcGCkALBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsMAEEfQQVBAPwMAQAL", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBGkEBQQP8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBDEEEQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBDEEFQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHEECQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHUECQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHEEEQQD8DAEBCw==", + "AGFzbQEAAAABCAJgAAF/YAAAAwwLAAAAAAAAAAAAAAEECQJwAR4ecAEcHAcIAQR0ZXN0AAoJJwQCAUECCwAEAwEEAQEABAIHAQgCAUEMCwAFBwUCAwYBAAUFCQIHBgpACwQAQQALBABBAQsEAEECCwQAQQMLBABBBAsEAEEFCwQAQQYLBABBBwsEAEEICwQAQQkLDABBHUEFQQD8DAEBCw==", + "AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABIEAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBACAB/AwAAAs=", + "AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQHAXABoAHAAgdkEgJmMAAAAmYxAAECZjIAAgJmMwADAmY0AAQCZjUABQJmNgAGAmY3AAcCZjgACAJmOQAJA2YxMAAKA2YxMQALA2YxMgAMA2YxMwANA2YxNAAOA2YxNQAPBHRlc3QAEANydW4AEQkUAQEAEAABAgMEBQYHCAkKCwwNDg8KZhIEAEEACwQAQQELBABBAgsEAEEDCwQAQQQLBABBBQsEAEEGCwQAQQcLBABBCAsEAEEJCwQAQQoLBABBCwsEAEEMCwQAQQ0LBABBDgsEAEEPCwcAIAARAAALDAAgAEEAIAH8DAAACw==", + "AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABQEAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBACAB/AwAAAs=", + "AGFzbQEAAAABDwNgAAF/YAF/AX9gAn9/AAMTEgAAAAAAAAAAAAAAAAAAAAABAgQFAXABEBAHZBICZjAAAAJmMQABAmYyAAICZjMAAwJmNAAEAmY1AAUCZjYABgJmNwAHAmY4AAgCZjkACQNmMTAACgNmMTEACwNmMTIADANmMTMADQNmMTQADgNmMTUADwR0ZXN0ABADcnVuABEJFAEBABAAAQIDBAUGBwgJCgsMDQ4PCmYSBABBAAsEAEEBCwQAQQILBABBAwsEAEEECwQAQQULBABBBgsEAEEHCwQAQQgLBABBCQsEAEEKCwQAQQsLBABBDAsEAEENCwQAQQ4LBABBDwsHACAAEQAACwwAIABBCCAB/AwAAAs=", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAABCcQBQQEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAEAAAoOAQwAQQBBAEEA/AxAAAs=", + "AGFzbQEAAAABIgdgAABgAX8Bb2ABfwFwYAJ/bwBgAn9wAGACf38AYAF/AX8DCAcAAQIDBAUGBAcCbwABcAACB2IGDWdldC1leHRlcm5yZWYAAQtnZXQtZnVuY3JlZgACDXNldC1leHRlcm5yZWYAAwtzZXQtZnVuY3JlZgAEEHNldC1mdW5jcmVmLWZyb20ABQ9pc19udWxsLWZ1bmNyZWYABgkJAQIBQQELAAEACjcHAgALBgAgACUACwYAIAAlAQsIACAAIAEmAAsIACAAIAEmAQsKACAAIAElASYBCwcAIAAQAtEL", + "AGFzbQEAAAABCQJgAAF/YAF/AAMJCAAAAAABAQEBBA8EbwAAbwABbwEAAm8BAwgHUQgHc2l6ZS10MAAAB3NpemUtdDEAAQdzaXplLXQyAAIHc2l6ZS10MwADB2dyb3ctdDAABAdncm93LXQxAAUHZ3Jvdy10MgAGB2dyb3ctdDMABwpFCAUA/BAACwUA/BABCwUA/BACCwUA/BADCwoA0G8gAPwPABoLCgDQbyAA/A8BGgsKANBvIAD8DwIaCwoA0G8gAPwPAxoL", + "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgmkAz0BAAAFcATSAAvSAAvQcAvSAQsBAAABAAQAAAEBAQAABXAE0gAL0gAL0HAL0gELAQAAAQAEAAABAQBBAAsABEEACwLSAAvQcAsAQQALAABBAAsCAAEAQQALAABBAAsCAAEAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAEQQALAtIAC9BwCwBBAAsABEEACwLSAAvQcAsAQQALAABBAAsCAAEAQQALAABBAAsCAAEAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAAAQQALAARBAAsC0gAL0HALAEEACwIAAABBAAsCAAADAAAHcATSAAvSAAvQcAvSAQsDAAADAAQAAAEBAwAAB3AE0gAL0gAL0HAL0gELAwAAAwAEAAABAQoHAgIACwIACw==", + "AGFzbQEAAAABBAFgAAADAwIAAAQFAXABAwMJDwEEQQALA9IAC9BwC9IBCwoHAgIACwIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEACwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBAAsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCR8FAEEACwEAAEEDCwEAAEEHCwEAAEEFCwEAAEEDCwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkfBQBBCQsBAABBAwsBAABBBwsBAABBAwsBAABBBQsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAACGAEIc3BlY3Rlc3QKZ2xvYmFsX2kzMgN/AAMCAQAEBQFwAOgHCQcBACMACwEACgQBAgAL", + "AGFzbQEAAAABBQFgAAF/AwUEAAAAAAQEAXAACgcTAgZjYWxsLTcAAgZjYWxsLTkAAwkNAgBBBwsBAABBCQsBAQodBAUAQcEACwUAQcIACwcAQQcRAAALBwBBCREAAAs=", + "AGFzbQEAAAABBQFgAAF/AwUEAAAAAAQEAXAACwcTAgZjYWxsLTcAAgZjYWxsLTkAAwkXAgRBBgsC0HAL0gALBEEJCwLSAQvQcAsKHQQFAEHBAAsFAEHCAAsHAEEHEQAACwcAQQkRAAAL", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEJCwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBCQsBAAoEAQIACw==", + "AGFzbQEAAAAEBAFwAAAJBgEAQQALAA==", + "AGFzbQEAAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAACQYBAEEACwA=", + "AGFzbQEAAAAEBQFwAQAACQYBAEEACwA=", + "AGFzbQEAAAAEBAFwABQJBgEAQRQLAA==", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAAAwIBAAkHAQBBAAsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAZAMCAQAJBwEAQQALAQAKBAECAAs=", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAAAwIBAAkHAQBBAQsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAACFQEIc3BlY3Rlc3QFdGFibGUBcAEAHgMCAQAJBwEAQQELAQAKBAECAAs=", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAACQcBAEEACwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAADAgEABAUBcAEAAAkHAQBBAAsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAUBcAEAAQkHAQBBAAsBAAoEAQIACw==", + "AGFzbQEAAAAEBAFwAAAJBgEAQQELAA==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEEKCwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBCgsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAUBcAEKFAkHAQBBCgsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEF/CwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBfwsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABAQBcAAKCQcBAEF2CwEACgQBAgAL", + "AGFzbQEAAAABBAFgAAACFAEIc3BlY3Rlc3QFdGFibGUBcAAKAwIBAAkHAQBBdgsBAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQRpbml0AAEJBwEAQQALAQAKEQICAAsMAEEAQQBBAfwMAAAL", + "AGFzbQEAAAABBAFgAAADAwIAAAQEAXAACgcIAQRpbml0AAEJBQEDAAEAChECAgALDABBAEEAQQH8DAAACw==", + "AGFzbQEAAAABBQFgAAF/AwQDAAAABAQBcAAKBxQBEGNhbGwtb3ZlcndyaXR0ZW4AAgkNAgBBCQsBAABBCQsBAQoVAwUAQcEACwUAQcIACwcAQQkRAAAL", + "AGFzbQEAAAABBQFgAAF/AhQBCHNwZWN0ZXN0BXRhYmxlAXAACgMEAwAAAAccARhjYWxsLW92ZXJ3cml0dGVuLWVsZW1lbnQAAgkNAgBBCQsBAABBCQsBAQoVAwUAQcEACwUAQcIACwcAQQkRAAAL", + "AGFzbQEAAAABBQFgAAF/AwYFAAAAAAAEBAFwAAoHKwQMc2hhcmVkLXRhYmxlAQAGY2FsbC03AAIGY2FsbC04AAMGY2FsbC05AAQJDQIAQQgLAQAAQQkLAQEKJQUFAEHBAAsFAEHCAAsHAEEHEQAACwcAQQgRAAALBwBBCREAAAs=", + "AGFzbQEAAAABBQFgAAF/AhoBB21vZHVsZTEMc2hhcmVkLXRhYmxlAXAACgMDAgAACQ0CAEEHCwEAAEEICwEBCg0CBQBBwwALBQBBxAAL", + "AGFzbQEAAAABBQFgAAF/AhoBB21vZHVsZTEMc2hhcmVkLXRhYmxlAXAACgMDAgAACQ0CAEEICwEAAEEJCwEBCg0CBQBBxQALBQBBxgAL", + "AGFzbQEAAAABCwJgAX8Bb2ACf28AAwMCAAEEBAFvAAIHFQMFdGFibGUBAANnZXQAAANzZXQAAQoRAgYAIAAlAAsIACAAIAEmAAs=", + "AGFzbQEAAAACFAEIZXhwb3J0ZXIFdGFibGUBbwACCQsBBgBBAAtvAdBvCw==", + "AGFzbQEAAAABBQFgAAF/AwIBAAYGAXAA0gALBwUBAWYDAAoGAQQAQSoL", + "AGFzbQEAAAABBQFgAAF/Ag4BB21vZHVsZTQBZgNwAAMCAQAEBAFwAAoHFgESY2FsbF9pbXBvcnRlZF9lbGVtAAAJCQEEQQALASMACwoJAQcAQQARAAAL", + "AGFzbQEAAAABBgFgAX8BfwMCAQAHBQEBZgAACgYBBAAgAAs=", + "AGFzbQEAAAABDQNgAX8Bf2AAAGAAAX8CBwEBTQFmAAADDw4AAQEBAQECAgIBAQAAAAQEAXAAAQYaBXAA0gALcADSAQtwAdIAC3AA0gMLcADSBAsHUAgJaXNfbnVsbC1mAAcJaXNfbnVsbC1nAAgJaXNfbnVsbC12AAkFc2V0LWYACgVzZXQtZwALBmNhbGwtZgAMBmNhbGwtZwANBmNhbGwtdgAOCRADAwACAwUDAAIEBgMAAgABCm4OBwAgAEEBagsIANIFGtIGGgsCAAsCAAsCAAsCAAsFANIA0QsFANIB0QsFACMC0QsGANIAJAILBgDSASQCCw8AQQDSACYAIABBABEAAAsPAEEA0gEmACAAQQARAAALDwBBACMCJgAgAEEAEQAACw==", + "AGFzbQEAAAABBAFgAAADCAcAAAAAAAAABAQBcAABBgYBcADSAAsHBQEBZgABCRUEAEEACwECAEEACwEDAQABBAEAAQUKIwcCAAsCAAsCAAsCAAsCAAsCAAsPANIA0gHSAtID0gTSBQ8L", + "AGFzbQEAAAABFwVgAXABf2ABbwF/YAAAYAFvAGABfwF/AwgHAAECAwIEBAQHAnAAAm8AAgdHBgdmdW5jcmVmAAAJZXh0ZXJucmVmAAEEaW5pdAADBmRlaW5pdAAEDGZ1bmNyZWYtZWxlbQAFDmV4dGVybnJlZi1lbGVtAAYJBwEAQQELAQIKOgcFACAA0QsFACAA0QsCAAsIAEEBIAAmAQsOAEEB0HAmAEEB0G8mAQsIACAAJQAQAAsIACAAJQEQAQs=", + "AGFzbQEAAAABCQJgAAFvYAABcAMDAgABBgsCbwDQbwtwANBwCwcXAglleHRlcm5yZWYAAAdmdW5jcmVmAAEKCwIEANBvCwQA0HAL", + "AGFzbQEAAAABBAFgAAADAgEABwUBAWEAAAoEAQIACw==", + "AGFzbQEAAAABBAFgAAADAgEABwkCAWEAAAFiAAAKBAECAAs=", + "AGFzbQEAAAABBAFgAAADAwIAAAcJAgFhAAABYgABCgcCAgALAgAL", + "AGFzbQEAAAABBAFgAAADAgEABw0DAWEAAAFiAAABYwAACgQBAgAL", + "AGFzbQEAAAABBQFgAX8AAwIBAAcJAgFhAAABYgAACgQBAgAL", + "AGFzbQEAAAABBgFgAX8BfwMCAQAHBQEBZQAACgoBCAAgAEEBag8L", + "AGFzbQEAAAA=", + "AGFzbQEAAAABBQFgAAF/AwIBAAcNAwFhAAABYgAAAWMAAAoGAQQAQSoL", + "AGFzbQEAAAAGBgF/AEEACwcFAQFhAwA=", + "AGFzbQEAAAAGBgF/AEEACwcJAgFhAwABYgMA", + "AGFzbQEAAAAGCwJ/AEEAC38AQQALBwkCAWEDAAFiAwE=", + "AGFzbQEAAAAGBgF/AEEqCwcFAQFlAwA=", + "AGFzbQEAAAAEBAFwAAAHBQEBYQEA", + "AGFzbQEAAAAEBAFwAAAHCQIBYQEAAWIBAA==", + "AGFzbQEAAAAEBwJwAABwAAAHCQIBYQEAAWIBAQ==", + "AGFzbQEAAAAEBQFwAQABBwUBAWEBAA==", + "AGFzbQEAAAAFAwEAAAcFAQFhAgA=", + "AGFzbQEAAAAFAwEAAAcJAgFhAgABYgIA", + "AGFzbQEAAAAFBAEBAAEHBQEBYQIA", + "AGFzbQEAAAABBQFgAAF/AwMCAAAHCAEEY2FsbAAACgsCBAAQAQsEAEECCw==", + "AGFzbQEAAAABBQFgAAF/AgsBAk1mBGNhbGwAAAMEAwAAAAchAwdNZi5jYWxsAAAMY2FsbCBNZi5jYWxsAAEEY2FsbAACChADBAAQAAsEABADCwQAQQML", + "AGFzbQEAAAABBQFgAX8AAhYBCHNwZWN0ZXN0CXByaW50X2kzMgAABwkBBXByaW50AAA=", + "AGFzbQEAAAABBQFgAX4AAhQBCnJlZXhwb3J0X2YFcHJpbnQAAA==", + "AGFzbQEAAAABBgFgAX8BfwIUAQpyZWV4cG9ydF9mBXByaW50AAA=", + "AGFzbQEAAAABCQJgAAF/YAF/AAMEAwAAAQYMAn8AQSoLfwFBjgELBy0FBGdsb2IDAANnZXQAAAhtdXRfZ2xvYgMBB2dldF9tdXQAAQdzZXRfbXV0AAIKEgMEACMACwQAIwELBgAgACQBCw==", + "AGFzbQEAAAABCQJgAAF/YAF/AAI+BQJNZwRnbG9iA38AAk1nCG11dF9nbG9iA38BAk1nA2dldAAAAk1nB2dldF9tdXQAAAJNZwdzZXRfbXV0AAEDAgEABgYBfwBBKwsHSQcHTWcuZ2xvYgMABk1nLmdldAAABGdsb2IDAgNnZXQAAwtNZy5tdXRfZ2xvYgMBCk1nLmdldF9tdXQAAQpNZy5zZXRfbXV0AAIKBgEEACMCCw==", + "AGFzbQEAAAACEAECTWcIbXV0X2dsb2IDfwA=", + "AGFzbQEAAAACDAECTWcEZ2xvYgN/AQ==", + "AGFzbQEAAAAGFQRwANBwC3AB0HALbwDQbwtvAdBvCwc9BAxnLWNvbnN0LWZ1bmMDAApnLXZhci1mdW5jAwEOZy1jb25zdC1leHRlcm4DAgxnLXZhci1leHRlcm4DAw==", + "AGFzbQEAAAACYQQHTXJlZl9leAxnLWNvbnN0LWZ1bmMDcAAHTXJlZl9leA5nLWNvbnN0LWV4dGVybgNvAAdNcmVmX2V4CmctdmFyLWZ1bmMDcAEHTXJlZl9leAxnLXZhci1leHRlcm4DbwE=", + "AGFzbQEAAAACGwEHTXJlZl9leA5nLWNvbnN0LWV4dGVybgNwAA==", + "AGFzbQEAAAACGQEHTXJlZl9leAxnLWNvbnN0LWZ1bmMDbwA=", + "AGFzbQEAAAACFwEHTXJlZl9leApnLXZhci1mdW5jA28B", + "AGFzbQEAAAACGQEHTXJlZl9leAxnLXZhci1leHRlcm4DcAE=", + "AGFzbQEAAAABDQNgAAF/YAAAYAF/AX8DBAMAAAIEBAFwAAoHEgMDdGFiAQABaAABBGNhbGwAAgkKAQBBAgsEAAAAAAoTAwQAQQQLBABBfAsHACAAEQAACw==", + "AGFzbQEAAAABDQNgAABgAAF/YAF/AX8CEgICTXQEY2FsbAACAk10AWgAAQMEAwECAgQFAXABBQUHIQMHTXQuY2FsbAAADGNhbGwgTXQuY2FsbAADBGNhbGwABAkLAQBBAAsFAgICAQAKFQMEAEEFCwYAIAAQAAsHACAAEQEACw==", + "AGFzbQEAAAABCgJgAAF/YAF/AX8CEwICTXQBaAAAAk10A3RhYgFwAAUDAwIAAQcIAQRjYWxsAAIJCAEAQQELAgEACg4CBABBBgsHACAAEQAACw==", + "AGFzbQEAAAABBAFgAAACDAECTXQDdGFiAXAAAAMCAQAJBwEAQQkLAQAKBAECAAs=", + "AGFzbQEAAAAGBgF/AEEFCwcFAQFnAwA=", + "AGFzbQEAAAACCQECRzEBZwN/AAYGAX8AIwALBwUBAWcDAQ==", + "AGFzbQEAAAABBAFgAAACDAECTXQDdGFiAXAAAAMCAQAJBwEAQQoLAQAKBAECAAs=", + "AGFzbQEAAAABBQFgAAF/AhYCAk10A3RhYgFwAAoCTXQDbWVtAgABAwIBAAkNAgBBBwsBAABBCQsBAAoGAQQAQQAL", + "AGFzbQEAAAABBQFgAAF/AgwBAk10A3RhYgFwAAoDAgEACRECAEEHCwEAAEEICwUAAAAAAAoGAQQAQQAL", + "AGFzbQEAAAABBQFgAAF/AgwBAk10A3RhYgFwAAoDAgEABQMBAAEJBwEAQQcLAQAKBgEEAEEACwsJAQBBgIAECwFk", + "AGFzbQEAAAAEBwJwAAFvAAEHFQIGdC1mdW5jAQAIdC1leHRlcm4BAQ==", + "AGFzbQEAAAACLQIJTXRhYmxlX2V4BnQtZnVuYwFwAAEJTXRhYmxlX2V4CHQtZXh0ZXJuAW8AAQ==", + "AGFzbQEAAAACFgEJTXRhYmxlX2V4BnQtZnVuYwFvAAE=", + "AGFzbQEAAAACGAEJTXRhYmxlX2V4CHQtZXh0ZXJuAXAAAQ==", + "AGFzbQEAAAABBgFgAX8BfwMCAQAFBAEBAQUHDgIDbWVtAgAEbG9hZAAACgkBBwAgAC0AAAsLEAEAQQoLCgABAgMEBQYHCAk=", + "AGFzbQEAAAABBgFgAX8BfwILAQJNbQRsb2FkAAADAgEABQMBAAEHEgIHTW0ubG9hZAAABGxvYWQAAQoJAQcAIAAtAAALCwwBAEEKCwbw8fLz9PU=", + "AGFzbQEAAAABBgFgAX8BfwILAQJNbQNtZW0CAAEDAgEABwgBBGxvYWQAAAoJAQcAIAAtAAALCw4BAEEFCwigoaKjpKWmpw==", + "AGFzbQEAAAACCwECTW0DbWVtAgAACwkBAEH//wMLAWE=", + "AGFzbQEAAAACCwECTW0DbWVtAgAACwkBAEGAgAQLAWE=", + "AGFzbQEAAAABBgFgAX8BfwIMAQJNbQNtZW0CAQEIAwIBAAcIAQRncm93AAAKCAEGACAAQAAL", + "AGFzbQEAAAABBAFgAAACJwMIc3BlY3Rlc3QFcHJpbnQAAAJNbQNtZW0CAAECTW0DdGFiAXAAAAsJAQBBAAsDYWJj", + "AGFzbQEAAAACCwECTW0DbWVtAgABCyICAEEACwNhYmMAQfb/EwsSenp6enp6enp6enp6enp6enp6", + "AGFzbQEAAAABBAFgAAACCwECTW0DbWVtAgABAwIBAAQEAXAAAAkHAQBBAAsBAAoEAQIACwsJAQBBAAsDYWJj", + "AGFzbQEAAAABBQFgAAF/AwMCAAAEBAFwAAEFAwEAAQcxBAZtZW1vcnkCAAV0YWJsZQEADWdldCBtZW1vcnlbMF0AAAxnZXQgdGFibGVbMF0AAQoRAgcAQQAtAAALBwBBABEAAAs=", + "AGFzbQEAAAABCAJgAAF/YAAAAhsCAk1zBm1lbW9yeQIAAQJNcwV0YWJsZQFwAAEDAwIAAQgBAQkHAQBBAAsBAAoMAgYAQa29AwsDAAALCwsBAEEACwVoZWxsbw==", +]; +export const script = "[[\"file\",\"table_copy.wast\"],[\"module\",6,0],[\"register\",\"a\"],[\"module\",15,1],[\"invoke\",45,0,\"test\",[]],[\"invoke\",50,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",54,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",58,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",62,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",66,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",71,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",75,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",79,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",83,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",87,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",92,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",96,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",100,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",104,0,\"check_t1\",[\"i32:28\"]],[\"module\",107,2],[\"invoke\",139,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",143,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",148,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",152,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",156,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",160,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",164,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",169,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",173,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",177,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",181,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",185,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",190,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",194,0,\"check_t1\",[\"i32:26\"]],[\"module\",199,3],[\"invoke\",229,0,\"test\",[]],[\"invoke\",233,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",237,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",241,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",246,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",250,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",254,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",258,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",262,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",267,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",271,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",275,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",279,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",283,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",288,0,\"check_t1\",[\"i32:28\"]],[\"module\",291,4],[\"invoke\",323,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",327,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",331,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",335,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",339,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",344,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",348,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",352,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",356,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",360,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",365,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",369,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",373,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",377,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",381,0,\"check_t1\",[\"i32:29\"]],[\"module\",383,5],[\"invoke\",417,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",421,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",425,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",429,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",433,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",437,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",442,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",446,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",450,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",454,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",458,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",463,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",467,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",471,0,\"check_t1\",[\"i32:27\"]],[\"module\",475,6],[\"invoke\",506,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",510,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",515,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",519,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",523,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",527,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",531,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",535,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",540,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",544,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",548,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",552,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",556,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",561,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",565,0,\"check_t1\",[\"i32:29\"]],[\"module\",567,7],[\"invoke\",600,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",604,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",608,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",613,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",617,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",621,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",625,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",629,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",633,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",638,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",642,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",646,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",650,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",654,0,\"check_t1\",[\"i32:26\"]],[\"module\",659,8],[\"invoke\",690,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",694,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",698,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",702,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",706,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",711,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",715,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",719,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",723,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",727,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",731,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",736,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",740,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",744,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",748,0,\"check_t1\",[\"i32:28\"]],[\"module\",751,9],[\"invoke\",783,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",788,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",792,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",796,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",800,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",804,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",809,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",813,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",817,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",821,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",825,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",829,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",834,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",838,0,\"check_t1\",[\"i32:26\"]],[\"module\",843,10],[\"invoke\",873,0,\"test\",[]],[\"invoke\",877,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",881,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",886,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",890,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",894,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",898,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",902,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",907,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",911,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",915,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",919,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",923,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",927,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",932,0,\"check_t1\",[\"i32:28\"]],[\"module\",935,11],[\"invoke\",967,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",971,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",975,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",979,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",984,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",988,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",992,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",996,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1000,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1005,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1009,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1013,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",1017,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1021,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",1025,0,\"check_t1\",[\"i32:29\"]],[\"module\",1027,12],[\"invoke\",1061,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",1065,0,\"check_t0\",[\"i32:7\"]],[\"invoke\",1069,0,\"check_t0\",[\"i32:11\"]],[\"invoke\",1073,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",1077,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",1082,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",1086,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",1090,0,\"check_t1\",[\"i32:2\"]],[\"invoke\",1094,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",1098,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",1103,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",1107,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",1111,0,\"check_t1\",[\"i32:23\"]],[\"invoke\",1115,0,\"check_t1\",[\"i32:27\"]],[\"module\",1119,13],[\"invoke\",1150,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",1154,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",1159,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",1163,0,\"check_t0\",[\"i32:13\"]],[\"invoke\",1167,0,\"check_t0\",[\"i32:17\"]],[\"invoke\",1171,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",1175,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",1180,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1184,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1188,0,\"check_t1\",[\"i32:8\"]],[\"invoke\",1192,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",1196,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",1201,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1205,0,\"check_t1\",[\"i32:25\"]],[\"invoke\",1209,0,\"check_t1\",[\"i32:29\"]],[\"module\",1211,14],[\"invoke\",1244,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",1248,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",1252,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",1257,0,\"check_t0\",[\"i32:15\"]],[\"invoke\",1261,0,\"check_t0\",[\"i32:19\"]],[\"invoke\",1265,0,\"check_t0\",[\"i32:23\"]],[\"invoke\",1269,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",1273,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",1278,0,\"check_t1\",[\"i32:6\"]],[\"invoke\",1282,0,\"check_t1\",[\"i32:10\"]],[\"invoke\",1286,0,\"check_t1\",[\"i32:14\"]],[\"invoke\",1290,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",1294,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",1299,0,\"check_t1\",[\"i32:27\"]],[\"module\",1303,15],[\"invoke\",1334,0,\"check_t0\",[\"i32:0\"]],[\"invoke\",1338,0,\"check_t0\",[\"i32:4\"]],[\"invoke\",1342,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",1346,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",1350,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",1355,0,\"check_t0\",[\"i32:21\"]],[\"invoke\",1359,0,\"check_t0\",[\"i32:25\"]],[\"invoke\",1363,0,\"check_t0\",[\"i32:29\"]],[\"invoke\",1367,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",1371,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",1376,0,\"check_t1\",[\"i32:12\"]],[\"invoke\",1380,0,\"check_t1\",[\"i32:16\"]],[\"invoke\",1384,0,\"check_t1\",[\"i32:20\"]],[\"invoke\",1388,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",1392,0,\"check_t1\",[\"i32:28\"]],[\"module\",1395,16],[\"invoke\",1428,0,\"check_t0\",[\"i32:2\"]],[\"invoke\",1432,0,\"check_t0\",[\"i32:6\"]],[\"invoke\",1436,0,\"check_t0\",[\"i32:10\"]],[\"invoke\",1440,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",1444,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",1448,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",1453,0,\"check_t0\",[\"i32:27\"]],[\"invoke\",1457,0,\"check_t1\",[\"i32:1\"]],[\"invoke\",1461,0,\"check_t1\",[\"i32:5\"]],[\"invoke\",1465,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1469,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1474,0,\"check_t1\",[\"i32:18\"]],[\"invoke\",1478,0,\"check_t1\",[\"i32:22\"]],[\"invoke\",1482,0,\"check_t1\",[\"i32:26\"]],[\"module\",1487,17],[\"invoke\",1517,0,\"test\",[]],[\"invoke\",1521,0,\"check_t0\",[\"i32:3\"]],[\"invoke\",1526,0,\"check_t0\",[\"i32:8\"]],[\"invoke\",1530,0,\"check_t0\",[\"i32:12\"]],[\"invoke\",1534,0,\"check_t0\",[\"i32:16\"]],[\"invoke\",1538,0,\"check_t0\",[\"i32:20\"]],[\"invoke\",1542,0,\"check_t0\",[\"i32:24\"]],[\"invoke\",1546,0,\"check_t0\",[\"i32:28\"]],[\"invoke\",1551,0,\"check_t1\",[\"i32:3\"]],[\"invoke\",1555,0,\"check_t1\",[\"i32:7\"]],[\"invoke\",1559,0,\"check_t1\",[\"i32:11\"]],[\"invoke\",1563,0,\"check_t1\",[\"i32:15\"]],[\"invoke\",1567,0,\"check_t1\",[\"i32:19\"]],[\"invoke\",1572,0,\"check_t1\",[\"i32:24\"]],[\"invoke\",1576,0,\"check_t1\",[\"i32:28\"]],[\"module\",1579,18],[\"invoke\",1611,0,\"check_t0\",[\"i32:1\"]],[\"invoke\",1615,0,\"check_t0\",[\"i32:5\"]],[\"invoke\",1619,0,\"check_t0\",[\"i32:9\"]],[\"invoke\",1624,0,\"check_t0\",[\"i32:14\"]],[\"invoke\",1628,0,\"check_t0\",[\"i32:18\"]],[\"invoke\",1632,0,\"check_t0\",[\"i32:22\"]],[\"invoke\",1636,0,\"check_t0\",[\"i32:26\"]],[\"invoke\",1640,0,\"check_t1\",[\"i32:0\"]],[\"invoke\",1644,0,\"check_t1\",[\"i32:4\"]],[\"invoke\",1649,0,\"check_t1\",[\"i32:9\"]],[\"invoke\",1653,0,\"check_t1\",[\"i32:13\"]],[\"invoke\",1657,0,\"check_t1\",[\"i32:17\"]],[\"invoke\",1661,0,\"check_t1\",[\"i32:21\"]],[\"invoke\",1665,0,\"check_t1\",[\"i32:25\"]],[\"module\",1671,19],[\"invoke\",1694,0,\"test\",[]],[\"module\",1696,20],[\"module\",1721,21],[\"module\",1746,22],[\"module\",1771,23],[\"invoke\",1794,0,\"test\",[]],[\"module\",1796,24],[\"module\",1821,25],[\"module\",1846,26],[\"module\",1871,27],[\"invoke\",1894,0,\"test\",[]],[\"module\",1896,28],[\"module\",1921,29],[\"module\",1946,30],[\"module\",1971,31],[\"invoke\",1994,0,\"test\",[]],[\"module\",1996,32],[\"module\",2021,33],[\"module\",2046,34],[\"module\",2071,35],[\"invoke\",2094,0,\"test\",[]],[\"module\",2096,36],[\"module\",2121,37],[\"module\",2146,38],[\"module\",2171,39],[\"module\",2196,40],[\"invoke\",2219,0,\"test\",[]],[\"module\",2221,41],[\"invoke\",2251,0,\"test\",[\"i32:2\"]],[\"invoke\",2255,0,\"test\",[\"i32:6\"]],[\"invoke\",2259,0,\"test\",[\"i32:10\"]],[\"invoke\",2263,0,\"test\",[\"i32:14\"]],[\"invoke\",2267,0,\"test\",[\"i32:18\"]],[\"invoke\",2272,0,\"test\",[\"i32:23\"]],[\"invoke\",2276,0,\"test\",[\"i32:27\"]],[\"invoke\",2280,0,\"test\",[\"i32:31\"]],[\"module\",2282,42],[\"invoke\",2312,0,\"test\",[\"i32:2\"]],[\"invoke\",2316,0,\"test\",[\"i32:6\"]],[\"invoke\",2321,0,\"test\",[\"i32:11\"]],[\"invoke\",2325,0,\"test\",[\"i32:15\"]],[\"invoke\",2329,0,\"test\",[\"i32:19\"]],[\"invoke\",2333,0,\"test\",[\"i32:23\"]],[\"invoke\",2337,0,\"test\",[\"i32:27\"]],[\"module\",2343,43],[\"invoke\",2369,0,\"run\",[\"i32:0\",\"i32:24\",\"i32:16\"]],[\"invoke\",2374,0,\"test\",[\"i32:3\"]],[\"invoke\",2378,0,\"test\",[\"i32:7\"]],[\"invoke\",2382,0,\"test\",[\"i32:11\"]],[\"invoke\",2386,0,\"test\",[\"i32:15\"]],[\"invoke\",2390,0,\"test\",[\"i32:19\"]],[\"invoke\",2395,0,\"test\",[\"i32:24\"]],[\"invoke\",2399,0,\"test\",[\"i32:28\"]],[\"module\",2404,44],[\"invoke\",2430,0,\"run\",[\"i32:0\",\"i32:23\",\"i32:15\"]],[\"invoke\",2435,0,\"test\",[\"i32:3\"]],[\"invoke\",2439,0,\"test\",[\"i32:7\"]],[\"invoke\",2444,0,\"test\",[\"i32:12\"]],[\"invoke\",2448,0,\"test\",[\"i32:16\"]],[\"invoke\",2452,0,\"test\",[\"i32:20\"]],[\"invoke\",2456,0,\"test\",[\"i32:24\"]],[\"invoke\",2460,0,\"test\",[\"i32:28\"]],[\"module\",2465,45],[\"invoke\",2493,0,\"test\",[\"i32:0\"]],[\"invoke\",2497,0,\"test\",[\"i32:4\"]],[\"invoke\",2501,0,\"test\",[\"i32:8\"]],[\"invoke\",2505,0,\"test\",[\"i32:12\"]],[\"invoke\",2509,0,\"test\",[\"i32:16\"]],[\"invoke\",2513,0,\"test\",[\"i32:20\"]],[\"invoke\",2518,0,\"test\",[\"i32:25\"]],[\"invoke\",2522,0,\"test\",[\"i32:29\"]],[\"module\",2526,43],[\"invoke\",2554,0,\"test\",[\"i32:0\"]],[\"invoke\",2558,0,\"test\",[\"i32:4\"]],[\"invoke\",2562,0,\"test\",[\"i32:8\"]],[\"invoke\",2567,0,\"test\",[\"i32:13\"]],[\"invoke\",2571,0,\"test\",[\"i32:17\"]],[\"invoke\",2575,0,\"test\",[\"i32:21\"]],[\"invoke\",2579,0,\"test\",[\"i32:25\"]],[\"invoke\",2583,0,\"test\",[\"i32:29\"]],[\"module\",2587,46],[\"invoke\",2616,0,\"test\",[\"i32:1\"]],[\"invoke\",2620,0,\"test\",[\"i32:5\"]],[\"invoke\",2624,0,\"test\",[\"i32:9\"]],[\"invoke\",2628,0,\"test\",[\"i32:13\"]],[\"invoke\",2632,0,\"test\",[\"i32:17\"]],[\"invoke\",2636,0,\"test\",[\"i32:21\"]],[\"invoke\",2641,0,\"test\",[\"i32:26\"]],[\"invoke\",2645,0,\"test\",[\"i32:30\"]],[\"module\",2648,43],[\"invoke\",2677,0,\"test\",[\"i32:1\"]],[\"invoke\",2681,0,\"test\",[\"i32:5\"]],[\"invoke\",2685,0,\"test\",[\"i32:9\"]],[\"invoke\",2690,0,\"test\",[\"i32:14\"]],[\"invoke\",2694,0,\"test\",[\"i32:18\"]],[\"invoke\",2698,0,\"test\",[\"i32:22\"]],[\"invoke\",2702,0,\"test\",[\"i32:26\"]],[\"invoke\",2706,0,\"test\",[\"i32:30\"]],[\"module\",2709,47],[\"invoke\",2739,0,\"test\",[\"i32:2\"]],[\"invoke\",2743,0,\"test\",[\"i32:6\"]],[\"invoke\",2747,0,\"test\",[\"i32:10\"]],[\"invoke\",2751,0,\"test\",[\"i32:14\"]],[\"invoke\",2755,0,\"test\",[\"i32:18\"]],[\"invoke\",2759,0,\"test\",[\"i32:22\"]],[\"invoke\",2764,0,\"test\",[\"i32:27\"]],[\"invoke\",2768,0,\"test\",[\"i32:31\"]],[\"module\",2770,48],[\"invoke\",2800,0,\"test\",[\"i32:2\"]],[\"invoke\",2804,0,\"test\",[\"i32:6\"]],[\"invoke\",2808,0,\"test\",[\"i32:10\"]],[\"invoke\",2813,0,\"test\",[\"i32:15\"]],[\"invoke\",2817,0,\"test\",[\"i32:19\"]],[\"invoke\",2821,0,\"test\",[\"i32:23\"]],[\"invoke\",2825,0,\"test\",[\"i32:27\"]],[\"invoke\",2829,0,\"test\",[\"i32:31\"]],[\"invoke\",2834,0,\"test\",[\"i32:36\"]],[\"invoke\",2838,0,\"test\",[\"i32:40\"]],[\"invoke\",2842,0,\"test\",[\"i32:44\"]],[\"invoke\",2846,0,\"test\",[\"i32:48\"]],[\"invoke\",2850,0,\"test\",[\"i32:52\"]],[\"invoke\",2854,0,\"test\",[\"i32:56\"]],[\"invoke\",2859,0,\"test\",[\"i32:61\"]],[\"invoke\",2863,0,\"test\",[\"i32:65\"]],[\"invoke\",2867,0,\"test\",[\"i32:69\"]],[\"invoke\",2871,0,\"test\",[\"i32:73\"]],[\"invoke\",2875,0,\"test\",[\"i32:77\"]],[\"invoke\",2880,0,\"test\",[\"i32:82\"]],[\"invoke\",2884,0,\"test\",[\"i32:86\"]],[\"invoke\",2888,0,\"test\",[\"i32:90\"]],[\"invoke\",2892,0,\"test\",[\"i32:94\"]],[\"invoke\",2896,0,\"test\",[\"i32:98\"]],[\"invoke\",2901,0,\"test\",[\"i32:103\"]],[\"invoke\",2905,0,\"test\",[\"i32:107\"]],[\"invoke\",2909,0,\"test\",[\"i32:111\"]],[\"invoke\",2913,0,\"test\",[\"i32:115\"]],[\"invoke\",2917,0,\"test\",[\"i32:119\"]],[\"invoke\",2921,0,\"test\",[\"i32:123\"]],[\"module\",2927,49],[\"invoke\",2953,0,\"run\",[\"i32:112\",\"i32:0\",\"i32:4294967264\"]],[\"invoke\",2958,0,\"test\",[\"i32:3\"]],[\"invoke\",2962,0,\"test\",[\"i32:7\"]],[\"invoke\",2966,0,\"test\",[\"i32:11\"]],[\"invoke\",2970,0,\"test\",[\"i32:15\"]],[\"invoke\",2975,0,\"test\",[\"i32:20\"]],[\"invoke\",2979,0,\"test\",[\"i32:24\"]],[\"invoke\",2983,0,\"test\",[\"i32:28\"]],[\"invoke\",2987,0,\"test\",[\"i32:32\"]],[\"invoke\",2991,0,\"test\",[\"i32:36\"]],[\"invoke\",2996,0,\"test\",[\"i32:41\"]],[\"invoke\",3000,0,\"test\",[\"i32:45\"]],[\"invoke\",3004,0,\"test\",[\"i32:49\"]],[\"invoke\",3008,0,\"test\",[\"i32:53\"]],[\"invoke\",3012,0,\"test\",[\"i32:57\"]],[\"invoke\",3016,0,\"test\",[\"i32:61\"]],[\"invoke\",3021,0,\"test\",[\"i32:66\"]],[\"invoke\",3025,0,\"test\",[\"i32:70\"]],[\"invoke\",3029,0,\"test\",[\"i32:74\"]],[\"invoke\",3033,0,\"test\",[\"i32:78\"]],[\"invoke\",3037,0,\"test\",[\"i32:82\"]],[\"invoke\",3042,0,\"test\",[\"i32:87\"]],[\"invoke\",3046,0,\"test\",[\"i32:91\"]],[\"invoke\",3050,0,\"test\",[\"i32:95\"]],[\"invoke\",3054,0,\"test\",[\"i32:99\"]],[\"invoke\",3058,0,\"test\",[\"i32:103\"]],[\"invoke\",3063,0,\"test\",[\"i32:108\"]],[\"invoke\",3067,0,\"test\",[\"i32:112\"]],[\"invoke\",3071,0,\"test\",[\"i32:116\"]],[\"invoke\",3075,0,\"test\",[\"i32:120\"]],[\"invoke\",3079,0,\"test\",[\"i32:124\"]],[\"file\",\"table_fill.wast\"],[\"module\",1,50],[\"invoke\",17,0,\"get\",[\"i32:1\"]],[\"invoke\",18,0,\"get\",[\"i32:2\"]],[\"invoke\",19,0,\"get\",[\"i32:3\"]],[\"invoke\",20,0,\"get\",[\"i32:4\"]],[\"invoke\",21,0,\"get\",[\"i32:5\"]],[\"invoke\",23,0,\"fill\",[\"i32:2\",\"externref:1\",\"i32:3\"]],[\"invoke\",24,0,\"get\",[\"i32:1\"]],[\"invoke\",25,0,\"get\",[\"i32:2\"]],[\"invoke\",26,0,\"get\",[\"i32:3\"]],[\"invoke\",27,0,\"get\",[\"i32:4\"]],[\"invoke\",28,0,\"get\",[\"i32:5\"]],[\"invoke\",30,0,\"fill\",[\"i32:4\",\"externref:2\",\"i32:2\"]],[\"invoke\",31,0,\"get\",[\"i32:3\"]],[\"invoke\",32,0,\"get\",[\"i32:4\"]],[\"invoke\",33,0,\"get\",[\"i32:5\"]],[\"invoke\",34,0,\"get\",[\"i32:6\"]],[\"invoke\",36,0,\"fill\",[\"i32:4\",\"externref:3\",\"i32:0\"]],[\"invoke\",37,0,\"get\",[\"i32:3\"]],[\"invoke\",38,0,\"get\",[\"i32:4\"]],[\"invoke\",39,0,\"get\",[\"i32:5\"]],[\"invoke\",41,0,\"fill\",[\"i32:8\",\"externref:4\",\"i32:2\"]],[\"invoke\",42,0,\"get\",[\"i32:7\"]],[\"invoke\",43,0,\"get\",[\"i32:8\"]],[\"invoke\",44,0,\"get\",[\"i32:9\"]],[\"invoke\",46,0,\"fill-abbrev\",[\"i32:9\",\"externref:null\",\"i32:1\"]],[\"invoke\",47,0,\"get\",[\"i32:8\"]],[\"invoke\",48,0,\"get\",[\"i32:9\"]],[\"invoke\",50,0,\"fill\",[\"i32:10\",\"externref:5\",\"i32:0\"]],[\"invoke\",51,0,\"get\",[\"i32:9\"]],[\"invoke\",54,0,\"fill\",[\"i32:8\",\"externref:6\",\"i32:3\"]],[\"invoke\",57,0,\"get\",[\"i32:7\"]],[\"invoke\",58,0,\"get\",[\"i32:8\"]],[\"invoke\",59,0,\"get\",[\"i32:9\"]],[\"invoke\",62,0,\"fill\",[\"i32:11\",\"externref:null\",\"i32:0\"]],[\"invoke\",67,0,\"fill\",[\"i32:11\",\"externref:null\",\"i32:10\"]],[\"file\",\"table_get.wast\"],[\"module\",1,51],[\"invoke\",24,0,\"init\",[\"externref:1\"]],[\"invoke\",26,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",27,0,\"get-externref\",[\"i32:1\"]],[\"invoke\",29,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",30,0,\"is_null-funcref\",[\"i32:1\"]],[\"invoke\",31,0,\"is_null-funcref\",[\"i32:2\"]],[\"invoke\",33,0,\"get-externref\",[\"i32:2\"]],[\"invoke\",34,0,\"get-funcref\",[\"i32:3\"]],[\"invoke\",35,0,\"get-externref\",[\"i32:4294967295\"]],[\"invoke\",36,0,\"get-funcref\",[\"i32:4294967295\"]],[\"file\",\"table_grow.wast\"],[\"module\",1,52],[\"invoke\",16,0,\"size\",[]],[\"invoke\",17,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",18,0,\"get\",[\"i32:0\"]],[\"invoke\",20,0,\"grow\",[\"i32:1\",\"externref:null\"]],[\"invoke\",21,0,\"size\",[]],[\"invoke\",22,0,\"get\",[\"i32:0\"]],[\"invoke\",23,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",24,0,\"get\",[\"i32:0\"]],[\"invoke\",25,0,\"set\",[\"i32:1\",\"externref:2\"]],[\"invoke\",26,0,\"get\",[\"i32:1\"]],[\"invoke\",28,0,\"grow-abbrev\",[\"i32:4\",\"externref:3\"]],[\"invoke\",29,0,\"size\",[]],[\"invoke\",30,0,\"get\",[\"i32:0\"]],[\"invoke\",31,0,\"set\",[\"i32:0\",\"externref:2\"]],[\"invoke\",32,0,\"get\",[\"i32:0\"]],[\"invoke\",33,0,\"get\",[\"i32:1\"]],[\"invoke\",34,0,\"get\",[\"i32:4\"]],[\"invoke\",35,0,\"set\",[\"i32:4\",\"externref:4\"]],[\"invoke\",36,0,\"get\",[\"i32:4\"]],[\"invoke\",37,0,\"set\",[\"i32:5\",\"externref:2\"]],[\"invoke\",38,0,\"get\",[\"i32:5\"]],[\"module\",42,53],[\"invoke\",50,0,\"grow\",[]],[\"module\",53,54],[\"invoke\",60,0,\"grow\",[\"i32:0\"]],[\"invoke\",61,0,\"grow\",[\"i32:1\"]],[\"invoke\",62,0,\"grow\",[\"i32:0\"]],[\"invoke\",63,0,\"grow\",[\"i32:2\"]],[\"invoke\",64,0,\"grow\",[\"i32:800\"]],[\"module\",67,55],[\"invoke\",74,0,\"grow\",[\"i32:0\"]],[\"invoke\",75,0,\"grow\",[\"i32:1\"]],[\"invoke\",76,0,\"grow\",[\"i32:1\"]],[\"invoke\",77,0,\"grow\",[\"i32:2\"]],[\"invoke\",78,0,\"grow\",[\"i32:6\"]],[\"invoke\",79,0,\"grow\",[\"i32:0\"]],[\"invoke\",80,0,\"grow\",[\"i32:1\"]],[\"invoke\",81,0,\"grow\",[\"i32:65536\"]],[\"module\",84,56],[\"invoke\",106,0,\"check-table-null\",[\"i32:0\",\"i32:9\"]],[\"invoke\",107,0,\"grow\",[\"i32:10\"]],[\"invoke\",108,0,\"check-table-null\",[\"i32:0\",\"i32:19\"]],[\"module\",111,57,\"$Tgt\"],[\"register\",\"grown-table\",\"$Tgt\"],[\"invoke\",116,\"$Tgt\",\"grow\",[]],[\"module\",117,58,\"$Tgit1\"],[\"register\",\"grown-imported-table\",\"$Tgit1\"],[\"invoke\",123,\"$Tgit1\",\"grow\",[]],[\"module\",124,59,\"$Tgit2\"],[\"invoke\",129,\"$Tgit2\",\"size\",[]],[\"file\",\"table_init.wast\"],[\"module\",6,0],[\"register\",\"a\"],[\"module\",15,60],[\"invoke\",41,0,\"test\",[]],[\"invoke\",43,0,\"check\",[\"i32:1\"]],[\"invoke\",45,0,\"check\",[\"i32:3\"]],[\"invoke\",47,0,\"check\",[\"i32:5\"]],[\"invoke\",48,0,\"check\",[\"i32:6\"]],[\"invoke\",50,0,\"check\",[\"i32:8\"]],[\"invoke\",52,0,\"check\",[\"i32:10\"]],[\"invoke\",53,0,\"check\",[\"i32:11\"]],[\"invoke\",55,0,\"check\",[\"i32:13\"]],[\"invoke\",57,0,\"check\",[\"i32:15\"]],[\"invoke\",58,0,\"check\",[\"i32:16\"]],[\"invoke\",60,0,\"check\",[\"i32:18\"]],[\"invoke\",62,0,\"check\",[\"i32:20\"]],[\"invoke\",64,0,\"check\",[\"i32:22\"]],[\"invoke\",65,0,\"check\",[\"i32:23\"]],[\"invoke\",67,0,\"check\",[\"i32:25\"]],[\"invoke\",69,0,\"check\",[\"i32:27\"]],[\"invoke\",70,0,\"check\",[\"i32:28\"]],[\"module\",73,61],[\"invoke\",99,0,\"test\",[]],[\"invoke\",101,0,\"check\",[\"i32:1\"]],[\"invoke\",102,0,\"check\",[\"i32:2\"]],[\"invoke\",104,0,\"check\",[\"i32:4\"]],[\"invoke\",106,0,\"check\",[\"i32:6\"]],[\"invoke\",107,0,\"check\",[\"i32:7\"]],[\"invoke\",109,0,\"check\",[\"i32:9\"]],[\"invoke\",111,0,\"check\",[\"i32:11\"]],[\"invoke\",113,0,\"check\",[\"i32:13\"]],[\"invoke\",114,0,\"check\",[\"i32:14\"]],[\"invoke\",116,0,\"check\",[\"i32:16\"]],[\"invoke\",118,0,\"check\",[\"i32:18\"]],[\"invoke\",119,0,\"check\",[\"i32:19\"]],[\"invoke\",121,0,\"check\",[\"i32:21\"]],[\"invoke\",123,0,\"check\",[\"i32:23\"]],[\"invoke\",124,0,\"check\",[\"i32:24\"]],[\"invoke\",126,0,\"check\",[\"i32:26\"]],[\"invoke\",128,0,\"check\",[\"i32:28\"]],[\"invoke\",129,0,\"check\",[\"i32:29\"]],[\"module\",131,62],[\"invoke\",166,0,\"check\",[\"i32:0\"]],[\"invoke\",168,0,\"check\",[\"i32:2\"]],[\"invoke\",170,0,\"check\",[\"i32:4\"]],[\"invoke\",171,0,\"check\",[\"i32:5\"]],[\"invoke\",173,0,\"check\",[\"i32:7\"]],[\"invoke\",175,0,\"check\",[\"i32:9\"]],[\"invoke\",176,0,\"check\",[\"i32:10\"]],[\"invoke\",178,0,\"check\",[\"i32:12\"]],[\"invoke\",180,0,\"check\",[\"i32:14\"]],[\"invoke\",181,0,\"check\",[\"i32:15\"]],[\"invoke\",183,0,\"check\",[\"i32:17\"]],[\"invoke\",185,0,\"check\",[\"i32:19\"]],[\"invoke\",186,0,\"check\",[\"i32:20\"]],[\"invoke\",188,0,\"check\",[\"i32:22\"]],[\"invoke\",190,0,\"check\",[\"i32:24\"]],[\"invoke\",192,0,\"check\",[\"i32:26\"]],[\"invoke\",193,0,\"check\",[\"i32:27\"]],[\"invoke\",195,0,\"check\",[\"i32:29\"]],[\"module\",197,63],[\"invoke\",224,0,\"check\",[\"i32:0\"]],[\"invoke\",225,0,\"check\",[\"i32:1\"]],[\"invoke\",227,0,\"check\",[\"i32:3\"]],[\"invoke\",229,0,\"check\",[\"i32:5\"]],[\"invoke\",230,0,\"check\",[\"i32:6\"]],[\"invoke\",232,0,\"check\",[\"i32:8\"]],[\"invoke\",234,0,\"check\",[\"i32:10\"]],[\"invoke\",235,0,\"check\",[\"i32:11\"]],[\"invoke\",237,0,\"check\",[\"i32:13\"]],[\"invoke\",239,0,\"check\",[\"i32:15\"]],[\"invoke\",241,0,\"check\",[\"i32:17\"]],[\"invoke\",242,0,\"check\",[\"i32:18\"]],[\"invoke\",244,0,\"check\",[\"i32:20\"]],[\"invoke\",246,0,\"check\",[\"i32:22\"]],[\"invoke\",247,0,\"check\",[\"i32:23\"]],[\"invoke\",249,0,\"check\",[\"i32:25\"]],[\"invoke\",251,0,\"check\",[\"i32:27\"]],[\"invoke\",252,0,\"check\",[\"i32:28\"]],[\"module\",255,64],[\"invoke\",281,0,\"test\",[]],[\"invoke\",283,0,\"check\",[\"i32:1\"]],[\"invoke\",284,0,\"check\",[\"i32:2\"]],[\"invoke\",286,0,\"check\",[\"i32:4\"]],[\"invoke\",288,0,\"check\",[\"i32:6\"]],[\"invoke\",290,0,\"check\",[\"i32:8\"]],[\"invoke\",291,0,\"check\",[\"i32:9\"]],[\"invoke\",293,0,\"check\",[\"i32:11\"]],[\"invoke\",295,0,\"check\",[\"i32:13\"]],[\"invoke\",296,0,\"check\",[\"i32:14\"]],[\"invoke\",298,0,\"check\",[\"i32:16\"]],[\"invoke\",300,0,\"check\",[\"i32:18\"]],[\"invoke\",301,0,\"check\",[\"i32:19\"]],[\"invoke\",303,0,\"check\",[\"i32:21\"]],[\"invoke\",305,0,\"check\",[\"i32:23\"]],[\"invoke\",306,0,\"check\",[\"i32:24\"]],[\"invoke\",308,0,\"check\",[\"i32:26\"]],[\"invoke\",310,0,\"check\",[\"i32:28\"]],[\"module\",313,65],[\"invoke\",347,0,\"test\",[]],[\"invoke\",348,0,\"check\",[\"i32:0\"]],[\"invoke\",350,0,\"check\",[\"i32:2\"]],[\"invoke\",352,0,\"check\",[\"i32:4\"]],[\"invoke\",353,0,\"check\",[\"i32:5\"]],[\"invoke\",355,0,\"check\",[\"i32:7\"]],[\"invoke\",357,0,\"check\",[\"i32:9\"]],[\"invoke\",358,0,\"check\",[\"i32:10\"]],[\"invoke\",360,0,\"check\",[\"i32:12\"]],[\"invoke\",362,0,\"check\",[\"i32:14\"]],[\"invoke\",363,0,\"check\",[\"i32:15\"]],[\"invoke\",365,0,\"check\",[\"i32:17\"]],[\"invoke\",367,0,\"check\",[\"i32:19\"]],[\"invoke\",369,0,\"check\",[\"i32:21\"]],[\"invoke\",370,0,\"check\",[\"i32:22\"]],[\"invoke\",372,0,\"check\",[\"i32:24\"]],[\"invoke\",374,0,\"check\",[\"i32:26\"]],[\"invoke\",375,0,\"check\",[\"i32:27\"]],[\"invoke\",377,0,\"check\",[\"i32:29\"]],[\"module\",407,66],[\"module\",431,67],[\"invoke\",453,0,\"test\",[]],[\"module\",455,68],[\"invoke\",477,0,\"test\",[]],[\"module\",479,69],[\"module\",503,70],[\"invoke\",525,0,\"test\",[]],[\"module\",527,71],[\"module\",551,72],[\"invoke\",573,0,\"test\",[]],[\"module\",575,73],[\"invoke\",597,0,\"test\",[]],[\"module\",599,74],[\"module\",623,75],[\"invoke\",645,0,\"test\",[]],[\"module\",647,76],[\"module\",671,77],[\"invoke\",693,0,\"test\",[]],[\"module\",695,78],[\"module\",719,79],[\"invoke\",741,0,\"test\",[]],[\"module\",743,80],[\"invoke\",765,0,\"test\",[]],[\"module\",767,81],[\"module\",791,82],[\"invoke\",813,0,\"test\",[]],[\"module\",815,83],[\"module\",839,84],[\"invoke\",861,0,\"test\",[]],[\"module\",863,85],[\"invoke\",885,0,\"test\",[]],[\"module\",887,86],[\"module\",1478,87],[\"invoke\",1506,0,\"run\",[\"i32:24\",\"i32:16\"]],[\"invoke\",1508,0,\"test\",[\"i32:1\"]],[\"invoke\",1509,0,\"test\",[\"i32:2\"]],[\"invoke\",1511,0,\"test\",[\"i32:4\"]],[\"invoke\",1513,0,\"test\",[\"i32:6\"]],[\"invoke\",1514,0,\"test\",[\"i32:7\"]],[\"invoke\",1516,0,\"test\",[\"i32:9\"]],[\"invoke\",1518,0,\"test\",[\"i32:11\"]],[\"invoke\",1520,0,\"test\",[\"i32:13\"]],[\"invoke\",1521,0,\"test\",[\"i32:14\"]],[\"invoke\",1523,0,\"test\",[\"i32:16\"]],[\"invoke\",1525,0,\"test\",[\"i32:18\"]],[\"invoke\",1526,0,\"test\",[\"i32:19\"]],[\"invoke\",1528,0,\"test\",[\"i32:21\"]],[\"invoke\",1530,0,\"test\",[\"i32:23\"]],[\"invoke\",1531,0,\"test\",[\"i32:24\"]],[\"invoke\",1533,0,\"test\",[\"i32:26\"]],[\"invoke\",1535,0,\"test\",[\"i32:28\"]],[\"invoke\",1536,0,\"test\",[\"i32:29\"]],[\"invoke\",1538,0,\"test\",[\"i32:31\"]],[\"module\",1540,87],[\"invoke\",1569,0,\"test\",[\"i32:0\"]],[\"invoke\",1571,0,\"test\",[\"i32:2\"]],[\"invoke\",1572,0,\"test\",[\"i32:3\"]],[\"invoke\",1574,0,\"test\",[\"i32:5\"]],[\"invoke\",1576,0,\"test\",[\"i32:7\"]],[\"invoke\",1577,0,\"test\",[\"i32:8\"]],[\"invoke\",1579,0,\"test\",[\"i32:10\"]],[\"invoke\",1581,0,\"test\",[\"i32:12\"]],[\"invoke\",1582,0,\"test\",[\"i32:13\"]],[\"invoke\",1584,0,\"test\",[\"i32:15\"]],[\"invoke\",1586,0,\"test\",[\"i32:17\"]],[\"invoke\",1587,0,\"test\",[\"i32:18\"]],[\"invoke\",1589,0,\"test\",[\"i32:20\"]],[\"invoke\",1591,0,\"test\",[\"i32:22\"]],[\"invoke\",1593,0,\"test\",[\"i32:24\"]],[\"invoke\",1594,0,\"test\",[\"i32:25\"]],[\"invoke\",1596,0,\"test\",[\"i32:27\"]],[\"invoke\",1598,0,\"test\",[\"i32:29\"]],[\"invoke\",1599,0,\"test\",[\"i32:30\"]],[\"module\",1602,88],[\"invoke\",1630,0,\"run\",[\"i32:96\",\"i32:32\"]],[\"invoke\",1632,0,\"test\",[\"i32:1\"]],[\"invoke\",1633,0,\"test\",[\"i32:2\"]],[\"invoke\",1635,0,\"test\",[\"i32:4\"]],[\"invoke\",1637,0,\"test\",[\"i32:6\"]],[\"invoke\",1638,0,\"test\",[\"i32:7\"]],[\"invoke\",1640,0,\"test\",[\"i32:9\"]],[\"invoke\",1642,0,\"test\",[\"i32:11\"]],[\"invoke\",1644,0,\"test\",[\"i32:13\"]],[\"invoke\",1645,0,\"test\",[\"i32:14\"]],[\"invoke\",1647,0,\"test\",[\"i32:16\"]],[\"invoke\",1649,0,\"test\",[\"i32:18\"]],[\"invoke\",1650,0,\"test\",[\"i32:19\"]],[\"invoke\",1652,0,\"test\",[\"i32:21\"]],[\"invoke\",1654,0,\"test\",[\"i32:23\"]],[\"invoke\",1655,0,\"test\",[\"i32:24\"]],[\"invoke\",1657,0,\"test\",[\"i32:26\"]],[\"invoke\",1659,0,\"test\",[\"i32:28\"]],[\"invoke\",1660,0,\"test\",[\"i32:29\"]],[\"invoke\",1662,0,\"test\",[\"i32:31\"]],[\"invoke\",1664,0,\"test\",[\"i32:33\"]],[\"invoke\",1666,0,\"test\",[\"i32:35\"]],[\"invoke\",1667,0,\"test\",[\"i32:36\"]],[\"invoke\",1669,0,\"test\",[\"i32:38\"]],[\"invoke\",1671,0,\"test\",[\"i32:40\"]],[\"invoke\",1672,0,\"test\",[\"i32:41\"]],[\"invoke\",1674,0,\"test\",[\"i32:43\"]],[\"invoke\",1676,0,\"test\",[\"i32:45\"]],[\"invoke\",1677,0,\"test\",[\"i32:46\"]],[\"invoke\",1679,0,\"test\",[\"i32:48\"]],[\"invoke\",1681,0,\"test\",[\"i32:50\"]],[\"invoke\",1682,0,\"test\",[\"i32:51\"]],[\"invoke\",1684,0,\"test\",[\"i32:53\"]],[\"invoke\",1686,0,\"test\",[\"i32:55\"]],[\"invoke\",1688,0,\"test\",[\"i32:57\"]],[\"invoke\",1689,0,\"test\",[\"i32:58\"]],[\"invoke\",1691,0,\"test\",[\"i32:60\"]],[\"invoke\",1693,0,\"test\",[\"i32:62\"]],[\"invoke\",1694,0,\"test\",[\"i32:63\"]],[\"invoke\",1696,0,\"test\",[\"i32:65\"]],[\"invoke\",1698,0,\"test\",[\"i32:67\"]],[\"invoke\",1699,0,\"test\",[\"i32:68\"]],[\"invoke\",1701,0,\"test\",[\"i32:70\"]],[\"invoke\",1703,0,\"test\",[\"i32:72\"]],[\"invoke\",1704,0,\"test\",[\"i32:73\"]],[\"invoke\",1706,0,\"test\",[\"i32:75\"]],[\"invoke\",1708,0,\"test\",[\"i32:77\"]],[\"invoke\",1710,0,\"test\",[\"i32:79\"]],[\"invoke\",1711,0,\"test\",[\"i32:80\"]],[\"invoke\",1713,0,\"test\",[\"i32:82\"]],[\"invoke\",1715,0,\"test\",[\"i32:84\"]],[\"invoke\",1716,0,\"test\",[\"i32:85\"]],[\"invoke\",1718,0,\"test\",[\"i32:87\"]],[\"invoke\",1720,0,\"test\",[\"i32:89\"]],[\"invoke\",1721,0,\"test\",[\"i32:90\"]],[\"invoke\",1723,0,\"test\",[\"i32:92\"]],[\"invoke\",1725,0,\"test\",[\"i32:94\"]],[\"invoke\",1726,0,\"test\",[\"i32:95\"]],[\"invoke\",1728,0,\"test\",[\"i32:97\"]],[\"invoke\",1730,0,\"test\",[\"i32:99\"]],[\"invoke\",1732,0,\"test\",[\"i32:101\"]],[\"invoke\",1733,0,\"test\",[\"i32:102\"]],[\"invoke\",1735,0,\"test\",[\"i32:104\"]],[\"invoke\",1737,0,\"test\",[\"i32:106\"]],[\"invoke\",1738,0,\"test\",[\"i32:107\"]],[\"invoke\",1740,0,\"test\",[\"i32:109\"]],[\"invoke\",1742,0,\"test\",[\"i32:111\"]],[\"invoke\",1743,0,\"test\",[\"i32:112\"]],[\"invoke\",1745,0,\"test\",[\"i32:114\"]],[\"invoke\",1747,0,\"test\",[\"i32:116\"]],[\"invoke\",1748,0,\"test\",[\"i32:117\"]],[\"invoke\",1750,0,\"test\",[\"i32:119\"]],[\"invoke\",1752,0,\"test\",[\"i32:121\"]],[\"invoke\",1754,0,\"test\",[\"i32:123\"]],[\"invoke\",1755,0,\"test\",[\"i32:124\"]],[\"invoke\",1757,0,\"test\",[\"i32:126\"]],[\"invoke\",1759,0,\"test\",[\"i32:128\"]],[\"invoke\",1760,0,\"test\",[\"i32:129\"]],[\"invoke\",1762,0,\"test\",[\"i32:131\"]],[\"invoke\",1764,0,\"test\",[\"i32:133\"]],[\"invoke\",1765,0,\"test\",[\"i32:134\"]],[\"invoke\",1767,0,\"test\",[\"i32:136\"]],[\"invoke\",1769,0,\"test\",[\"i32:138\"]],[\"invoke\",1770,0,\"test\",[\"i32:139\"]],[\"invoke\",1772,0,\"test\",[\"i32:141\"]],[\"invoke\",1774,0,\"test\",[\"i32:143\"]],[\"invoke\",1776,0,\"test\",[\"i32:145\"]],[\"invoke\",1777,0,\"test\",[\"i32:146\"]],[\"invoke\",1779,0,\"test\",[\"i32:148\"]],[\"invoke\",1781,0,\"test\",[\"i32:150\"]],[\"invoke\",1782,0,\"test\",[\"i32:151\"]],[\"invoke\",1784,0,\"test\",[\"i32:153\"]],[\"invoke\",1786,0,\"test\",[\"i32:155\"]],[\"invoke\",1787,0,\"test\",[\"i32:156\"]],[\"invoke\",1789,0,\"test\",[\"i32:158\"]],[\"module\",1792,88],[\"invoke\",1820,0,\"run\",[\"i32:97\",\"i32:31\"]],[\"invoke\",1821,0,\"test\",[\"i32:0\"]],[\"invoke\",1823,0,\"test\",[\"i32:2\"]],[\"invoke\",1825,0,\"test\",[\"i32:4\"]],[\"invoke\",1827,0,\"test\",[\"i32:6\"]],[\"invoke\",1828,0,\"test\",[\"i32:7\"]],[\"invoke\",1830,0,\"test\",[\"i32:9\"]],[\"invoke\",1832,0,\"test\",[\"i32:11\"]],[\"invoke\",1833,0,\"test\",[\"i32:12\"]],[\"invoke\",1835,0,\"test\",[\"i32:14\"]],[\"invoke\",1837,0,\"test\",[\"i32:16\"]],[\"invoke\",1838,0,\"test\",[\"i32:17\"]],[\"invoke\",1840,0,\"test\",[\"i32:19\"]],[\"invoke\",1842,0,\"test\",[\"i32:21\"]],[\"invoke\",1843,0,\"test\",[\"i32:22\"]],[\"invoke\",1845,0,\"test\",[\"i32:24\"]],[\"invoke\",1847,0,\"test\",[\"i32:26\"]],[\"invoke\",1849,0,\"test\",[\"i32:28\"]],[\"invoke\",1850,0,\"test\",[\"i32:29\"]],[\"invoke\",1852,0,\"test\",[\"i32:31\"]],[\"invoke\",1854,0,\"test\",[\"i32:33\"]],[\"invoke\",1855,0,\"test\",[\"i32:34\"]],[\"invoke\",1857,0,\"test\",[\"i32:36\"]],[\"invoke\",1859,0,\"test\",[\"i32:38\"]],[\"invoke\",1860,0,\"test\",[\"i32:39\"]],[\"invoke\",1862,0,\"test\",[\"i32:41\"]],[\"invoke\",1864,0,\"test\",[\"i32:43\"]],[\"invoke\",1865,0,\"test\",[\"i32:44\"]],[\"invoke\",1867,0,\"test\",[\"i32:46\"]],[\"invoke\",1869,0,\"test\",[\"i32:48\"]],[\"invoke\",1871,0,\"test\",[\"i32:50\"]],[\"invoke\",1872,0,\"test\",[\"i32:51\"]],[\"invoke\",1874,0,\"test\",[\"i32:53\"]],[\"invoke\",1876,0,\"test\",[\"i32:55\"]],[\"invoke\",1877,0,\"test\",[\"i32:56\"]],[\"invoke\",1879,0,\"test\",[\"i32:58\"]],[\"invoke\",1881,0,\"test\",[\"i32:60\"]],[\"invoke\",1882,0,\"test\",[\"i32:61\"]],[\"invoke\",1884,0,\"test\",[\"i32:63\"]],[\"invoke\",1886,0,\"test\",[\"i32:65\"]],[\"invoke\",1887,0,\"test\",[\"i32:66\"]],[\"invoke\",1889,0,\"test\",[\"i32:68\"]],[\"invoke\",1891,0,\"test\",[\"i32:70\"]],[\"invoke\",1893,0,\"test\",[\"i32:72\"]],[\"invoke\",1894,0,\"test\",[\"i32:73\"]],[\"invoke\",1896,0,\"test\",[\"i32:75\"]],[\"invoke\",1898,0,\"test\",[\"i32:77\"]],[\"invoke\",1899,0,\"test\",[\"i32:78\"]],[\"invoke\",1901,0,\"test\",[\"i32:80\"]],[\"invoke\",1903,0,\"test\",[\"i32:82\"]],[\"invoke\",1904,0,\"test\",[\"i32:83\"]],[\"invoke\",1906,0,\"test\",[\"i32:85\"]],[\"invoke\",1908,0,\"test\",[\"i32:87\"]],[\"invoke\",1909,0,\"test\",[\"i32:88\"]],[\"invoke\",1911,0,\"test\",[\"i32:90\"]],[\"invoke\",1913,0,\"test\",[\"i32:92\"]],[\"invoke\",1915,0,\"test\",[\"i32:94\"]],[\"invoke\",1916,0,\"test\",[\"i32:95\"]],[\"invoke\",1918,0,\"test\",[\"i32:97\"]],[\"invoke\",1920,0,\"test\",[\"i32:99\"]],[\"invoke\",1921,0,\"test\",[\"i32:100\"]],[\"invoke\",1923,0,\"test\",[\"i32:102\"]],[\"invoke\",1925,0,\"test\",[\"i32:104\"]],[\"invoke\",1926,0,\"test\",[\"i32:105\"]],[\"invoke\",1928,0,\"test\",[\"i32:107\"]],[\"invoke\",1930,0,\"test\",[\"i32:109\"]],[\"invoke\",1931,0,\"test\",[\"i32:110\"]],[\"invoke\",1933,0,\"test\",[\"i32:112\"]],[\"invoke\",1935,0,\"test\",[\"i32:114\"]],[\"invoke\",1937,0,\"test\",[\"i32:116\"]],[\"invoke\",1938,0,\"test\",[\"i32:117\"]],[\"invoke\",1940,0,\"test\",[\"i32:119\"]],[\"invoke\",1942,0,\"test\",[\"i32:121\"]],[\"invoke\",1943,0,\"test\",[\"i32:122\"]],[\"invoke\",1945,0,\"test\",[\"i32:124\"]],[\"invoke\",1947,0,\"test\",[\"i32:126\"]],[\"invoke\",1948,0,\"test\",[\"i32:127\"]],[\"invoke\",1950,0,\"test\",[\"i32:129\"]],[\"invoke\",1952,0,\"test\",[\"i32:131\"]],[\"invoke\",1953,0,\"test\",[\"i32:132\"]],[\"invoke\",1955,0,\"test\",[\"i32:134\"]],[\"invoke\",1957,0,\"test\",[\"i32:136\"]],[\"invoke\",1959,0,\"test\",[\"i32:138\"]],[\"invoke\",1960,0,\"test\",[\"i32:139\"]],[\"invoke\",1962,0,\"test\",[\"i32:141\"]],[\"invoke\",1964,0,\"test\",[\"i32:143\"]],[\"invoke\",1965,0,\"test\",[\"i32:144\"]],[\"invoke\",1967,0,\"test\",[\"i32:146\"]],[\"invoke\",1969,0,\"test\",[\"i32:148\"]],[\"invoke\",1970,0,\"test\",[\"i32:149\"]],[\"invoke\",1972,0,\"test\",[\"i32:151\"]],[\"invoke\",1974,0,\"test\",[\"i32:153\"]],[\"invoke\",1975,0,\"test\",[\"i32:154\"]],[\"invoke\",1977,0,\"test\",[\"i32:156\"]],[\"invoke\",1979,0,\"test\",[\"i32:158\"]],[\"module\",1982,89],[\"invoke\",2010,0,\"run\",[\"i32:48\",\"i32:4294967280\"]],[\"invoke\",2011,0,\"test\",[\"i32:0\"]],[\"invoke\",2013,0,\"test\",[\"i32:2\"]],[\"invoke\",2015,0,\"test\",[\"i32:4\"]],[\"invoke\",2016,0,\"test\",[\"i32:5\"]],[\"invoke\",2018,0,\"test\",[\"i32:7\"]],[\"invoke\",2020,0,\"test\",[\"i32:9\"]],[\"invoke\",2021,0,\"test\",[\"i32:10\"]],[\"invoke\",2023,0,\"test\",[\"i32:12\"]],[\"invoke\",2025,0,\"test\",[\"i32:14\"]],[\"invoke\",2026,0,\"test\",[\"i32:15\"]],[\"invoke\",2028,0,\"test\",[\"i32:17\"]],[\"invoke\",2030,0,\"test\",[\"i32:19\"]],[\"invoke\",2032,0,\"test\",[\"i32:21\"]],[\"invoke\",2033,0,\"test\",[\"i32:22\"]],[\"invoke\",2035,0,\"test\",[\"i32:24\"]],[\"invoke\",2037,0,\"test\",[\"i32:26\"]],[\"invoke\",2038,0,\"test\",[\"i32:27\"]],[\"invoke\",2040,0,\"test\",[\"i32:29\"]],[\"invoke\",2042,0,\"test\",[\"i32:31\"]],[\"invoke\",2043,0,\"test\",[\"i32:32\"]],[\"invoke\",2045,0,\"test\",[\"i32:34\"]],[\"invoke\",2047,0,\"test\",[\"i32:36\"]],[\"invoke\",2048,0,\"test\",[\"i32:37\"]],[\"invoke\",2050,0,\"test\",[\"i32:39\"]],[\"invoke\",2052,0,\"test\",[\"i32:41\"]],[\"invoke\",2054,0,\"test\",[\"i32:43\"]],[\"invoke\",2055,0,\"test\",[\"i32:44\"]],[\"invoke\",2057,0,\"test\",[\"i32:46\"]],[\"invoke\",2059,0,\"test\",[\"i32:48\"]],[\"invoke\",2060,0,\"test\",[\"i32:49\"]],[\"invoke\",2062,0,\"test\",[\"i32:51\"]],[\"invoke\",2064,0,\"test\",[\"i32:53\"]],[\"invoke\",2065,0,\"test\",[\"i32:54\"]],[\"invoke\",2067,0,\"test\",[\"i32:56\"]],[\"invoke\",2069,0,\"test\",[\"i32:58\"]],[\"invoke\",2070,0,\"test\",[\"i32:59\"]],[\"invoke\",2072,0,\"test\",[\"i32:61\"]],[\"invoke\",2074,0,\"test\",[\"i32:63\"]],[\"module\",2076,90],[\"invoke\",2105,0,\"test\",[\"i32:0\"]],[\"invoke\",2106,0,\"test\",[\"i32:1\"]],[\"invoke\",2108,0,\"test\",[\"i32:3\"]],[\"invoke\",2110,0,\"test\",[\"i32:5\"]],[\"invoke\",2111,0,\"test\",[\"i32:6\"]],[\"invoke\",2113,0,\"test\",[\"i32:8\"]],[\"invoke\",2115,0,\"test\",[\"i32:10\"]],[\"invoke\",2116,0,\"test\",[\"i32:11\"]],[\"invoke\",2118,0,\"test\",[\"i32:13\"]],[\"invoke\",2120,0,\"test\",[\"i32:15\"]],[\"module\",2122,91],[\"file\",\"table_set.wast\"],[\"module\",1,92],[\"invoke\",29,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",30,0,\"set-externref\",[\"i32:0\",\"externref:1\"]],[\"invoke\",31,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",32,0,\"set-externref\",[\"i32:0\",\"externref:null\"]],[\"invoke\",33,0,\"get-externref\",[\"i32:0\"]],[\"invoke\",35,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",36,0,\"set-funcref-from\",[\"i32:0\",\"i32:1\"]],[\"invoke\",37,0,\"is_null-funcref\",[\"i32:0\"]],[\"invoke\",38,0,\"set-funcref\",[\"i32:0\",\"funcref:null\"]],[\"invoke\",39,0,\"get-funcref\",[\"i32:0\"]],[\"invoke\",41,0,\"set-externref\",[\"i32:2\",\"externref:null\"]],[\"invoke\",42,0,\"set-funcref\",[\"i32:3\",\"funcref:null\"]],[\"invoke\",43,0,\"set-externref\",[\"i32:4294967295\",\"externref:null\"]],[\"invoke\",44,0,\"set-funcref\",[\"i32:4294967295\",\"funcref:null\"]],[\"invoke\",46,0,\"set-externref\",[\"i32:2\",\"externref:0\"]],[\"invoke\",47,0,\"set-funcref-from\",[\"i32:3\",\"i32:1\"]],[\"invoke\",48,0,\"set-externref\",[\"i32:4294967295\",\"externref:0\"]],[\"invoke\",49,0,\"set-funcref-from\",[\"i32:4294967295\",\"i32:1\"]],[\"file\",\"table_size.wast\"],[\"module\",1,93],[\"invoke\",26,0,\"size-t0\",[]],[\"invoke\",27,0,\"grow-t0\",[\"i32:1\"]],[\"invoke\",28,0,\"size-t0\",[]],[\"invoke\",29,0,\"grow-t0\",[\"i32:4\"]],[\"invoke\",30,0,\"size-t0\",[]],[\"invoke\",31,0,\"grow-t0\",[\"i32:0\"]],[\"invoke\",32,0,\"size-t0\",[]],[\"invoke\",34,0,\"size-t1\",[]],[\"invoke\",35,0,\"grow-t1\",[\"i32:1\"]],[\"invoke\",36,0,\"size-t1\",[]],[\"invoke\",37,0,\"grow-t1\",[\"i32:4\"]],[\"invoke\",38,0,\"size-t1\",[]],[\"invoke\",39,0,\"grow-t1\",[\"i32:0\"]],[\"invoke\",40,0,\"size-t1\",[]],[\"invoke\",42,0,\"size-t2\",[]],[\"invoke\",43,0,\"grow-t2\",[\"i32:3\"]],[\"invoke\",44,0,\"size-t2\",[]],[\"invoke\",45,0,\"grow-t2\",[\"i32:1\"]],[\"invoke\",46,0,\"size-t2\",[]],[\"invoke\",47,0,\"grow-t2\",[\"i32:0\"]],[\"invoke\",48,0,\"size-t2\",[]],[\"invoke\",49,0,\"grow-t2\",[\"i32:4\"]],[\"invoke\",50,0,\"size-t2\",[]],[\"invoke\",51,0,\"grow-t2\",[\"i32:1\"]],[\"invoke\",52,0,\"size-t2\",[]],[\"invoke\",54,0,\"size-t3\",[]],[\"invoke\",55,0,\"grow-t3\",[\"i32:1\"]],[\"invoke\",56,0,\"size-t3\",[]],[\"invoke\",57,0,\"grow-t3\",[\"i32:3\"]],[\"invoke\",58,0,\"size-t3\",[]],[\"invoke\",59,0,\"grow-t3\",[\"i32:0\"]],[\"invoke\",60,0,\"size-t3\",[]],[\"invoke\",61,0,\"grow-t3\",[\"i32:2\"]],[\"invoke\",62,0,\"size-t3\",[]],[\"invoke\",63,0,\"grow-t3\",[\"i32:1\"]],[\"invoke\",64,0,\"size-t3\",[]],[\"file\",\"elem.wast\"],[\"module\",4,94],[\"module\",80,95],[\"module\",90,96],[\"module\",95,97],[\"module\",101,98],[\"module\",110,99],[\"module\",120,100],[\"module\",127,100],[\"module\",134,101],[\"invoke\",148,0,\"call-7\",[]],[\"invoke\",149,0,\"call-9\",[]],[\"module\",153,102],[\"invoke\",167,0,\"call-7\",[]],[\"invoke\",168,0,\"call-9\",[]],[\"module\",182,103],[\"module\",187,104],[\"module\",193,105],[\"module\",197,106],[\"module\",202,107],[\"module\",207,108],[\"module\",212,109],[\"module\",218,110],[\"module\",224,111],[\"module\",230,112],[\"instantiate\",239,113],[\"instantiate\",248,114],[\"instantiate\",257,115],[\"instantiate\",266,116],[\"instantiate\",273,117],[\"instantiate\",281,118],[\"instantiate\",290,119],[\"instantiate\",298,118],[\"instantiate\",307,120],[\"instantiate\",315,121],[\"instantiate\",324,122],[\"instantiate\",332,123],[\"module\",342,124],[\"invoke\",350,0,\"init\",[]],[\"module\",352,125],[\"invoke\",360,0,\"init\",[]],[\"module\",537,126],[\"invoke\",548,0,\"call-overwritten\",[]],[\"module\",550,127],[\"invoke\",561,0,\"call-overwritten-element\",[]],[\"module\",565,128,\"$module1\"],[\"register\",\"module1\",\"$module1\"],[\"invoke\",585,\"$module1\",\"call-7\",[]],[\"invoke\",586,\"$module1\",\"call-8\",[]],[\"invoke\",587,\"$module1\",\"call-9\",[]],[\"module\",589,129,\"$module2\"],[\"invoke\",598,\"$module1\",\"call-7\",[]],[\"invoke\",599,\"$module1\",\"call-8\",[]],[\"invoke\",600,\"$module1\",\"call-9\",[]],[\"module\",602,130,\"$module3\"],[\"invoke\",611,\"$module1\",\"call-7\",[]],[\"invoke\",612,\"$module1\",\"call-8\",[]],[\"invoke\",613,\"$module1\",\"call-9\",[]],[\"module\",646,131,\"$m\"],[\"register\",\"exporter\",\"$m\"],[\"invoke\",655,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",656,\"$m\",\"get\",[\"i32:1\"]],[\"invoke\",658,\"$m\",\"set\",[\"i32:0\",\"externref:42\"]],[\"invoke\",659,\"$m\",\"set\",[\"i32:1\",\"externref:137\"]],[\"invoke\",661,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",662,\"$m\",\"get\",[\"i32:1\"]],[\"module\",664,132],[\"invoke\",668,\"$m\",\"get\",[\"i32:0\"]],[\"invoke\",669,\"$m\",\"get\",[\"i32:1\"]],[\"module\",673,133,\"$module4\"],[\"register\",\"module4\",\"$module4\"],[\"module\",682,134],[\"invoke\",692,0,\"call_imported_elem\",[]],[\"file\",\"ref_func.wast\"],[\"module\",1,135],[\"register\",\"M\"],[\"module\",6,136],[\"invoke\",56,0,\"is_null-f\",[]],[\"invoke\",57,0,\"is_null-g\",[]],[\"invoke\",58,0,\"is_null-v\",[]],[\"invoke\",60,0,\"call-f\",[\"i32:4\"]],[\"invoke\",61,0,\"call-g\",[\"i32:4\"]],[\"invoke\",62,0,\"call-v\",[\"i32:4\"]],[\"invoke\",63,0,\"set-g\",[]],[\"invoke\",64,0,\"call-v\",[\"i32:4\"]],[\"invoke\",65,0,\"set-f\",[]],[\"invoke\",66,0,\"call-v\",[\"i32:4\"]],[\"module\",80,137],[\"file\",\"ref_is_null.wast\"],[\"module\",1,138],[\"invoke\",30,0,\"funcref\",[\"funcref:null\"]],[\"invoke\",31,0,\"externref\",[\"externref:null\"]],[\"invoke\",33,0,\"externref\",[\"externref:1\"]],[\"invoke\",35,0,\"init\",[\"externref:0\"]],[\"invoke\",37,0,\"funcref-elem\",[\"i32:0\"]],[\"invoke\",38,0,\"externref-elem\",[\"i32:0\"]],[\"invoke\",40,0,\"funcref-elem\",[\"i32:1\"]],[\"invoke\",41,0,\"externref-elem\",[\"i32:1\"]],[\"invoke\",43,0,\"deinit\",[]],[\"invoke\",45,0,\"funcref-elem\",[\"i32:0\"]],[\"invoke\",46,0,\"externref-elem\",[\"i32:0\"]],[\"invoke\",48,0,\"funcref-elem\",[\"i32:1\"]],[\"invoke\",49,0,\"externref-elem\",[\"i32:1\"]],[\"file\",\"ref_null.wast\"],[\"module\",1,139],[\"invoke\",9,0,\"externref\",[]],[\"invoke\",10,0,\"funcref\",[]],[\"file\",\"exports.wast\"],[\"module\",3,140],[\"module\",4,141],[\"module\",5,142],[\"module\",7,140],[\"module\",8,143],[\"module\",9,144],[\"module\",10,140],[\"module\",11,140],[\"module\",12,140],[\"module\",13,140],[\"module\",14,140],[\"module\",16,145,\"$Func\"],[\"invoke\",22,0,\"e\",[\"i32:42\"]],[\"invoke\",23,\"$Func\",\"e\",[\"i32:42\"]],[\"module\",24,146],[\"module\",25,146,\"$Other1\"],[\"invoke\",26,\"$Func\",\"e\",[\"i32:42\"]],[\"module\",28,147],[\"invoke\",34,0,\"a\",[]],[\"invoke\",35,0,\"b\",[]],[\"invoke\",36,0,\"c\",[]],[\"module\",74,148],[\"module\",75,149],[\"module\",76,150],[\"module\",78,148],[\"module\",79,148],[\"module\",80,148],[\"module\",81,148],[\"module\",82,148],[\"module\",83,148],[\"module\",85,151,\"$Global\"],[\"get\",89,0,\"e\",[]],[\"get\",90,\"$Global\",\"e\",[]],[\"module\",91,146],[\"module\",92,146,\"$Other2\"],[\"get\",93,\"$Global\",\"e\",[]],[\"module\",131,152],[\"module\",132,153],[\"module\",133,154],[\"module\",135,152],[\"module\",136,155],[\"module\",137,152],[\"module\",138,155],[\"module\",139,152],[\"module\",140,155],[\"module\",141,152],[\"module\",142,155],[\"module\",143,152],[\"module\",144,155],[\"module\",145,152],[\"module\",146,155],[\"module\",186,156],[\"module\",187,157],[\"module\",191,156],[\"module\",192,158],[\"module\",193,156],[\"module\",194,158],[\"module\",195,156],[\"module\",196,158],[\"module\",197,156],[\"module\",198,158],[\"module\",199,156],[\"module\",200,158],[\"module\",201,156],[\"module\",202,158],[\"file\",\"linking.wast\"],[\"module\",3,159,\"$Mf\"],[\"register\",\"Mf\",\"$Mf\"],[\"module\",9,160,\"$Nf\"],[\"invoke\",17,\"$Mf\",\"call\",[]],[\"invoke\",18,\"$Nf\",\"Mf.call\",[]],[\"invoke\",19,\"$Nf\",\"call\",[]],[\"invoke\",20,\"$Nf\",\"call Mf.call\",[]],[\"module\",22,161],[\"register\",\"reexport_f\"],[\"instantiate\",28,162],[\"instantiate\",32,163],[\"module\",39,164,\"$Mg\"],[\"register\",\"Mg\",\"$Mg\"],[\"module\",50,165,\"$Ng\"],[\"get\",67,\"$Mg\",\"glob\",[]],[\"get\",68,\"$Ng\",\"Mg.glob\",[]],[\"get\",69,\"$Ng\",\"glob\",[]],[\"invoke\",70,\"$Mg\",\"get\",[]],[\"invoke\",71,\"$Ng\",\"Mg.get\",[]],[\"invoke\",72,\"$Ng\",\"get\",[]],[\"get\",74,\"$Mg\",\"mut_glob\",[]],[\"get\",75,\"$Ng\",\"Mg.mut_glob\",[]],[\"invoke\",76,\"$Mg\",\"get_mut\",[]],[\"invoke\",77,\"$Ng\",\"Mg.get_mut\",[]],[\"invoke\",79,\"$Mg\",\"set_mut\",[\"i32:241\"]],[\"get\",80,\"$Mg\",\"mut_glob\",[]],[\"get\",81,\"$Ng\",\"Mg.mut_glob\",[]],[\"invoke\",82,\"$Mg\",\"get_mut\",[]],[\"invoke\",83,\"$Ng\",\"Mg.get_mut\",[]],[\"instantiate\",87,166],[\"instantiate\",91,167],[\"module\",96,168,\"$Mref_ex\"],[\"register\",\"Mref_ex\",\"$Mref_ex\"],[\"module\",104,169,\"$Mref_im\"],[\"instantiate\",113,170],[\"instantiate\",117,171],[\"instantiate\",123,172],[\"instantiate\",127,173],[\"module\",134,174,\"$Mt\"],[\"register\",\"Mt\",\"$Mt\"],[\"module\",149,175,\"$Nt\"],[\"invoke\",168,\"$Mt\",\"call\",[\"i32:2\"]],[\"invoke\",169,\"$Nt\",\"Mt.call\",[\"i32:2\"]],[\"invoke\",170,\"$Nt\",\"call\",[\"i32:2\"]],[\"invoke\",171,\"$Nt\",\"call Mt.call\",[\"i32:2\"]],[\"invoke\",173,\"$Mt\",\"call\",[\"i32:1\"]],[\"invoke\",174,\"$Nt\",\"Mt.call\",[\"i32:1\"]],[\"invoke\",175,\"$Nt\",\"call\",[\"i32:1\"]],[\"invoke\",176,\"$Nt\",\"call Mt.call\",[\"i32:1\"]],[\"invoke\",178,\"$Mt\",\"call\",[\"i32:0\"]],[\"invoke\",179,\"$Nt\",\"Mt.call\",[\"i32:0\"]],[\"invoke\",180,\"$Nt\",\"call\",[\"i32:0\"]],[\"invoke\",181,\"$Nt\",\"call Mt.call\",[\"i32:0\"]],[\"invoke\",183,\"$Mt\",\"call\",[\"i32:20\"]],[\"invoke\",184,\"$Nt\",\"Mt.call\",[\"i32:20\"]],[\"invoke\",185,\"$Nt\",\"call\",[\"i32:7\"]],[\"invoke\",186,\"$Nt\",\"call Mt.call\",[\"i32:20\"]],[\"invoke\",188,\"$Nt\",\"call\",[\"i32:3\"]],[\"invoke\",189,\"$Nt\",\"call\",[\"i32:4\"]],[\"module\",191,176,\"$Ot\"],[\"invoke\",204,\"$Mt\",\"call\",[\"i32:3\"]],[\"invoke\",205,\"$Nt\",\"Mt.call\",[\"i32:3\"]],[\"invoke\",206,\"$Nt\",\"call Mt.call\",[\"i32:3\"]],[\"invoke\",207,\"$Ot\",\"call\",[\"i32:3\"]],[\"invoke\",209,\"$Mt\",\"call\",[\"i32:2\"]],[\"invoke\",210,\"$Nt\",\"Mt.call\",[\"i32:2\"]],[\"invoke\",211,\"$Nt\",\"call\",[\"i32:2\"]],[\"invoke\",212,\"$Nt\",\"call Mt.call\",[\"i32:2\"]],[\"invoke\",213,\"$Ot\",\"call\",[\"i32:2\"]],[\"invoke\",215,\"$Mt\",\"call\",[\"i32:1\"]],[\"invoke\",216,\"$Nt\",\"Mt.call\",[\"i32:1\"]],[\"invoke\",217,\"$Nt\",\"call\",[\"i32:1\"]],[\"invoke\",218,\"$Nt\",\"call Mt.call\",[\"i32:1\"]],[\"invoke\",219,\"$Ot\",\"call\",[\"i32:1\"]],[\"invoke\",221,\"$Mt\",\"call\",[\"i32:0\"]],[\"invoke\",222,\"$Nt\",\"Mt.call\",[\"i32:0\"]],[\"invoke\",223,\"$Nt\",\"call\",[\"i32:0\"]],[\"invoke\",224,\"$Nt\",\"call Mt.call\",[\"i32:0\"]],[\"invoke\",225,\"$Ot\",\"call\",[\"i32:0\"]],[\"invoke\",227,\"$Ot\",\"call\",[\"i32:20\"]],[\"module\",229,177],[\"module\",235,178,\"$G1\"],[\"register\",\"G1\",\"$G1\"],[\"module\",237,179,\"$G2\"],[\"get\",241,\"$G2\",\"g\",[]],[\"instantiate\",244,180],[\"instantiate\",253,181],[\"invoke\",262,\"$Mt\",\"call\",[\"i32:7\"]],[\"instantiate\",267,182],[\"invoke\",275,\"$Mt\",\"call\",[\"i32:7\"]],[\"invoke\",276,\"$Mt\",\"call\",[\"i32:8\"]],[\"instantiate\",279,183],[\"invoke\",288,\"$Mt\",\"call\",[\"i32:7\"]],[\"module\",291,184,\"$Mtable_ex\"],[\"register\",\"Mtable_ex\",\"$Mtable_ex\"],[\"module\",297,185],[\"instantiate\",303,186],[\"instantiate\",307,187],[\"module\",314,188,\"$Mm\"],[\"register\",\"Mm\",\"$Mm\"],[\"module\",324,189,\"$Nm\"],[\"invoke\",336,\"$Mm\",\"load\",[\"i32:12\"]],[\"invoke\",337,\"$Nm\",\"Mm.load\",[\"i32:12\"]],[\"invoke\",338,\"$Nm\",\"load\",[\"i32:12\"]],[\"module\",340,190,\"$Om\"],[\"invoke\",349,\"$Mm\",\"load\",[\"i32:12\"]],[\"invoke\",350,\"$Nm\",\"Mm.load\",[\"i32:12\"]],[\"invoke\",351,\"$Nm\",\"load\",[\"i32:12\"]],[\"invoke\",352,\"$Om\",\"load\",[\"i32:12\"]],[\"module\",354,191],[\"instantiate\",360,192],[\"module\",367,193,\"$Pm\"],[\"invoke\",375,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",376,\"$Pm\",\"grow\",[\"i32:2\"]],[\"invoke\",377,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",378,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",379,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",380,\"$Pm\",\"grow\",[\"i32:0\"]],[\"invoke\",381,\"$Pm\",\"grow\",[\"i32:1\"]],[\"invoke\",382,\"$Pm\",\"grow\",[\"i32:0\"]],[\"instantiate\",385,194],[\"invoke\",393,\"$Mm\",\"load\",[\"i32:0\"]],[\"instantiate\",398,195],[\"invoke\",406,\"$Mm\",\"load\",[\"i32:0\"]],[\"invoke\",407,\"$Mm\",\"load\",[\"i32:327670\"]],[\"instantiate\",410,196],[\"invoke\",419,\"$Mm\",\"load\",[\"i32:0\"]],[\"module\",422,197,\"$Ms\"],[\"register\",\"Ms\",\"$Ms\"],[\"instantiate\",436,198],[\"invoke\",452,\"$Ms\",\"get memory[0]\",[]],[\"invoke\",453,\"$Ms\",\"get table[0]\",[]]]"; diff --git a/tests/corpus/webassembly-spec-table/main.mjs b/tests/corpus/webassembly-spec-table/main.mjs new file mode 100644 index 000000000..38c7c67d5 --- /dev/null +++ b/tests/corpus/webassembly-spec-table/main.mjs @@ -0,0 +1,5 @@ +// Executes WebAssembly spec tests (13 files); see tests/wasm-spec/runner.mjs. +import { run } from "../../wasm-spec/runner.mjs"; +import { modules, script } from "./data.mjs"; + +run(modules, script); diff --git a/tests/corpus/webassembly-typed.ts b/tests/corpus/webassembly-typed.ts new file mode 100644 index 000000000..967996135 --- /dev/null +++ b/tests/corpus/webassembly-typed.ts @@ -0,0 +1,51 @@ +// The WebAssembly API from TypeScript: lib.dom's types (declared by +// scriptc.d.ts), exports read through casts, and the error classes in +// catch clauses. The module text is tests/wasm-spec/corpus/api.wat. +const bytes: Uint8Array = Buffer.from("AGFzbQEAAAABLwlgAX8Bf2ACf38Bf2ABfwBgAABgAAF/YAJ+fgF+YAJ/fwJ/f2ABfQF9YAN/f38AAi0EA2VudgNsb2cAAgNlbnYEZmFpbAAAA2VudgRiYXNlA38AA2VudgNiaWcDfgADEhEAAAMBAQQFBgcAAAQAAAAICAQFAXABAggFBAEBAQQGFwN/AUEAC3wARAAAAAAAAApAC34AIwELB5kBEwZtZW1vcnkCAAV0YWJsZQEAB2NvdW50ZXIDAgJwaQMDBGJpZzIDBANhZGQABQVhcHBseQAGBGJ1bXAABwVtdWw2NAAIBmRpdm1vZAAJBm5hcnJvdwAKBnN0cmxlbgALBGdyb3cADAVwYWdlcwANCGNhbGxGYWlsAA4EdHJhcAAPB3JlY3Vyc2UAEARmaWxsABEEY29weQASCAEECQgBAEEACwICAwqHAhEHACAAQQJsCwcAIAAgAGwLBgAjABAACwcAIAAgAWoLCQAgASAAEQAACwsAIwJBAWokAiMCCwcAIAAgAX4LDAAgACABbSAAIAFvCwQAIAALIAEBfwJAA0AgACABai0AAEUNASABQQFqIQEMAAsLIAELBgAgAEAACwQAPwALCQAgABABQQFqC1kAAkACQAJAAkACQAJAAkAgAA4GAAECAwQFBgsAC0EBQQBuDwtB/////wcoAgAPC0EBQQJBABEBAA8LQQFBBxEAAA8LRAAAAAAAAPh/qg8LQYCAgIB4QX9tCwwAIABBAWoQEEEBagsLACAAIAEgAvwLAAsMACAAIAEgAvwKAAALCw4BAEEQCwhzY3JpcHRjAA==", "base64"); + +const logs: string[] = []; +// lib.dom's ImportValue has no bigint; Node accepts one for an i64 global. +const imports = { + env: { + log: (v: number): void => { + logs.push(`start ${v}`); + }, + fail: (v: number): number => v + 1, + base: 7, + big: 1n, + }, +} as unknown as WebAssembly.Imports; + +async function main(): Promise { + const { module, instance } = await WebAssembly.instantiate(bytes, imports); + console.log(logs.join(","), module instanceof WebAssembly.Module); + const exports = instance.exports; + const add = exports.add as (a: number, b: number) => number; + const mul64 = exports.mul64 as (a: bigint, b: bigint) => bigint; + const strlen = exports.strlen as (p: number) => number; + const trap = exports.trap as (kind: number) => number; + console.log(add(20, 22), mul64(3n, 4n), strlen(16)); + const memory = exports.memory as WebAssembly.Memory; + const text = new Uint8Array(memory.buffer, 16, strlen(16)); + console.log(new TextDecoder().decode(text), memory.buffer.byteLength); + const counter = exports.counter as WebAssembly.Global; + counter.value = 41; + console.log((exports.bump as () => number)(), counter.value); + for (let kind = 0; kind < 3; kind++) { + try { + trap(kind); + } catch (e) { + if (e instanceof WebAssembly.RuntimeError) console.log(`RuntimeError: ${e.message}`); + else throw e; + } + } + const second = new WebAssembly.Instance(module, imports); + console.log((second.exports.add as (a: number, b: number) => number)(1, 2), WebAssembly.validate(bytes)); + try { + await WebAssembly.compile(new Uint8Array([0, 97, 115, 109])); + } catch (e) { + console.log(e instanceof WebAssembly.CompileError); + } +} + +await main(); diff --git a/tests/corpus/webassembly-worker.ts b/tests/corpus/webassembly-worker.ts new file mode 100644 index 000000000..02bdf22c6 --- /dev/null +++ b/tests/corpus/webassembly-worker.ts @@ -0,0 +1,68 @@ +import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads"; + +// The codemode shape: the main thread compiles a module once and sends the +// WebAssembly.Module to a fresh Worker per job, through workerData and +// through postMessage; each worker instantiates it with its own imports, +// runs it, and reports back. Traps in a worker (out-of-bounds memory and +// stack exhaustion, both caught by the per-thread fault handling) stay in +// that worker. The module text is tests/wasm-spec/corpus/api.wat. +const bytes = Buffer.from("AGFzbQEAAAABLwlgAX8Bf2ACf38Bf2ABfwBgAABgAAF/YAJ+fgF+YAJ/fwJ/f2ABfQF9YAN/f38AAi0EA2VudgNsb2cAAgNlbnYEZmFpbAAAA2VudgRiYXNlA38AA2VudgNiaWcDfgADEhEAAAMBAQQFBgcAAAQAAAAICAQFAXABAggFBAEBAQQGFwN/AUEAC3wARAAAAAAAAApAC34AIwELB5kBEwZtZW1vcnkCAAV0YWJsZQEAB2NvdW50ZXIDAgJwaQMDBGJpZzIDBANhZGQABQVhcHBseQAGBGJ1bXAABwVtdWw2NAAIBmRpdm1vZAAJBm5hcnJvdwAKBnN0cmxlbgALBGdyb3cADAVwYWdlcwANCGNhbGxGYWlsAA4EdHJhcAAPB3JlY3Vyc2UAEARmaWxsABEEY29weQASCAEECQgBAEEACwICAwqHAhEHACAAQQJsCwcAIAAgAGwLBgAjABAACwcAIAAgAWoLCQAgASAAEQAACwsAIwJBAWokAiMCCwcAIAAgAX4LDAAgACABbSAAIAFvCwQAIAALIAEBfwJAA0AgACABai0AAEUNASABQQFqIQEMAAsLIAELBgAgAEAACwQAPwALCQAgABABQQFqC1kAAkACQAJAAkACQAJAAkAgAA4GAAECAwQFBgsAC0EBQQBuDwtB/////wcoAgAPC0EBQQJBABEBAA8LQQFBBxEAAA8LRAAAAAAAAPh/qg8LQYCAgIB4QX9tCwwAIABBAWoQEEEBagsLACAAIAEgAvwLAAsMACAAIAEgAvwKAAALCw4BAEEQCwhzY3JpcHRjAA==", "base64"); + +interface Job { + module: WebAssembly.Module; + input: number; +} + +function run(module: WebAssembly.Module, input: number): string { + const seen: number[] = []; + const imports = { + env: { + log: (v: number): void => { + seen.push(v); + }, + fail: (v: number): number => v * 2, + base: input, + big: BigInt(input), + }, + } as unknown as WebAssembly.Imports; + const instance = new WebAssembly.Instance(module, imports); + const exports = instance.exports; + const apply = exports.apply as (fn: number, v: number) => number; + const callFail = exports.callFail as (v: number) => number; + const trap = exports.trap as (kind: number) => number; + const recurse = exports.recurse as (v: number) => number; + const traps: string[] = []; + for (const attempt of [() => trap(2), () => recurse(0), () => trap(1)]) { + try { + attempt(); + } catch (e) { + traps.push(e instanceof Error ? `${e.name}: ${e.message}` : String(e)); + } + } + return `job ${input}: start ${seen.join(",")} apply ${apply(1, input + 3)} callFail ${callFail(input)} traps ${traps.join(" | ")}`; +} + +if (isMainThread) { + const module = await WebAssembly.compile(bytes); + console.log(run(module, 0)); + for (let input = 1; input <= 3; input++) { + const line = await new Promise((resolve, reject) => { + const worker = new Worker(new URL(import.meta.url), { workerData: { module, input } satisfies Job }); + worker.once("message", (message: string) => resolve(message)); + worker.once("error", reject); + }); + console.log(line); + } + const worker = new Worker(new URL(import.meta.url), { workerData: null }); + const reply = new Promise((resolve) => worker.once("message", (message: string) => resolve(message))); + worker.postMessage({ module, input: 9 } satisfies Job); + console.log(await reply); + console.log("main still works:", run(module, 4).slice(0, 18)); +} else if (workerData !== null) { + const job = workerData as Job; + parentPort!.postMessage(`${job.module instanceof WebAssembly.Module} ${run(job.module, job.input)}`); +} else { + parentPort!.once("message", (job: Job) => { + parentPort!.postMessage(run(job.module, job.input)); + }); +} diff --git a/tests/corpus/webassembly-wpt-jsapi.mjs b/tests/corpus/webassembly-wpt-jsapi.mjs new file mode 100644 index 000000000..419e655ab --- /dev/null +++ b/tests/corpus/webassembly-wpt-jsapi.mjs @@ -0,0 +1,1117 @@ +// A port of web-platform-tests wasm/jsapi tests for the parts W5 adds: +// global/constructor, global/value-get-set, global/valueOf, +// memory/constructor, memory/grow, table/constructor, table/get-set, +// table/grow, module/imports, module/exports and module/customSections +// (WPT, 3-Clause BSD). Each test keeps its WPT name and body, with these +// changes: modules that WasmModuleBuilder builds at run time are inline +// base64 literals (built by the same builder), a minimal testharness prints +// one line per test, and the assertions the port cannot express are left +// out: the classes as values (prototypes, function name and length, +// property descriptors, branding through Function.prototype.call), +// Symbol arguments, Object.isFrozen and Object.isExtensible of +// ArrayBuffers (general scriptc gaps), and the old buffer's detachment +// after Memory.grow (a documented difference). assert_Global's parameter carries a JSDoc type: +// scriptc answers a Global's valueOf() only on receivers typed as one. +const modules = { + empty: "AGFzbQEAAAA=", + imports: "AGFzbQEAAAABBAFgAAACRAQGbW9kdWxlAmZuAAAGbW9kdWxlBmdsb2JhbAN/AAZtb2R1bGUGbWVtb3J5AgEAgAEGbW9kdWxlBXRhYmxlAXABAIAB", + importsEmptyModule: "AGFzbQEAAAABBAFgAAACLAQAAmZuAAAABmdsb2JhbAN/AAAGbWVtb3J5AgEAgAEABXRhYmxlAXABAIAB", + importsEmptyNames: "AGFzbQEAAAABBAFgAAACHQQBYQAAAAFiAAN/AAFjAAIBAIABAWQAAXABAIAB", + importsEmptyBoth: "AGFzbQEAAAABBAFgAAACGQQAAAAAAAADfwAAAAIBAIABAAABcAEAgAE=", + exports: "AGFzbQEAAAABBwJgAABgAAADAwIAAQQEAXAAAQUFAQEAgAIGEgJ/AUEHC3wBRDMzMzMzM/M/CwcwBgJmbgAAA2ZuMgABBXRhYmxlAQAGZ2xvYmFsAwAHZ2xvYmFsMgMBBm1lbW9yeQIACgcCAgALAgALABEEbmFtZQEKAgACZm4BA2ZuMg==", + exportsEmptyFunction: "AGFzbQEAAAABBAFgAAADAgEABwQBAAAACgQBAgALAAoEbmFtZQEDAQAA", + exportsEmptyTable: "AGFzbQEAAAAEBAFwAAEHBAEAAQA=", + exportsEmptyGlobal: "AGFzbQEAAAAGBgF/AUEHCwcEAQADAA==", + custom: "AGFzbQEAAAAAEARuYW1lV2ViQXNzZW1ibHkACgRuYW1lSlNBUEkADwNmb29XZWJBc3NlbWJseQ==", + customSurrogates: "AGFzbQEAAAAAFgp5ZWXwkJC3ZWV5V2ViQXNzZW1ibHk=", + customReplacement: "AGFzbQEAAAAAEwduYe+/vW1lV2ViQXNzZW1ibHk=", + customCopy: "AGFzbQEAAAAACARjb3B5ESIz", + fn: "AGFzbQEAAAABBAFgAAADAgEABwYBAmZuAAAKBAECAAsADARuYW1lAQUBAAJmbg==", + functions: "AGFzbQEAAAABCAJgAXwAYAAAAwMCAAEHDAICZm4AAANmbjIAAQoHAgIACwIACwARBG5hbWUBCgIAAmZuAQNmbjI=", +}; +const binary = (name) => Buffer.from(modules[name], "base64"); + +// ── a minimal testharness ───────────────────────────────────────────────── +class AssertionError extends Error {} +function fail(message) { + throw new AssertionError(message); +} +function same(a, b) { + return a === b ? a !== 0 || 1 / a === 1 / b : a !== a && b !== b; +} +function format_value(v) { + if (typeof v === "string") return JSON.stringify(v); + if (typeof v === "bigint") return `${v}n`; + if (typeof v === "number") return same(v, -0) ? "-0" : String(v); + if (typeof v === "boolean") return String(v); + if (v === null) return "null"; + if (v === undefined) return "undefined"; + return typeof v; +} +function assert_equals(actual, expected, description = "") { + if (!same(actual, expected)) fail(`${description} expected ${format_value(expected)} but got ${format_value(actual)}`); +} +function assert_not_equals(actual, expected, description = "") { + if (same(actual, expected)) fail(`${description} got disallowed value ${format_value(actual)}`); +} +function assert_true(actual, description = "") { + if (actual !== true) fail(`${description} expected true got ${format_value(actual)}`); +} +function assert_false(actual, description = "") { + if (actual !== false) fail(`${description} expected false got ${format_value(actual)}`); +} +function assert_array_equals(actual, expected, description = "") { + if (actual.length !== expected.length) fail(`${description} lengths differ, expected ${expected.length} got ${actual.length}`); + for (let i = 0; i < expected.length; i++) { + if (!same(actual[i], expected[i])) fail(`${description} property ${i}, expected ${format_value(expected[i])} but got ${format_value(actual[i])}`); + } +} +function assert_throws_js(name, func, description = "") { + try { + func(); + } catch (e) { + if (e instanceof AssertionError) throw e; + if (!(e instanceof Error) || e.name !== name) fail(`${description} threw ${e instanceof Error ? e.name : format_value(e)}, expected ${name}`); + return; + } + fail(`${description} did not throw`); +} +let passed = 0; +let failed = 0; +function test(func, name) { + try { + func(); + passed++; + console.log(`PASS ${name}`); + } catch (e) { + failed++; + console.log(`FAIL ${name}: ${e instanceof Error ? e.message : format_value(e)}`); + } +} + +// ── global/constructor.any.js ───────────────────────────────────────────── +/** @param {WebAssembly.Global} actual (typed so that valueOf() reaches the Global) */ +function assert_Global(actual, expected) { + assert_equals(actual.value, expected, "value"); + assert_equals(actual.valueOf(), expected, "valueOf"); +} + +test(() => { + // @ts-ignore: no descriptor + assert_throws_js("TypeError", () => new WebAssembly.Global()); +}, "No arguments"); + +test(() => { + const argument = { value: "i32" }; + // @ts-ignore: called without new + assert_throws_js("TypeError", () => WebAssembly.Global(argument)); +}, "Calling"); + +test(() => { + const order = []; + new WebAssembly.Global( + { + get value() { + order.push("descriptor value"); + return { + toString() { + order.push("descriptor value toString"); + return "f64"; + }, + }; + }, + get mutable() { + order.push("descriptor mutable"); + return false; + }, + }, + { + valueOf() { + order.push("value valueOf()"); + }, + }, + ); + assert_array_equals(order, ["descriptor mutable", "descriptor value", "descriptor value toString", "value valueOf()"]); +}, "Order of evaluation"); + +test(() => { + const invalidArguments = [undefined, null, false, true, "", "test", 1, NaN, {}]; + for (const invalidArgument of invalidArguments) { + // @ts-ignore: an invalid descriptor + assert_throws_js("TypeError", () => new WebAssembly.Global(invalidArgument), `new Global(${format_value(invalidArgument)})`); + } +}, "Invalid descriptor argument"); + +test(() => { + const invalidTypes = ["i16", "i128", "f16", "f128", "u32", "u64", "i32\0"]; + for (const value of invalidTypes) { + const argument = { value }; + // @ts-ignore: an invalid type + assert_throws_js("TypeError", () => new WebAssembly.Global(argument)); + } +}, "Invalid type argument"); + +test(() => { + const argument = { value: "v128" }; + // @ts-ignore: v128 + assert_throws_js("TypeError", () => new WebAssembly.Global(argument)); +}, "Construct v128 global"); + +test(() => { + const argument = { value: "i64" }; + const global = new WebAssembly.Global(argument); + assert_Global(global, 0n); +}, "i64 with default"); + +for (const type of ["i32", "f32", "f64"]) { + test(() => { + const argument = { value: type }; + const global = new WebAssembly.Global(argument); + assert_Global(global, 0); + }, `Default value for type ${type}`); + + const valueArguments = [ + { value: undefined, expected: 0, name: "undefined" }, + { value: null, expected: 0, name: "null" }, + { value: true, expected: 1, name: "true" }, + { value: false, expected: 0, name: "false" }, + { value: 2, expected: 2, name: "2" }, + { value: "3", expected: 3, name: '"3"' }, + { value: { toString() { return "5"; } }, expected: 5, name: "object with toString returning string" }, + { value: { valueOf() { return "8"; } }, expected: 8, name: "object with valueOf returning string" }, + { value: { toString() { return 6; } }, expected: 6, name: "object with toString returning number" }, + { value: { valueOf() { return 9; } }, expected: 9, name: "object with valueOf returning number" }, + ]; + for (const { value, expected, name } of valueArguments) { + test(() => { + const argument = { value: type }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type ${type}`); + } + + test(() => { + const argument = { value: type }; + assert_throws_js("TypeError", () => new WebAssembly.Global(argument, 0n)); + }, `BigInt value for type ${type}`); +} + +const bigintArguments = [ + { value: undefined, expected: 0n, name: "undefined" }, + { value: true, expected: 1n, name: "true" }, + { value: false, expected: 0n, name: "false" }, + { value: "3", expected: 3n, name: '"3"' }, + { value: 123n, expected: 123n, name: "123n" }, + { value: { toString() { return "5"; } }, expected: 5n, name: "object with toString returning string" }, + { value: { valueOf() { return "8"; } }, expected: 8n, name: "object with valueOf returning string" }, + { value: { toString() { return 6n; } }, expected: 6n, name: "object with toString returning bigint" }, + { value: { valueOf() { return 9n; } }, expected: 9n, name: "object with valueOf returning bigint" }, +]; +for (const { value, expected, name } of bigintArguments) { + test(() => { + const argument = { value: "i64" }; + const global = new WebAssembly.Global(argument, value); + assert_Global(global, expected); + }, `Explicit value ${name} for type i64`); +} + +const invalidBigints = [ + { value: null, name: "null" }, + { value: 666, name: "666" }, + { value: { toString() { return 5; } }, name: "object" }, + { value: { valueOf() { return 8; } }, name: "object" }, +]; +for (const { value: invalidBigint, name } of invalidBigints) { + test(() => { + const argument = { value: "i64" }; + assert_throws_js("TypeError", () => new WebAssembly.Global(argument, invalidBigint)); + }, `Pass non-bigint as i64 Global value: ${name}`); +} + +test(() => { + const argument = { value: "i32" }; + const global = new WebAssembly.Global(argument, 0, {}); + assert_Global(global, 0); +}, "Stray argument"); + +// ── global/value-get-set.any.js and global/valueOf.any.js ──────────────── +for (const type of ["i32", "i64", "f32", "f64"]) { + const [initial, value, invalid] = type === "i64" ? [0n, 1n, 2] : [0, 1, 2n]; + const immutableOptions = [ + { opts: {}, name: "missing" }, + { opts: { mutable: undefined }, name: "undefined" }, + { opts: { mutable: null }, name: "null" }, + { opts: { mutable: false }, name: "false" }, + { opts: { mutable: "" }, name: "empty string" }, + { opts: { mutable: 0 }, name: "zero" }, + ]; + for (const { opts, name } of immutableOptions) { + test(() => { + const global = new WebAssembly.Global({ ...opts, value: type }); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + assert_throws_js("TypeError", () => { + global.value = value; + }); + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); + }, `Immutable ${type} (${name})`); + + test(() => { + const global = new WebAssembly.Global({ ...opts, value: type }); + let touched = false; + const value = { + valueOf() { + touched = true; + return 1; + }, + toString() { + touched = true; + return "1"; + }, + }; + assert_throws_js("TypeError", () => { + global.value = value; + }); + assert_false(touched, "should not call valueOf or toString"); + assert_equals(global.value, initial, "post-set value"); + assert_equals(global.valueOf(), initial, "post-set valueOf"); + }, `Immutable ${type} with ToNumber side-effects (${name})`); + } + + const mutableOptions = [ + { opts: { mutable: true }, name: "true" }, + { opts: { mutable: 1 }, name: "one" }, + { opts: { mutable: "x" }, name: "string" }, + ]; + for (const { opts, name } of mutableOptions) { + test(() => { + const global = new WebAssembly.Global({ ...opts, value: type }); + assert_equals(global.value, initial, "initial value"); + assert_equals(global.valueOf(), initial, "initial valueOf"); + global.value = value; + assert_throws_js("TypeError", () => { + global.value = invalid; + }); + assert_equals(global.value, value, "post-set value"); + assert_equals(global.valueOf(), value, "post-set valueOf"); + }, `Mutable ${type} (${name})`); + } +} + +test(() => { + const argument = { value: "i64", mutable: true }; + const global = new WebAssembly.Global(argument); + assert_equals(global.value, 0n, "initial value using ToJSValue"); + const valid = [ + { input: 123n, output: 123n }, + { input: 2n ** 63n, output: -(2n ** 63n) }, + { input: true, output: 1n }, + { input: false, output: 0n }, + { input: "456", output: 456n }, + ]; + for (const { input, output } of valid) { + global.value = input; + assert_equals(global.valueOf(), output, "post-set valueOf"); + } + const invalid = [undefined, null, 0, 1, 4.2]; + for (const input of invalid) { + assert_throws_js("TypeError", () => { + global.value = input; + }); + } +}, "i64 mutability"); + +test(() => { + const argument = { value: "i32" }; + const global = new WebAssembly.Global(argument, 0); + assert_equals(global.valueOf({}), 0); +}, "valueOf: Stray argument"); + +// ── memory/constructor.any.js ───────────────────────────────────────────── +function assert_ArrayBuffer(actual, { size = 0 }, message) { + assert_equals(actual.toString(), "[object ArrayBuffer]", `${message}: toString()`); + assert_equals(actual.byteLength, 0x10000 * size, `${message}: size`); + if (size > 0) { + const array = new Uint8Array(actual); + assert_equals(array[0], 0, `${message}: first element`); + assert_equals(array[array.byteLength - 1], 0, `${message}: last element`); + } +} + +function assert_Memory(memory, { size = 0 }) { + assert_equals(memory.buffer, memory.buffer, "buffer should be idempotent"); + assert_ArrayBuffer(memory.buffer, { size }); +} + +test(() => { + // @ts-ignore: no descriptor + assert_throws_js("TypeError", () => new WebAssembly.Memory()); +}, "Memory: No arguments"); + +test(() => { + const argument = { initial: 0 }; + // @ts-ignore: called without new + assert_throws_js("TypeError", () => WebAssembly.Memory(argument)); +}, "Memory: Calling"); + +test(() => { + const invalidArguments = [undefined, null, false, true, "", "test", 1, NaN, {}]; + for (const invalidArgument of invalidArguments) { + // @ts-ignore: an invalid descriptor + assert_throws_js("TypeError", () => new WebAssembly.Memory(invalidArgument), `new Memory(${format_value(invalidArgument)})`); + } +}, "Memory: Invalid descriptor argument"); + +test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Memory({ initial: undefined })); +}, "Undefined initial value in descriptor"); + +const outOfRangeValues = [NaN, Infinity, -Infinity, -1, 0x100000000, 0x1000000000]; +for (const value of outOfRangeValues) { + test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Memory({ initial: value })); + }, `Memory: Out-of-range initial value in descriptor: ${format_value(value)}`); + + test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Memory({ initial: 0, maximum: value })); + }, `Memory: Out-of-range maximum value in descriptor: ${format_value(value)}`); +} + +test(() => { + assert_throws_js("RangeError", () => new WebAssembly.Memory({ initial: 10, maximum: 9 })); +}, "Memory: Initial value exceeds maximum"); + +test(() => { + const order = []; + new WebAssembly.Memory({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + }); + assert_array_equals(order, ["initial", "initial valueOf", "maximum", "maximum valueOf"]); +}, "Memory: Order of evaluation for descriptor"); + +test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { size: 0 }); +}, "Zero initial"); + +test(() => { + const argument = { initial: 4 }; + const memory = new WebAssembly.Memory(argument); + assert_Memory(memory, { size: 4 }); +}, "Non-zero initial"); + +test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument, {}); + assert_Memory(memory, { size: 0 }); +}, "Memory: Stray argument"); + +// ── memory/grow.any.js ──────────────────────────────────────────────────── +test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument); + // @ts-ignore: no delta + assert_throws_js("TypeError", () => memory.grow()); +}, "grow: Missing arguments"); + +for (const { initial, delta, label } of [ + { initial: 0, delta: 2, label: "Zero initial" }, + { initial: { valueOf() { return 0; } }, delta: { valueOf() { return 2; } }, label: "Zero initial with valueOf" }, + { initial: 3, delta: 2, label: "Non-zero initial" }, +]) { + test(() => { + const memory = new WebAssembly.Memory({ initial }); + const before = Number(initial); + const oldMemory = memory.buffer; + assert_ArrayBuffer(oldMemory, { size: before }, "Buffer before growing"); + const result = memory.grow(delta); + assert_equals(result, before); + const newMemory = memory.buffer; + assert_not_equals(oldMemory, newMemory); + assert_ArrayBuffer(newMemory, { size: before + 2 }, "New buffer after growing"); + }, `grow: ${label}`); +} + +test(() => { + const argument = { initial: 0, maximum: 2 }; + const memory = new WebAssembly.Memory(argument); + const result = memory.grow(2); + assert_equals(result, 0); + assert_ArrayBuffer(memory.buffer, { size: 2 }, "New buffer after growing"); +}, "grow: Zero initial with respected maximum"); + +test(() => { + const argument = { initial: 0, maximum: 2 }; + const memory = new WebAssembly.Memory(argument); + assert_equals(memory.grow(1), 0); + assert_equals(memory.grow(1), 1); + assert_ArrayBuffer(memory.buffer, { size: 2 }, "Buffer after growing twice"); +}, "grow: Zero initial with respected maximum grown twice"); + +test(() => { + const argument = { initial: 1, maximum: 2 }; + const memory = new WebAssembly.Memory(argument); + const oldMemory = memory.buffer; + assert_throws_js("RangeError", () => memory.grow(2)); + assert_equals(memory.buffer, oldMemory); + assert_ArrayBuffer(memory.buffer, { size: 1 }, "Buffer after trying to grow"); +}, "grow: Zero initial growing too much"); + +const outOfRangeDeltas = [undefined, NaN, Infinity, -Infinity, -1, 0x100000000, 0x1000000000]; +for (const value of outOfRangeDeltas) { + test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument); + assert_throws_js("TypeError", () => memory.grow(value)); + }, `grow: Out-of-range argument: ${format_value(value)}`); +} + +test(() => { + const argument = { initial: 0 }; + const memory = new WebAssembly.Memory(argument); + const result = memory.grow(2, {}); + assert_equals(result, 0); +}, "grow: Stray argument"); + +// ── table/constructor.any.js ────────────────────────────────────────────── +function assert_equal_to_array(table, expected, message = "") { + assert_equals(table.length, expected.length, `${message}: length`); + assert_throws_js("TypeError", () => table.get(-1), `${message}: table.get(-1)`); + for (let i = 0; i < expected.length; ++i) { + assert_equals(table.get(i), expected[i], `${message}: table.get(${i} of ${expected.length})`); + } + assert_throws_js("RangeError", () => table.get(expected.length), `${message}: table.get(${expected.length} of ${expected.length})`); + assert_throws_js("RangeError", () => table.get(expected.length + 1), `${message}: table.get(${expected.length + 1} of ${expected.length})`); +} + +function assert_Table(actual, expected) { + assert_equals(actual.length, expected.length, "length"); + for (let i = 0; i < expected.length; ++i) { + assert_equals(actual.get(i), null, `actual.get(${i})`); + } +} + +test(() => { + // @ts-ignore: no descriptor + assert_throws_js("TypeError", () => new WebAssembly.Table()); +}, "Table: No arguments"); + +test(() => { + const argument = { element: "anyfunc", initial: 0 }; + // @ts-ignore: called without new + assert_throws_js("TypeError", () => WebAssembly.Table(argument)); +}, "Table: Calling"); + +test(() => { + // @ts-ignore: an empty descriptor + assert_throws_js("TypeError", () => new WebAssembly.Table({})); +}, "Empty descriptor"); + +test(() => { + const invalidArguments = [undefined, null, false, true, "", "test", 1, NaN, {}]; + for (const invalidArgument of invalidArguments) { + // @ts-ignore: an invalid descriptor + assert_throws_js("TypeError", () => new WebAssembly.Table(invalidArgument), `new Table(${format_value(invalidArgument)})`); + } +}, "Table: Invalid descriptor argument"); + +test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Table({ element: "anyfunc", initial: undefined })); +}, "Table: Undefined initial value in descriptor"); + +test(() => { + // @ts-ignore: an undefined element + assert_throws_js("TypeError", () => new WebAssembly.Table({ element: undefined, initial: 0 })); +}, "Undefined element value in descriptor"); + +for (const value of outOfRangeValues) { + test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Table({ element: "anyfunc", initial: value })); + }, `Table: Out-of-range initial value in descriptor: ${format_value(value)}`); + + test(() => { + assert_throws_js("TypeError", () => new WebAssembly.Table({ element: "anyfunc", initial: 0, maximum: value })); + }, `Table: Out-of-range maximum value in descriptor: ${format_value(value)}`); +} + +test(() => { + assert_throws_js("RangeError", () => new WebAssembly.Table({ element: "anyfunc", initial: 10, maximum: 9 })); +}, "Table: Initial value exceeds maximum"); + +test(() => { + const argument = { element: "anyfunc", initial: 0 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { length: 0 }); +}, "Basic (zero)"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_Table(table, { length: 5 }); +}, "Basic (non-zero)"); + +test(() => { + const argument = { element: "anyfunc", initial: 0 }; + const table = new WebAssembly.Table(argument, null, {}); + assert_Table(table, { length: 0 }); +}, "Table: Stray argument"); + +test(() => { + const table = new WebAssembly.Table({ + // @ts-ignore: an object element + element: { + toString() { + return "anyfunc"; + }, + }, + initial: 1, + }); + assert_Table(table, { length: 1 }); +}, "Type conversion for descriptor.element"); + +test(() => { + const order = []; + new WebAssembly.Table({ + get maximum() { + order.push("maximum"); + return { + valueOf() { + order.push("maximum valueOf"); + return 1; + }, + }; + }, + get initial() { + order.push("initial"); + return { + valueOf() { + order.push("initial valueOf"); + return 1; + }, + }; + }, + get element() { + order.push("element"); + return { + toString() { + order.push("element toString"); + return "anyfunc"; + }, + }; + }, + }); + assert_array_equals(order, ["element", "element toString", "initial", "initial valueOf", "maximum", "maximum valueOf"]); +}, "Table: Order of evaluation for descriptor"); + +test(() => { + const testObject = {}; + const argument = { element: "externref", initial: 3 }; + const table = new WebAssembly.Table(argument, testObject); + assert_equals(table.length, 3); + assert_equals(table.get(0), testObject); + assert_equals(table.get(1), testObject); + assert_equals(table.get(2), testObject); +}, "initialize externref table with default value"); + +test(() => { + const argument = { element: "i32", initial: 3 }; + // @ts-ignore: an invalid element + assert_throws_js("TypeError", () => new WebAssembly.Table(argument)); +}, "initialize table with a wrong element value"); + +test(() => { + const fn = new WebAssembly.Instance(new WebAssembly.Module(binary("fn"))).exports.fn; + const argument = { element: "anyfunc", initial: 3 }; + const table = new WebAssembly.Table(argument, fn); + assert_equals(table.length, 3); + assert_equals(table.get(0), fn); + assert_equals(table.get(1), fn); + assert_equals(table.get(2), fn); +}, "initialize anyfunc table with default value"); + +test(() => { + const argument = { element: "anyfunc", initial: 3 }; + assert_throws_js("TypeError", () => new WebAssembly.Table(argument, {})); + assert_throws_js("TypeError", () => new WebAssembly.Table(argument, "cannot be used as a wasm function")); + assert_throws_js("TypeError", () => new WebAssembly.Table(argument, 37)); +}, "initialize anyfunc table with a bad default value"); + +test(() => { + assert_throws_js("RangeError", () => new WebAssembly.Table({ element: "anyfunc", initial: 3, maximum: 2 }, 37)); +}, "initialize anyfunc table with a bad default value and a bad descriptor"); + +// ── table/get-set.any.js ────────────────────────────────────────────────── +const functions = new WebAssembly.Instance(new WebAssembly.Module(binary("functions")), {}).exports; + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + // @ts-ignore: no index + assert_throws_js("TypeError", () => table.get()); +}, "Missing arguments: get"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + // @ts-ignore: no index + assert_throws_js("TypeError", () => table.set()); +}, "Missing arguments: set"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + const { fn, fn2 } = functions; + assert_equals(table.set(0, fn), undefined, "set() returns undefined."); + table.set(2, fn2); + table.set(4, fn); + assert_equal_to_array(table, [fn, null, fn2, null, fn]); + table.set(0, null); + assert_equal_to_array(table, [null, null, fn2, null, fn]); +}, "Basic"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + const { fn, fn2 } = functions; + table.set(0, fn); + table.set(2, fn2); + table.set(4, fn); + assert_equal_to_array(table, [fn, null, fn2, null, fn]); + table.grow(4); + assert_equal_to_array(table, [fn, null, fn2, null, fn, null, null, null, null]); +}, "Growing"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null, null, null, null, null]); + const { fn } = functions; + assert_throws_js("TypeError", () => table.set(-1, fn)); + assert_throws_js("RangeError", () => table.set(5, fn)); + assert_equal_to_array(table, [null, null, null, null, null]); +}, "Setting out-of-bounds"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + const invalidArguments = [undefined, true, false, "test", 7, NaN, {}]; + for (const argument of invalidArguments) { + assert_throws_js("TypeError", () => table.set(0, argument), `set(${format_value(argument)})`); + } + assert_equal_to_array(table, [null]); +}, "Setting non-function"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + const fn = function () {}; + assert_throws_js("TypeError", () => table.set(0, fn)); + assert_equal_to_array(table, [null]); +}, "Setting non-wasm function"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, [null]); + const fn = () => {}; + assert_throws_js("TypeError", () => table.set(0, fn)); + assert_equal_to_array(table, [null]); +}, "Setting non-wasm arrow function"); + +const outOfRangeIndices = [ + { value: undefined, name: "undefined" }, + { value: NaN, name: "NaN" }, + { value: Infinity, name: "Infinity" }, + { value: -Infinity, name: "-Infinity" }, + { value: -1, name: "-1" }, + { value: 0x100000000, name: "4294967296" }, + { value: 0x1000000000, name: "68719476736" }, + { value: "0x100000000", name: '"0x100000000"' }, + { value: { valueOf() { return 0x100000000; } }, name: "object" }, +]; +for (const { value, name } of outOfRangeIndices) { + test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.get(value)); + }, `Getting out-of-range argument: ${name}`); + + test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.set(value, null)); + }, `Setting out-of-range argument: ${name}`); +} + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + let called = 0; + const value = { + valueOf() { + called++; + return 0; + }, + }; + assert_throws_js("TypeError", () => table.set(value, {})); + assert_equals(called, 1); +}, "Order of argument conversion"); + +test(() => { + const { fn } = functions; + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_equals(table.get(0, {}), null); + assert_equals(table.set(0, fn, {}), undefined); +}, "get-set: Stray argument"); + +test(() => { + const fn = new WebAssembly.Instance(new WebAssembly.Module(binary("fn"))).exports.fn; + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument, fn); + assert_equals(table.get(0), fn); + table.set(0); + assert_equals(table.get(0), null); + table.set(0, fn); + assert_equals(table.get(0), fn); + assert_throws_js("TypeError", () => table.set(0, {})); + assert_throws_js("TypeError", () => table.set(0, 37)); +}, "Arguments for anyfunc table set"); + +test(() => { + const testObject = {}; + const argument = { element: "externref", initial: 1 }; + const table = new WebAssembly.Table(argument, testObject); + assert_equals(table.get(0), testObject); + table.set(0); + assert_equals(table.get(0), undefined); + table.set(0, testObject); + assert_equals(table.get(0), testObject); + table.set(0, 37); + assert_equals(table.get(0), 37); +}, "Arguments for externref table set"); + +// ── table/grow.any.js ───────────────────────────────────────────────────── +function nulls(n) { + return Array(n).fill(null); +} + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + // @ts-ignore: no delta + assert_throws_js("TypeError", () => table.grow()); +}, "Table grow: Missing arguments"); + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(5), "before"); + const result = table.grow(3); + assert_equals(result, 5); + assert_equal_to_array(table, nulls(8), "after"); +}, "Table grow: Basic"); + +test(() => { + const argument = { element: "anyfunc", initial: 3, maximum: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(3), "before"); + const result = table.grow(2); + assert_equals(result, 3); + assert_equal_to_array(table, nulls(5), "after"); +}, "Reached maximum"); + +test(() => { + const argument = { element: "anyfunc", initial: 2, maximum: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(2), "before"); + assert_throws_js("RangeError", () => table.grow(4)); + assert_equal_to_array(table, nulls(2), "after"); +}, "Exceeded maximum"); + +for (const { value, name } of outOfRangeIndices) { + test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.grow(value)); + }, `Table grow: Out-of-range argument: ${name}`); +} + +test(() => { + const argument = { element: "anyfunc", initial: 5 }; + const table = new WebAssembly.Table(argument); + assert_equal_to_array(table, nulls(5), "before"); + const result = table.grow(3, null, {}); + assert_equals(result, 5); + assert_equal_to_array(table, nulls(8), "after"); +}, "Table grow: Stray argument"); + +test(() => { + const fn = new WebAssembly.Instance(new WebAssembly.Module(binary("fn"))).exports.fn; + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument, fn); + assert_equal_to_array(table, [fn], "before"); + const result = table.grow(2, fn); + assert_equals(result, 1); + assert_equal_to_array(table, [fn, fn, fn], "after"); +}, "Grow with exported-function argument"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.grow(2, {})); +}, "Grow with non-function argument"); + +test(() => { + const argument = { element: "anyfunc", initial: 1 }; + const table = new WebAssembly.Table(argument); + assert_throws_js("TypeError", () => table.grow(2, () => true)); +}, "Grow with JS-function argument"); + +// ── module/imports.any.js and module/exports.any.js ────────────────────── +function assert_descriptors(actual, expected, keys) { + assert_true(Array.isArray(actual), "Should be array"); + assert_true(Object.isExtensible(actual), "isExtensible"); + assert_equals(actual.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_true(Object.isExtensible(actual[i]), "isExtensible"); + assert_array_equals(Object.keys(actual[i]), keys, "keys"); + for (const key of keys) assert_equals(actual[i][key], expected[i][key], key); + } +} +const assert_imports = (imports, expected) => assert_descriptors(imports, expected, ["module", "name", "kind"]); +const assert_exports = (exports, expected) => assert_descriptors(exports, expected, ["name", "kind"]); +const emptyModule = new WebAssembly.Module(binary("empty")); + +for (const which of ["imports", "exports"]) { + test(() => { + // @ts-ignore: no module + assert_throws_js("TypeError", () => (which === "imports" ? WebAssembly.Module.imports() : WebAssembly.Module.exports())); + }, `${which}: Missing arguments`); + + test(() => { + const invalidArguments = [undefined, null, true, "", 1, {}]; + for (const argument of invalidArguments) { + assert_throws_js( + "TypeError", + // @ts-ignore: not a module + () => (which === "imports" ? WebAssembly.Module.imports(argument) : WebAssembly.Module.exports(argument)), + `${which}(${format_value(argument)})`, + ); + } + }, `${which}: Non-Module arguments`); + + test(() => { + const result = which === "imports" ? WebAssembly.Module.imports(emptyModule) : WebAssembly.Module.exports(emptyModule); + assert_true(Array.isArray(result)); + }, `${which}: Return type`); + + test(() => { + if (which === "imports") assert_imports(WebAssembly.Module.imports(emptyModule), []); + else assert_exports(WebAssembly.Module.exports(emptyModule), []); + }, `${which}: Empty module`); + + test(() => { + if (which === "imports") + assert_not_equals(WebAssembly.Module.imports(emptyModule), WebAssembly.Module.imports(emptyModule)); + else assert_not_equals(WebAssembly.Module.exports(emptyModule), WebAssembly.Module.exports(emptyModule)); + }, `${which}: Empty module: array caching`); +} + +test(() => { + const module = new WebAssembly.Module(binary("imports")); + const imports = WebAssembly.Module.imports(module); + const expected = [ + { module: "module", kind: "function", name: "fn" }, + { module: "module", kind: "global", name: "global" }, + { module: "module", kind: "memory", name: "memory" }, + { module: "module", kind: "table", name: "table" }, + ]; + assert_imports(imports, expected); +}, "imports"); + +for (const { key, label, expected } of [ + { + key: "importsEmptyModule", + label: "imports with empty module name", + expected: [ + { module: "", kind: "function", name: "fn" }, + { module: "", kind: "global", name: "global" }, + { module: "", kind: "memory", name: "memory" }, + { module: "", kind: "table", name: "table" }, + ], + }, + { + key: "importsEmptyNames", + label: "imports with empty names", + expected: [ + { module: "a", kind: "function", name: "" }, + { module: "b", kind: "global", name: "" }, + { module: "c", kind: "memory", name: "" }, + { module: "d", kind: "table", name: "" }, + ], + }, + { + key: "importsEmptyBoth", + label: "imports with empty module names and names", + expected: [ + { module: "", kind: "function", name: "" }, + { module: "", kind: "global", name: "" }, + { module: "", kind: "memory", name: "" }, + { module: "", kind: "table", name: "" }, + ], + }, +]) { + test(() => { + const module = new WebAssembly.Module(binary(key)); + assert_imports(WebAssembly.Module.imports(module), expected); + }, label); +} + +test(() => { + assert_imports(WebAssembly.Module.imports(emptyModule, {}), []); +}, "imports: Stray argument"); + +test(() => { + const module = new WebAssembly.Module(binary("exports")); + const exports = WebAssembly.Module.exports(module); + const expected = [ + { kind: "function", name: "fn" }, + { kind: "function", name: "fn2" }, + { kind: "table", name: "table" }, + { kind: "global", name: "global" }, + { kind: "global", name: "global2" }, + { kind: "memory", name: "memory" }, + ]; + assert_exports(exports, expected); +}, "exports"); + +for (const { key, kind } of [ + { key: "exportsEmptyFunction", kind: "function" }, + { key: "exportsEmptyTable", kind: "table" }, + { key: "exportsEmptyGlobal", kind: "global" }, +]) { + test(() => { + const module = new WebAssembly.Module(binary(key)); + assert_exports(WebAssembly.Module.exports(module), [{ kind, name: "" }]); + }, `exports with empty name: ${kind}`); +} + +test(() => { + assert_exports(WebAssembly.Module.exports(emptyModule, {}), []); +}, "exports: Stray argument"); + +// ── module/customSections.any.js ────────────────────────────────────────── +function assert_sections(sections, expected) { + assert_true(Array.isArray(sections), "Should be array"); + assert_true(Object.isExtensible(sections), "isExtensible"); + assert_equals(sections.length, expected.length); + for (let i = 0; i < expected.length; ++i) { + assert_true(sections[i] instanceof ArrayBuffer, "ArrayBuffer"); + assert_array_equals(new Uint8Array(sections[i]), expected[i]); + } +} + +test(() => { + // @ts-ignore: no arguments + assert_throws_js("TypeError", () => WebAssembly.Module.customSections()); + // @ts-ignore: no name + assert_throws_js("TypeError", () => WebAssembly.Module.customSections(emptyModule)); +}, "customSections: Missing arguments"); + +test(() => { + const invalidArguments = [undefined, null, true, "", 1, {}]; + for (const argument of invalidArguments) { + // @ts-ignore: not a module + assert_throws_js("TypeError", () => WebAssembly.Module.customSections(argument, ""), `customSections(${format_value(argument)})`); + } +}, "customSections: Non-Module arguments"); + +test(() => { + assert_sections(WebAssembly.Module.customSections(emptyModule, ""), []); +}, "customSections: Empty module"); + +test(() => { + assert_not_equals(WebAssembly.Module.customSections(emptyModule, ""), WebAssembly.Module.customSections(emptyModule, "")); +}, "customSections: Empty module: array caching"); + +const bytes1 = [87, 101, 98, 65, 115, 115, 101, 109, 98, 108, 121]; +const bytes2 = [74, 83, 65, 80, 73]; + +test(() => { + const module = new WebAssembly.Module(binary("custom")); + assert_sections(WebAssembly.Module.customSections(module, "name"), [bytes1, bytes2]); + assert_sections(WebAssembly.Module.customSections(module, "foo"), [bytes1]); + assert_sections(WebAssembly.Module.customSections(module, ""), []); + assert_sections(WebAssembly.Module.customSections(module, "\0"), []); + assert_sections(WebAssembly.Module.customSections(module, "name\0"), []); + assert_sections(WebAssembly.Module.customSections(module, "foo\0"), []); +}, "Custom sections"); + +test(() => { + const name = "yee𐐷eey"; + const module = new WebAssembly.Module(binary("customSurrogates")); + assert_sections(WebAssembly.Module.customSections(module, name), [bytes1]); + assert_sections(WebAssembly.Module.customSections(module, "yee�eey"), []); + assert_sections(WebAssembly.Module.customSections(module, "yee��eey"), []); +}, "Custom sections with surrogate pairs"); + +test(() => { + const module = new WebAssembly.Module(binary("customReplacement")); + assert_sections(WebAssembly.Module.customSections(module, "name"), []); + assert_sections(WebAssembly.Module.customSections(module, "na�me"), [bytes1]); + assert_sections(WebAssembly.Module.customSections(module, "na\uDC01me"), []); +}, "Custom sections with U+FFFD"); + +test(() => { + assert_sections(WebAssembly.Module.customSections(emptyModule, "", {}), []); +}, "customSections: Stray argument"); + +test(() => { + const module = new WebAssembly.Module(binary("customCopy")); + const first = WebAssembly.Module.customSections(module, "copy"); + const second = WebAssembly.Module.customSections(module, "copy"); + assert_equals(first.length, 1); + assert_equals(second.length, 1); + assert_not_equals(first, second); + assert_not_equals(first[0], second[0]); +}, "Custom section buffers are independent copies"); + +test(() => { + const bytes = [0x11, 0x22, 0x33]; + const module = new WebAssembly.Module(binary("customCopy")); + const first = WebAssembly.Module.customSections(module, "copy"); + const second = WebAssembly.Module.customSections(module, "copy"); + const view = new Uint8Array(first[0]); + view[0] = 0xff; + view[1] = 0xee; + view[2] = 0xdd; + const again = WebAssembly.Module.customSections(module, "copy"); + assert_array_equals(new Uint8Array(again[0]), bytes); + assert_array_equals(new Uint8Array(second[0]), bytes); +}, "Mutating a custom section buffer does not change other copies"); + +console.log(`${passed} passed, ${failed} failed`); diff --git a/tests/coverage-fixtures/divergences.ts b/tests/coverage-fixtures/divergences.ts index 10b0a12e7..83c3ede49 100644 --- a/tests/coverage-fixtures/divergences.ts +++ b/tests/coverage-fixtures/divergences.ts @@ -47,7 +47,7 @@ function sizeOf(s: Shape): number { } console.log(sizeOf(new Circle())); -// The native date parser takes ISO 8601 with an explicit offset. +// Date strings parse with V8's grammar natively, so none of these diverge. console.log(Date.parse("Jan 2, 2024"), new Date("2024/01/02").getTime(), Date.parse("2024-01-02T03:04:05Z")); const stamps = ["2024-03-04T05:06:07Z", "March 4, 2024"]; for (const stamp of stamps) console.log(Date.parse(stamp)); diff --git a/tests/coverage-fixtures/wasm/ext.so b/tests/coverage-fixtures/wasm/ext.so new file mode 100644 index 0000000000000000000000000000000000000000..d8b6461083bc0ee6db05933d542c24b1df7dea13 GIT binary patch literal 36 rcmZQbEY4+QU|?WmWlUgTtY>CoWMF4!WKOLpVPN25V`O1)WakC|QojTH literal 0 HcmV?d00001 diff --git a/tests/coverage-fixtures/wasm/glue.js b/tests/coverage-fixtures/wasm/glue.js index 631560632..4a18af315 100644 --- a/tests/coverage-fixtures/wasm/glue.js +++ b/tests/coverage-fixtures/wasm/glue.js @@ -8,6 +8,15 @@ export function hasModule() { return existsSync(new URL("./add.wasm", import.meta.url).pathname); } +// WebAssembly shared libraries (quickjs-wasi's extensions) use .so: ext.so +// is a module, native.so is not and is not reported. +export function sharedLibraries() { + return [ + existsSync(new URL("./ext.so", import.meta.url).pathname), + existsSync(new URL("./native.so", import.meta.url).pathname), + ]; +} + export function inlineModule() { return ANSWER; } diff --git a/tests/coverage-fixtures/wasm/main.ts b/tests/coverage-fixtures/wasm/main.ts index a2d80b6b3..4e54283e7 100644 --- a/tests/coverage-fixtures/wasm/main.ts +++ b/tests/coverage-fixtures/wasm/main.ts @@ -1,9 +1,10 @@ // The WebAssembly modules this program can load are visible to the build in -// four ways (wasm/discover.ts): a path the compiler resolves (glue.js), an -// inline base64 literal (glue.js), a .wasm file shipped by a package whose -// code mentions WebAssembly (wasm-fixture-pkg), and the scriptc.wasm list in -// package.json (build/generated.wasm). -import { hasModule, inlineModule } from "./glue.js"; +// four ways (wasm/discover.ts): a path the compiler resolves (glue.js, +// including a .so shared library), an inline base64 literal (glue.js), a +// .wasm file shipped by a package whose code mentions WebAssembly +// (wasm-fixture-pkg), and the scriptc.wasm list in package.json +// (build/generated.wasm). +import { hasModule, inlineModule, sharedLibraries } from "./glue.js"; import { describeModule } from "wasm-fixture-pkg"; -console.log(hasModule(), inlineModule().length, describeModule()); +console.log(hasModule(), inlineModule().length, describeModule(), sharedLibraries()); diff --git a/tests/coverage-fixtures/wasm/native.so b/tests/coverage-fixtures/wasm/native.so new file mode 100644 index 0000000000000000000000000000000000000000..66d7d9f629a698a934bba1fce8f776473cf19b07 GIT binary patch literal 25 gcmb<-^>JfjWMs(8FHuNTC{HZTRmjaxDa}a*09SVhs{jB1 literal 0 HcmV?d00001 diff --git a/tests/diagnostics/arrow-bind.ts b/tests/diagnostics/arrow-bind.ts index c78db514b..0b677c5ef 100644 --- a/tests/diagnostics/arrow-bind.ts +++ b/tests/diagnostics/arrow-bind.ts @@ -1,4 +1,5 @@ -// Typed preset and rest signatures still exceed the callable adapter. +// Typed rest signatures still exceed the callable adapter. (A typed preset +// binds to a fixed signature and compiles.) const preset = ((value: number) => value).bind(null, 1); const rest = ((...values: number[]) => values.length).bind(null); export {}; diff --git a/tests/diagnostics/boot-path-node-surface-fences.ts b/tests/diagnostics/boot-path-node-surface-fences.ts new file mode 100644 index 000000000..146a32aa1 --- /dev/null +++ b/tests/diagnostics/boot-path-node-surface-fences.ts @@ -0,0 +1,23 @@ +// The boot-path Node surface keeps its boundaries: process.send answers +// presence checks but stays fenced as a stored value, a stream hook must +// declare Node's argument type or a supertype of it, and util.promisify +// over a function value needs an Error-typed callback error. +import { promisify } from "node:util"; +import { Writable } from "node:stream"; + +const send = process.send; +console.log(typeof send); + +class Counter extends Writable { + _write(chunk: number, _encoding: BufferEncoding, cb: (err?: Error | null) => void): void { + console.log(chunk); + cb(); + } +} +new Counter().end("x"); + +function legacy(cb: (code: number, value: string) => void): void { + cb(0, "v"); +} +const legacyAsync = promisify(legacy); +legacyAsync().then((v) => console.log(v)); diff --git a/tests/diagnostics/builtin-base-class-value-arguments.ts b/tests/diagnostics/builtin-base-class-value-arguments.ts new file mode 100644 index 000000000..db5be873b --- /dev/null +++ b/tests/diagnostics/builtin-base-class-value-arguments.ts @@ -0,0 +1,14 @@ +// A class without a constructor that extends a stream class inherits the +// runtime constructor, which takes no arguments here. Passing arguments +// through the class's value reports at the first argument, as direct +// construction does. +import { Transform } from "node:stream"; + +class Upper extends Transform {} +const registry = { upper: Upper }; +const upper = new registry.upper({ objectMode: true }); +upper.destroy(); + +const Kind = Upper; +const other = new Kind({ highWaterMark: 4 }); +other.destroy(); diff --git a/tests/diagnostics/builtin-constructor-members.ts b/tests/diagnostics/builtin-constructor-members.ts new file mode 100644 index 000000000..294fe397b --- /dev/null +++ b/tests/diagnostics/builtin-constructor-members.ts @@ -0,0 +1,14 @@ +// Builtin constructors have interned function values, but those values model +// only name, length and constructor. Every other static member or prototype +// read as a value reports SC2020 at the read, whether it is spelled directly, +// through globalThis or through an alias; it never reads undefined. +const mapPrototype = Map.prototype; +const datePrototype = Date.prototype; +const regExpPrototype = RegExp.prototype; +const promisePrototype = Promise.prototype; +const weakSetPrototype = WeakSet.prototype; +const frozen = Object.isFrozen; +const setPrototype = globalThis.Set.prototype; +const D = Date; +const aliasPrototype = D.prototype; +// End with a nonblank context line so diagnostic snapshots carry no trailing whitespace. diff --git a/tests/diagnostics/child-process.ts b/tests/diagnostics/child-process.ts index f96bce418..02b4349e8 100644 --- a/tests/diagnostics/child-process.ts +++ b/tests/diagnostics/child-process.ts @@ -42,3 +42,13 @@ const staticWorker = new URL("./child-process.ts", import.meta.url); fork(staticWorker, [], { serialization: "advanced" }); fork(staticWorker, [], { stdio: ["ignore", "ignore", "ignore", "ignore"] }); // Keep each fence on its own statement so diagnostics remain site-specific. +// An unknown 'data' chunk takes the setEncoding state established before +// the listener registers; a setEncoding placed elsewhere leaves it unclear. +const pipedOut = piped.stdout!; +pipedOut.on("data", (chunk: unknown) => console.log(String(chunk))); +pipedOut.setEncoding("utf8"); +// A setEncoding through a copy that can hold the same stream (a `let` +// binding here) leaves the state unclear as well. +let pipedErr = piped.stderr!; +pipedErr.setEncoding("utf8"); +piped.stderr!.on("data", (chunk: unknown) => console.log(String(chunk))); diff --git a/tests/diagnostics/cjs-member-alias-writes/main.ts b/tests/diagnostics/cjs-member-alias-writes/main.ts new file mode 100644 index 000000000..d3723c41a --- /dev/null +++ b/tests/diagnostics/cjs-member-alias-writes/main.ts @@ -0,0 +1,6 @@ +// A member export is read through its binding only when that binding still +// holds the exported value: a member that holds a reassigned binding, or one +// that both a module and its re-exporter write, is refused. +import { counter, version } from "metrics"; +console.log(counter); +console.log(version); diff --git a/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/index.d.ts b/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/index.d.ts new file mode 100644 index 000000000..c9c639246 --- /dev/null +++ b/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/index.d.ts @@ -0,0 +1,6 @@ +declare function metrics(): string; +declare namespace metrics { + const counter: number; + const version: string; +} +export = metrics; diff --git a/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/index.js b/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/index.js new file mode 100644 index 000000000..7449f0e8a --- /dev/null +++ b/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/index.js @@ -0,0 +1,3 @@ +"use strict"; +module.exports = require("./lib.js"); +module.exports.version = "2.0.0"; diff --git a/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/lib.js b/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/lib.js new file mode 100644 index 000000000..913fe8bd1 --- /dev/null +++ b/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/lib.js @@ -0,0 +1,8 @@ +"use strict"; +let counter = 1; +exports = module.exports = function metrics() { + return "metrics"; +}; +exports.counter = counter; +exports.version = "1.0.0"; +counter = 5; diff --git a/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/package.json b/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/package.json new file mode 100644 index 000000000..f97fe551f --- /dev/null +++ b/tests/diagnostics/cjs-member-alias-writes/node_modules/metrics/package.json @@ -0,0 +1,6 @@ +{ + "name": "metrics", + "version": "1.0.0", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/diagnostics/class-field-accessor-descriptor.ts b/tests/diagnostics/class-field-accessor-descriptor.ts new file mode 100644 index 000000000..66078f6c2 --- /dev/null +++ b/tests/diagnostics/class-field-accessor-descriptor.ts @@ -0,0 +1,14 @@ +// Object.defineProperty can change a declared class field's value and +// attributes, but not turn it into an accessor: a literal get or set +// descriptor is refused at compile time, naming the field. +class Value { + x = 1; +} + +const value = new Value(); +Object.defineProperty(value, "x", { + get() { + return 2; + }, +}); +console.log(value.x); diff --git a/tests/diagnostics/comptime.ts b/tests/diagnostics/comptime.ts index 9ba882aaf..645779ea7 100644 --- a/tests/diagnostics/comptime.ts +++ b/tests/diagnostics/comptime.ts @@ -20,3 +20,14 @@ const nested = comptime(() => comptime(() => 1)); const eventually = comptime(async () => 1); const lied = comptime(() => 1 as unknown as string); + +// A record type reachable from its own fields has no literal form. +interface Chain { + value: number; + next: Chain | null; +} +const chain: Chain = comptime(() => ({ value: 1, next: null }) as Chain); + +// A union reachable from itself through an array has no literal form either. +type Tree = number | Tree[]; +const tree: Tree = comptime((): Tree => [1, [2, [3]]]); diff --git a/tests/diagnostics/events-once-fences.ts b/tests/diagnostics/events-once-fences.ts index 2e5613f70..97bc76254 100644 --- a/tests/diagnostics/events-once-fences.ts +++ b/tests/diagnostics/events-once-fences.ts @@ -1,11 +1,14 @@ -// events.once lowers for EventEmitter instances and literal event names; -// these forms stay refused. +// events.once lowers for EventEmitter and stream instances, literal event +// names and a literal { signal } options object; these forms stay refused. import { EventEmitter, once } from "node:events"; +import { PassThrough } from "node:stream"; const ee = new EventEmitter(); const name: string = process.argv[2] ?? "ready"; +const options = {}; async function main(): Promise { - await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options + await once(ee, "ready", options); // options that are not an object literal await once(ee, name); // computed event name + await once(new PassThrough(), "data"); // a stream's 'data' event } void main(); diff --git a/tests/diagnostics/fromentries-aliased-entries.ts b/tests/diagnostics/fromentries-aliased-entries.ts new file mode 100644 index 000000000..ee5cc5338 --- /dev/null +++ b/tests/diagnostics/fromentries-aliased-entries.ts @@ -0,0 +1,13 @@ +// `Object.fromEntries(...) as T` keeps the object only when the entries' +// values are scalars or checked-dynamic values. Native arrays and records +// in the entries would be copied, severing their identity, so the +// assertion keeps the exact-shape fence (corpus 6467 covers the cases that +// lower). +type Scores = { a: number[]; b: number[] }; +const shared = [10]; +const scores = Object.fromEntries([ + ["a", shared], + ["b", shared], +]) as Scores; +scores.a.push(11); +console.log(shared.length, scores.b.length); diff --git a/tests/diagnostics/inferred-callback-drops-conditional.ts b/tests/diagnostics/inferred-callback-drops-conditional.ts new file mode 100644 index 000000000..f124beed9 --- /dev/null +++ b/tests/diagnostics/inferred-callback-drops-conditional.ts @@ -0,0 +1,12 @@ +type Plain = { name: string; n: number }; +type Item = Plain & { extra?: boolean }; +const plain: Plain[] = [{ name: "a", n: 1 }]; +function run(fn: () => T): T { + return fn(); +} +// The checker unifies both branches into Plain[]; the extended objects keep +// `extra` in Node, so converting them into Plain would drop it silently. +const items: Item[] = run(() => + plain.length === 0 ? plain : plain.map((x) => ({ ...x, extra: true })), +); +console.log(items.length); diff --git a/tests/diagnostics/inferred-callback-drops-satisfies.ts b/tests/diagnostics/inferred-callback-drops-satisfies.ts new file mode 100644 index 000000000..ad58237b8 --- /dev/null +++ b/tests/diagnostics/inferred-callback-drops-satisfies.ts @@ -0,0 +1,13 @@ +type Plain = { name: string; n: number }; +type Item = Plain & { extra?: boolean }; +const plain: Plain[] = [{ name: "a", n: 1 }]; +function run(fn: () => T): T { + return fn(); +} +// `satisfies` checks the return against Plain[] without changing it; the +// extended objects keep `extra` in Node. +const items: Item[] = run(() => { + if (plain.length === 0) return plain; + return plain.map((x) => ({ ...x, extra: true })) satisfies Plain[]; +}); +console.log(items.length); diff --git a/tests/diagnostics/inferred-callback-drops.ts b/tests/diagnostics/inferred-callback-drops.ts new file mode 100644 index 000000000..0d4808b6a --- /dev/null +++ b/tests/diagnostics/inferred-callback-drops.ts @@ -0,0 +1,13 @@ +type Plain = { name: string; n: number }; +type Item = Plain & { extra?: boolean }; +const plain: Plain[] = [{ name: "a", n: 1 }]; +function run(fn: () => T): T { + return fn(); +} +// The checker unifies both returns into Plain[]; the extended objects keep +// `extra` in Node, so converting them into Plain would drop it silently. +const items: Item[] = run(() => { + if (plain.length === 0) return plain; + return plain.map((x) => ({ ...x, extra: true })); +}); +console.log(items.length); diff --git a/tests/diagnostics/json-shape/main.ts b/tests/diagnostics/json-shape/main.ts index 1d9552506..a46338d52 100644 --- a/tests/diagnostics/json-shape/main.ts +++ b/tests/diagnostics/json-shape/main.ts @@ -1,7 +1,7 @@ -/* A JSON document whose shape doesn't bake: a null-valued field has no - * standalone STATIC type, so the import reports the dynamic-family type - * diagnostic (SC2011 at the import site — the shape maps in the embedded - * engine) and uses of the binding poison. */ +/* A JSON document whose value doesn't bake: `1e400` parses to Infinity, + * which no number literal spells, so the import reports the bake failure + * at the import site and uses of the binding poison. (Its null-valued + * field bakes as null.) */ import bad from "./nulled.json" with { type: "json" }; console.log(bad.name); diff --git a/tests/diagnostics/json-shape/nulled.json b/tests/diagnostics/json-shape/nulled.json index fa4738449..5e506a6af 100644 --- a/tests/diagnostics/json-shape/nulled.json +++ b/tests/diagnostics/json-shape/nulled.json @@ -1 +1 @@ -{ "name": "x", "deprecated": null } +{ "name": "x", "deprecated": null, "limit": 1e400 } diff --git a/tests/diagnostics/local-class-boundaries.ts b/tests/diagnostics/local-class-boundaries.ts index a82dfb869..2c43e9900 100644 --- a/tests/diagnostics/local-class-boundaries.ts +++ b/tests/diagnostics/local-class-boundaries.ts @@ -1,4 +1,4 @@ -function privateBrand() { return class { #value = 1; read(): number { return this.#value; } }; } +function privateBrand() { return class { #value = 1; has(other: object): boolean { return #value in other; } }; } function selfName() { return class Local { copy() { return new Local(); } }; } class Base {} function derived() { return class extends Base {}; } diff --git a/tests/diagnostics/local-class-type-query-brand.ts b/tests/diagnostics/local-class-type-query-brand.ts index 566304627..aeb905cad 100644 --- a/tests/diagnostics/local-class-type-query-brand.ts +++ b/tests/diagnostics/local-class-type-query-brand.ts @@ -1,11 +1,15 @@ // The same speculative element-type query, reaching a function-local class -// with a private brand through the function's returned array. +// with a private brand through the function's returned array. The brand is +// tested on another object, so the evaluations' private names must differ. function make() { class Box { #v = 1; read(): number { return this.#v; } + same(other: object): boolean { + return #v in other; + } } return [new Box()]; } diff --git a/tests/diagnostics/maybe-promise-fences.ts b/tests/diagnostics/maybe-promise-fences.ts index efe541c9e..1dcccaf08 100644 --- a/tests/diagnostics/maybe-promise-fences.ts +++ b/tests/diagnostics/maybe-promise-fences.ts @@ -30,8 +30,3 @@ console.log(typeof widened); // `instanceof Promise` on a value that is never a promise. const record = { v: 1 }; console.log(record instanceof Promise); - -// A variadic `unknown[]` slot takes closures with the same variadic ABI. -type HandlerFn = (...args: unknown[]) => Promise; -const fixed: HandlerFn = async (first: unknown) => first; -fixed(1, 2).then((v) => console.log(v)); diff --git a/tests/diagnostics/mock-fences.ts b/tests/diagnostics/mock-fences.ts index 4425dfa5f..ef92f366f 100644 --- a/tests/diagnostics/mock-fences.ts +++ b/tests/diagnostics/mock-fences.ts @@ -1,8 +1,9 @@ -// node:test mocks lower for mock.fn(original[, implementation]), +// node:test mocks lower for mock.fn([original[, implementation]]), // mock.method(object, methodName[, implementation]) on records, their // t.mock twins, and the trackers' restoreAll and reset. The rest of the // mock surface is refused, each with a named diagnostic: mock.fn() without -// an original; mock.method on a class instance or a builtin object, with +// an original for a function type whose return type does not include +// undefined; mock.method on a class instance or a builtin object, with // an options object, or with an implementation of another type when the // result is used; and functions whose union return type has no undefined // arm (the calls log records undefined for calls that throw). @@ -11,8 +12,11 @@ import { mock, test } from "node:test"; const add = mock.fn((a: number, b: number) => a + b); // lowers add(1, 2); -const empty = mock.fn(); // no original function -empty(); +const empty = mock.fn(); // lowers +empty(1, "two"); + +const counted = mock.fn<() => number>(); // no original, returns number +counted(); const obj = { value: () => 1 }; mock.method(obj, "value"); // lowers diff --git a/tests/diagnostics/module-out-of-scope.ts b/tests/diagnostics/module-out-of-scope.ts index 35fd3ee2d..e947a077d 100644 --- a/tests/diagnostics/module-out-of-scope.ts +++ b/tests/diagnostics/module-out-of-scope.ts @@ -1,13 +1,13 @@ // The out-of-scope builtin fences: modules a compiled binary is not going -// to serve say WHY — V8 introspection, the inspector protocol, the bundled -// SQLite engine, the deprecated domain module, and Node's underscore- -// prefixed implementation internals — while genuinely pending surface -// (stream/web here) keeps the plain not-yet wording. -import { getHeapStatistics } from "node:v8"; +// to serve say WHY — runtime code evaluation (vm), the inspector protocol, +// the bundled SQLite engine, the deprecated domain module, and Node's +// underscore-prefixed implementation internals — while genuinely pending +// surface (repl here) keeps the plain not-yet wording. +import { runInNewContext } from "node:vm"; import { create } from "domain"; import { DatabaseSync } from "node:sqlite"; import { HTTPParser } from "_http_common"; import { open } from "node:inspector"; -import { ReadableStream } from "node:stream/web"; +import { start } from "node:repl"; -console.log(getHeapStatistics, create, DatabaseSync, HTTPParser, open, ReadableStream); +console.log(runInNewContext, create, DatabaseSync, HTTPParser, open, start); diff --git a/tests/diagnostics/object-assign-class-copy.ts b/tests/diagnostics/object-assign-class-copy.ts new file mode 100644 index 000000000..4899306a9 --- /dev/null +++ b/tests/diagnostics/object-assign-class-copy.ts @@ -0,0 +1,11 @@ +// Object.assign copying a class instance into a fresh object: the result +// is a plain object holding the instance's own fields, never an instance, +// so a result the checker types as the class is refused rather than +// failing its checked cast at run time. +class Config { + b = 1; + a = 2; +} +const config = new Config(); +const copy = Object.assign({}, config); +console.log(JSON.stringify(copy), Object.keys(copy).join(",")); diff --git a/tests/diagnostics/object-literal-accessor-values.ts b/tests/diagnostics/object-literal-accessor-values.ts new file mode 100644 index 000000000..e4c456a76 --- /dev/null +++ b/tests/diagnostics/object-literal-accessor-values.ts @@ -0,0 +1,21 @@ +// Object literals with get/set accessors keep their closures in reserved +// slots, so their typed values have no JSON form: Node's JSON.stringify +// invokes the getter, which the typed serializer cannot do. + +const counter = { + count: 1, + get doubled(): number { + return 2; + }, +}; + +// JSON.stringify of the typed value would omit the getter's property. +console.log(JSON.stringify(counter)); + +// A literal written into an `any` slot directly keeps its accessor. +const direct: any = { + get value(): number { + return 3; + }, +}; +console.log(direct.value); diff --git a/tests/diagnostics/promise-all-fences.ts b/tests/diagnostics/promise-all-fences.ts index 89c29c308..c7ddc74bd 100644 --- a/tests/diagnostics/promise-all-fences.ts +++ b/tests/diagnostics/promise-all-fences.ts @@ -1,14 +1,8 @@ -// Promise.all, Promise.allSettled and Promise.any lower over ONE promise -// type (any Promise[] expression); heterogeneous array literals land -// on the checker's tuple overload and fence with the shared-type hint, -// and non-array arguments fence on their shape. -async function hetero(): Promise { - const pair = await Promise.all([ - new Promise((resolve) => resolve("s")), - new Promise((resolve) => resolve(1)), - ]); - console.log(pair.length); -} +// Promise.allSettled and Promise.any lower over ONE promise type (any +// Promise[] expression); their heterogeneous array literals land on +// the checker's tuple overload and fence with the shared-type hint, and +// non-array arguments fence on their shape. (Promise.all's tuple overload +// lowers: corpus 6482.) async function settled(): Promise { const results = await Promise.allSettled([ new Promise((resolve) => resolve("s")), @@ -22,6 +16,5 @@ function* pending(): Generator> { async function anyOfGenerator(): Promise { console.log(await Promise.any(pending())); } -hetero(); settled(); anyOfGenerator(); diff --git a/tests/diagnostics/stdlib-fence.ts b/tests/diagnostics/stdlib-fence.ts index 11b516b99..ae456af90 100644 --- a/tests/diagnostics/stdlib-fence.ts +++ b/tests/diagnostics/stdlib-fence.ts @@ -20,7 +20,7 @@ const parsedMs = Date.parse; const fmt = new Intl.NumberFormat(); // Constructors. -const d = new Date(2024, 0); // the local-time year/month field constructor stays fenced +const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced const mutableDate = new Date(0); mutableDate.setUTCFullYear(2024); // mutation is outside the read-only Date slice const sameDate = mutableDate === mutableDate; // scalar storage must not fake object identity diff --git a/tests/diagnostics/stream-completion-callback-error.ts b/tests/diagnostics/stream-completion-callback-error.ts new file mode 100644 index 000000000..eb73b485d --- /dev/null +++ b/tests/diagnostics/stream-completion-callback-error.ts @@ -0,0 +1,11 @@ +import { Writable } from "node:stream"; + +// Node passes write and end callbacks an error argument (null on success); +// the native callbacks receive none, so a callback that names it refuses. +const sink = new Writable({ + write(_chunk, _encoding, done) { + done(); + }, +}); +sink.write("a", (err) => console.log("written", err)); +sink.end((err?: Error | null) => console.log("ended", err)); diff --git a/tests/diagnostics/thread-slots-fences.ts b/tests/diagnostics/thread-slots-fences.ts index 7ec71db34..ed985c803 100644 --- a/tests/diagnostics/thread-slots-fences.ts +++ b/tests/diagnostics/thread-slots-fences.ts @@ -27,3 +27,8 @@ function local(slots: ThreadSlots, key: Key): State | undefined { const ctor = ThreadSlots; const getter = first.get; console.log(named, second, shaped, callbacks, local(first, new Key()), ctor, getter); + +// A read through an annotated value type the stored values can't be converted +// to: they are stored as records, which a read as the class can't convert. +const records: ThreadSlots = new ThreadSlots("fences.records"); +console.log(records.get(new Key())); diff --git a/tests/diagnostics/variadic-wrapper-fences.ts b/tests/diagnostics/variadic-wrapper-fences.ts new file mode 100644 index 000000000..d2ad65646 --- /dev/null +++ b/tests/diagnostics/variadic-wrapper-fences.ts @@ -0,0 +1,28 @@ +// Variadic generic wrappers specialize per call site; the forms that have +// no fixed signature or no static name keep precise refusals. +type AnyFn = (...args: any) => any; + +// `arguments` with no fixed signature to lower against. +const loose = function () { + return arguments[0]; +}; + +// `fn.name` where the argument's name is only known at run time. +function named(fn: F): string { + return fn.name; +} +function one(): number { + return 1; +} +function two(): number { + return 2; +} +const pick = (flag: boolean) => (flag ? one : two); +console.log(named(pick(true))); + +// Forwarding a fixed tuple to a variadic function value. +function forward(sink: (...xs: number[]) => number, ...args: A): number { + return sink.apply(null, args); +} +console.log(forward((...xs: number[]) => xs.length, 1, 2)); +export { loose }; diff --git a/tests/diagnostics/webassembly-fences.ts b/tests/diagnostics/webassembly-fences.ts index 56b2991de..fe90d41c9 100644 --- a/tests/diagnostics/webassembly-fences.ts +++ b/tests/diagnostics/webassembly-fences.ts @@ -1,30 +1,17 @@ -// No WebAssembly module compiles to native code yet: every reached use of -// the API refuses at its site, in each spelling, while declarations that -// only mention its types compile. -interface LoaderOptions { - wasm?: BufferSource | WebAssembly.Module; - imports?: WebAssembly.Imports; -} - -async function load(options: LoaderOptions, bytes: Uint8Array): Promise { - console.log(WebAssembly.validate(bytes)); +// The parts of the WebAssembly API scriptc does not implement yet refuse at +// each reached use, in each spelling. compile, instantiate, validate, the +// Module, Instance, Memory, Table and Global constructors, Module.imports, +// exports and customSections, and the error classes compile +// (tests/corpus/webassembly-*). +async function load(bytes: Uint8Array): Promise { const module = await WebAssembly.compile(bytes); - const { 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); + const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); + console.log(streamed.instance); + const Ctor = WebAssembly.Module; + console.log(Ctor); + const Memory = WebAssembly.Memory; + console.log(new Memory({ initial: 1 })); } -// pi's codemode spelling: the global object viewed through a cast. -interface WebAssemblyGlobal { - compile(bytes: Uint8Array): Promise; -} -const { WebAssembly: viewed } = globalThis as unknown as { WebAssembly: WebAssemblyGlobal }; -console.log(typeof viewed.compile); - -await load({}, new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])); +await load(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0])); diff --git a/tests/fixtures/esm-path-globals/lib/helper.ts b/tests/fixtures/esm-path-globals/lib/helper.ts new file mode 100644 index 000000000..61960c097 --- /dev/null +++ b/tests/fixtures/esm-path-globals/lib/helper.ts @@ -0,0 +1,2 @@ +export const helperDir: string = __dirname; +export const helperFile: string = __filename; diff --git a/tests/fixtures/esm-path-globals/main.ts b/tests/fixtures/esm-path-globals/main.ts new file mode 100644 index 000000000..d3d3c3b8a --- /dev/null +++ b/tests/fixtures/esm-path-globals/main.ts @@ -0,0 +1,12 @@ +// __dirname and __filename in TypeScript files with import syntax: each +// module reads its own source location, the same strings as +// import.meta.dirname and import.meta.filename. +import { basename, join } from "node:path"; +import { helperDir, helperFile } from "./lib/helper.ts"; + +console.log(typeof __dirname, typeof __filename); +console.log(basename(__dirname), basename(__filename)); +console.log(__dirname === import.meta.dirname, __filename === import.meta.filename); +console.log(join(__dirname, "lib") === helperDir, basename(helperFile)); +const home = typeof __dirname === "string" && __dirname.endsWith("/lib") ? join(__dirname, "..") : "/tmp"; +console.log(home); diff --git a/tests/fixtures/node-test/cases/mock-fn-default.ts b/tests/fixtures/node-test/cases/mock-fn-default.ts new file mode 100644 index 000000000..4666dfa5e --- /dev/null +++ b/tests/fixtures/node-test/cases/mock-fn-default.ts @@ -0,0 +1,72 @@ +// mock.fn() and t.mock.fn() without an original: the mock returns +// undefined and records the arguments of each call, whatever its callers +// pass. mockImplementation and mockImplementationOnce replace its +// behavior, and restore() brings back the function that returns undefined. +// mock.fn() records F's parameters, for a function type whose return +// type is void or includes undefined. +import assert from "node:assert"; +import { mock, test } from "node:test"; + +test("records calls with any arguments", () => { + const fn = mock.fn(); + fn(1, "two"); + fn(); + fn(true, null, [3]); + assert.strictEqual(fn.mock.callCount(), 3); + const [first, second, third] = fn.mock.calls; + assert.deepStrictEqual(first!.arguments, [1, "two"]); + assert.strictEqual(second!.arguments.length, 0); + assert.deepStrictEqual(third!.arguments, [true, null, [3]]); + assert.ok(first!.result === undefined); + assert.ok(first!.error === undefined); + assert.ok(first!.this === undefined); +}); + +test("changes implementations", () => { + const seen: string[] = []; + const fn = mock.fn(); + fn.mock.mockImplementation((...args: unknown[]) => { + seen.push(`impl ${args.length}`); + return undefined; + }); + fn.mock.mockImplementationOnce(() => { + seen.push("once"); + return undefined; + }); + fn("a"); + fn("a", "b"); + fn.mock.restore(); + fn("c"); + assert.deepStrictEqual(seen, ["once", "impl 2"]); + assert.strictEqual(fn.mock.callCount(), 3); + fn.mock.resetCalls(); + assert.strictEqual(fn.mock.callCount(), 0); +}); + +test("records a throwing implementation", () => { + const fn = mock.fn(); + fn.mock.mockImplementation(() => { + throw new Error("boom"); + }); + assert.throws(() => fn(7), { message: "boom" }); + const call = fn.mock.calls[0]!; + assert.deepStrictEqual(call.arguments, [7]); + assert.ok(call.error instanceof Error); +}); + +test("t.mock.fn() and typed mock.fn()", (t) => { + const fn = t.mock.fn(); + fn("x"); + assert.strictEqual(fn.mock.callCount(), 1); + const typed = t.mock.fn<(a: number, b: string) => void>(); + typed(1, "x"); + typed(2, "y"); + assert.strictEqual(typed.mock.callCount(), 2); + assert.strictEqual(typed.mock.calls[1]!.arguments[0], 2); + assert.strictEqual(typed.mock.calls[1]!.arguments[1], "y"); + const maybe = t.mock.fn<() => number | undefined>(); + assert.ok(maybe() === undefined); + maybe.mock.mockImplementation(() => 4); + assert.ok(maybe() === 4); + assert.ok(maybe.mock.calls[1]!.result === 4); +}); diff --git a/tests/fixtures/node-test/cases/thrown-values.ts b/tests/fixtures/node-test/cases/thrown-values.ts index 2793b62b5..52788c19f 100644 --- a/tests/fixtures/node-test/cases/thrown-values.ts +++ b/tests/fixtures/node-test/cases/thrown-values.ts @@ -1,6 +1,7 @@ // @exit: 1 // Failures from thrown non-Error values print as Node inspects them: a -// string quoted, a number and a boolean as written. +// string quoted, a number and a boolean as written, negative zero as -0, +// and records, arrays, null and undefined as util.inspect renders them. import { test } from "node:test"; test("throws a string", () => { @@ -12,6 +13,21 @@ test("throws a string with a quote", () => { test("throws a number", () => { throw 42; }); +test("throws negative zero", () => { + throw -0; +}); test("throws a boolean", () => { throw false; }); +test("throws a record", () => { + throw { code: 42, nested: { list: [1, 2] } }; +}); +test("throws an array", () => { + throw [1, "two", { three: 3 }]; +}); +test("throws null", () => { + throw null; +}); +test("throws undefined", () => { + throw undefined; +}); diff --git a/tests/fixtures/node-types/README.md b/tests/fixtures/node-types/README.md index 0784c57fe..243ce0b81 100644 --- a/tests/fixtures/node-types/README.md +++ b/tests/fixtures/node-types/README.md @@ -21,6 +21,7 @@ What the fixture pins (see tests/harness/project-config.test.ts): (process.uptime, Buffer.from, setInterval) reports the SC2020-family fence naming @types/node, instead of typechecking its way into a broken binary — and never a raw "Cannot find name" error. +- `event-loop-metrics.ts` and `timeout-type.ts` — performance.eventLoopUtilization, monitorEventLoopDelay's IntervalHistogram and NodeJS.Timeout as declared types lower under @types/node exactly as under the fallback declarations. - `source-import/` — an imported TypeScript source may use the `RequestInfo` global supplied by TypeScript's default DOM lib while scriptc adopts the project's @types/node declarations. Projects WITHOUT @types/node (every other fixture and the whole corpus) keep diff --git a/tests/fixtures/node-types/child-stdin-fences.ts b/tests/fixtures/node-types/child-stdin-fences.ts deleted file mode 100644 index 0859d8513..000000000 --- a/tests/fixtures/node-types/child-stdin-fences.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { spawn } from "node:child_process"; - -const child = spawn("node", []); -child.stdin.write("x", "utf8"); -child.stdin.end("x"); -child.stdin.destroy(new Error("stop")); diff --git a/tests/fixtures/node-types/child-stdin-streams.ts b/tests/fixtures/node-types/child-stdin-streams.ts new file mode 100644 index 000000000..c433d7bfb --- /dev/null +++ b/tests/fixtures/node-types/child-stdin-streams.ts @@ -0,0 +1,40 @@ +// child.stdin forms beyond write(string): a write with an encoding, end +// with a final chunk, and destroy with an error observed by an 'error' +// listener. They compile through the general stream representation, where +// child.stdin is the child's bridged Writable. +import { spawn } from "node:child_process"; + +function feed(): Promise { + return new Promise((resolve) => { + const child = spawn("cat", [], { stdio: ["pipe", "pipe", "inherit"] }); + let out = ""; + child.stdout.on("data", (chunk: Buffer) => { + out += chunk.toString(); + }); + child.on("close", (code) => { + console.log("fed", JSON.stringify(out), code); + resolve(); + }); + child.stdin.write("first ", "utf8"); + child.stdin.end("second\n"); + }); +} + +function abort(): Promise { + return new Promise((resolve) => { + const child = spawn("cat", [], { stdio: ["pipe", "ignore", "inherit"] }); + child.stdin.on("error", (err) => console.log("stdin error", err.message)); + child.on("close", (code) => { + console.log("aborted", code); + resolve(); + }); + child.stdin.destroy(new Error("stop")); + }); +} + +async function main(): Promise { + await feed(); + await abort(); +} + +void main(); diff --git a/tests/fixtures/node-types/event-loop-metrics.ts b/tests/fixtures/node-types/event-loop-metrics.ts new file mode 100644 index 000000000..f5c97c41b --- /dev/null +++ b/tests/fixtures/node-types/event-loop-metrics.ts @@ -0,0 +1,49 @@ +// Event-loop monitoring under @types/node: monitorEventLoopDelay's histogram, +// eventLoopUtilization deltas, and an unref'd interval typed NodeJS.Timeout. +import { monitorEventLoopDelay, performance } from "node:perf_hooks"; + +const histogram = monitorEventLoopDelay({ resolution: 10 }); +console.log("enable", histogram.enable(), histogram.enable()); +const first = performance.eventLoopUtilization(); +console.log("before loop", first.idle, first.active, first.utilization); + +let last = performance.eventLoopUtilization(); +let ticks = 0; +// The unref'd interval keeps nothing alive; this timer holds the loop open +// until the third sample. +const keepAlive = setTimeout(() => console.log("never"), 10_000); +const interval: NodeJS.Timeout = setInterval(() => { + const now = performance.eventLoopUtilization(); + const delta = performance.eventLoopUtilization(now, last); + last = now; + ticks++; + const p50 = histogram.percentile(50); + const p99 = histogram.percentile(99); + console.log( + "tick", + ticks, + delta.utilization >= 0 && delta.utilization <= 1, + delta.idle >= 0, + p50 > 0, + p99 >= p50, + histogram.max >= p99, + histogram.min <= p50, + histogram.count > 0, + ); + histogram.reset(); + console.log("after reset", histogram.count, histogram.max, histogram.min, Number.isNaN(histogram.mean)); + if (ticks === 3) { + clearInterval(interval); + clearTimeout(keepAlive); + console.log("disable", histogram.disable(), histogram.disable()); + const since = performance.eventLoopUtilization(first); + console.log("since", since.utilization > 0 && since.utilization <= 1, since.idle > 0); + try { + histogram.percentile(0); + } catch (e) { + console.log((e as Error).message, (e as { code?: string }).code); + } + } +}, 50); +interval.unref(); +console.log("hasRef", interval.hasRef()); diff --git a/tests/fixtures/node-types/events-once-signal-race.ts b/tests/fixtures/node-types/events-once-signal-race.ts new file mode 100644 index 000000000..4bc1d2d36 --- /dev/null +++ b/tests/fixtures/node-types/events-once-signal-race.ts @@ -0,0 +1,86 @@ +// events.once under @types/node, which types the result Promise: +// a NodeJS.ReadableStream slot and a PassThrough raced with an AbortSignal +// that the finally block aborts (the entries that throw are +// Promise, so the race result is the any[] of the 'end' entry), an +// EventEmitter race, and a Transform whose completion callback is declared +// with the broad `Function` type. +import { EventEmitter, once } from "node:events"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Transform } from "node:stream"; + +function copyPart(src: NodeJS.ReadableStream, out: PassThrough) { + const ac = new AbortController(); + let failed = false; + src.pipe(out, { end: false }); + return Promise.race([ + once(src, "end", { signal: ac.signal }), + once(src, "error", { signal: ac.signal }).then(([error]) => { + failed = true; + throw error; + }), + once(out, "error", { signal: ac.signal }).then(([error]) => { + failed = true; + throw error; + }), + once(out, "close", { signal: ac.signal }).then(() => { + failed = true; + throw new Error("output closed before the part ended"); + }), + ]).finally(() => { + ac.abort(); + src.unpipe(out); + console.log("part done", failed); + }); +} + +function waitFor(a: EventEmitter) { + const ac = new AbortController(); + return Promise.race([ + once(a, "done", { signal: ac.signal }), + once(a, "error", { signal: ac.signal }).then(([error]) => { + throw error; + }), + ]).finally(() => ac.abort()); +} + +class Prefixer extends Transform { + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type + _transform(chunk: Buffer, _encoding: string, cb: Function) { + cb(null, "> " + chunk.toString()); + } +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-once-race-")); + const file = path.join(dir, "a.txt"); + fs.writeFileSync(file, "part\n"); + const out = new PassThrough(); + let text = ""; + out.on("data", (chunk: Buffer) => { + text += chunk.toString(); + }); + const ended = await copyPart(fs.createReadStream(file), out); + console.log("ended", ended.length); + await copyPart(fs.createReadStream(file).pipe(new Prefixer()), out); + console.log(JSON.stringify(text)); + try { + await copyPart(fs.createReadStream(path.join(dir, "missing.txt")), out); + } catch (err) { + console.log("missing", (err as NodeJS.ErrnoException).code); + } + const a = new EventEmitter(); + setTimeout(() => a.emit("done", 1, "two"), 1); + const args = (await waitFor(a)) as unknown[]; + console.log("done", JSON.stringify(args), a.listenerCount("done"), a.listenerCount("error")); + setTimeout(() => a.emit("error", new Error("bad")), 1); + try { + await waitFor(a); + } catch (err) { + console.log("caught", (err as Error).message, a.listenerCount("done")); + } + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/fixtures/node-types/fetch-init-members.ts b/tests/fixtures/node-types/fetch-init-members.ts new file mode 100644 index 000000000..371941e5a --- /dev/null +++ b/tests/fixtures/node-types/fetch-init-members.ts @@ -0,0 +1,68 @@ +// Options derived from RequestInit under @types/node: an interface that +// extends Omit holds fetch's BodyInit and HeadersInit +// members, which are destructured, narrowed and passed back into fetch, +// with the remaining members carried as an Omit<...> of RequestInit. +import * as http from "node:http"; +import type { AddressInfo } from "node:net"; + +type PassthroughRequestInit = Omit; +type FetchBody = NonNullable; + +interface PostOptions extends PassthroughRequestInit { + timeoutMs?: number; +} + +function describe(body: FetchBody | null | undefined): string { + return typeof body === "string" ? `text(${body.length})` : body == null ? "none" : "binary or form"; +} + +async function send(params: { + port: number; + method: string; + body: FetchBody | null | undefined; + headers: RequestInit["headers"]; + init: Omit; + timeoutMs: number; +}): Promise { + const { port, method, body, headers, init, timeoutMs } = params; + const fetchInit: RequestInit = { + ...init, + method, + headers, + body: body ?? undefined, + signal: AbortSignal.timeout(timeoutMs), + }; + return fetch(`http://127.0.0.1:${port}/`, fetchInit); +} + +async function post(port: number, options: PostOptions = {}): Promise { + const { timeoutMs, body, method, headers, ...rest } = options; + const res = await send({ + port, + method: method ?? "POST", + body, + headers, + init: rest, + timeoutMs: timeoutMs ?? 5000, + }); + return `${describe(body)} -> ${res.status} ${await res.text()}`; +} + +const server = http.createServer((req, res) => { + let data = ""; + req.on("data", (chunk: Buffer) => { + data += chunk.toString(); + }); + req.on("end", () => { + res.end(`${req.method} type=${req.headers["content-type"]} x=${req.headers["x-n"]} body=${data}`); + }); +}); + +server.listen(0, "127.0.0.1", async () => { + const port = (server.address() as AddressInfo).port; + console.log(await post(port, { body: "plain", headers: { "x-n": "1" } })); + console.log(await post(port, { body: new URLSearchParams("a=1&b=2"), headers: [["x-n", "2"]] })); + console.log(await post(port, { body: Buffer.from("bytes"), method: "PUT", timeoutMs: 2000 })); + console.log(await post(port, { method: "DELETE", headers: new Headers({ "x-n": "4" }) })); + server.close(); +}); diff --git a/tests/fixtures/node-types/http-socket-path.ts b/tests/fixtures/node-types/http-socket-path.ts new file mode 100644 index 000000000..9282ed052 --- /dev/null +++ b/tests/fixtures/node-types/http-socket-path.ts @@ -0,0 +1,80 @@ +// An HTTP client that reaches its API over a Unix socket unless an +// endpoint URL is configured, typed by @types/node: the location is an +// http.RequestOptions chosen at run time, spread into each request's +// options with a path, method, headers, timeout and an optional signal. +// server.address() answers the socket path (a TCP server's the record), and +// server.close callbacks take Node's ERR_SERVER_NOT_RUNNING error. +import * as http from "node:http"; +import { existsSync } from "node:fs"; +import type { AddressInfo } from "node:net"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const socketPath: string = join(tmpdir(), `scriptc-api-${process.pid}.sock`); +const endpoint = process.env.NO_SUCH_API_ENDPOINT_VAR ? new URL(process.env.NO_SUCH_API_ENDPOINT_VAR) : undefined; +const location: http.RequestOptions = endpoint + ? { port: endpoint.port, host: endpoint.hostname } + : { socketPath }; + +function call(path: string, body: string, timeout: number, ac?: AbortController): Promise { + return new Promise((resolve, reject) => { + const headers: Record = { + "content-type": "application/json", + "content-length": Buffer.byteLength(body), + }; + const options = { + ...location, + path, + method: "POST", + headers, + timeout, + signal: ac?.signal, + }; + const request = http.request(options, (res) => { + let data = ""; + res.on("data", (part: Buffer) => { + data += part.toString(); + }); + res.on("error", reject); + res.on("end", () => resolve(`${res.statusCode} ${data}`)); + }); + request.on("error", reject); + request.on("timeout", () => request.destroy(new Error("timed out"))); + request.end(body); + }); +} + +const server = http.createServer((req, res) => { + let body = ""; + req.on("data", (part: Buffer) => { + body += part.toString(); + }); + req.on("end", () => { + res.end(`${req.method} ${req.url} host=${req.headers.host} body=${body}`); + }); +}); + +function describeAddress(address: string | AddressInfo | null): string { + if (address === null) return "null"; + return typeof address === "string" ? address.split(socketPath).join("") : `${address.family} port>0=${address.port > 0}`; +} + +server.listen(socketPath, async () => { + console.log("address", describeAddress(server.address())); + const tcp = http.createServer(); + await new Promise((resolve) => tcp.listen(0, "127.0.0.1", () => resolve())); + console.log("tcp address", describeAddress(tcp.address())); + tcp.close(); + console.log(await call("/v1/start", JSON.stringify({ id: 1 }), 5000)); + console.log(await call("/v1/status", JSON.stringify({ id: 2 }), 5000, new AbortController())); + try { + const ac = new AbortController(); + ac.abort(); + await call("/v1/never", "{}", 5000, ac); + } catch (err) { + console.log("aborted", (err as Error).name); + } + server.close((err) => console.log("closed", err === undefined, existsSync(socketPath))); + server.close((err) => console.log("closed again", err?.name, (err as NodeJS.ErrnoException)?.code, err?.message)); + http.createServer().close((err) => console.log("never listened", (err as NodeJS.ErrnoException)?.code)); +}); diff --git a/tests/fixtures/node-types/import-fences.ts b/tests/fixtures/node-types/import-fences.ts index 1109a2fa4..15cd1f005 100644 --- a/tests/fixtures/node-types/import-fences.ts +++ b/tests/fixtures/node-types/import-fences.ts @@ -2,12 +2,12 @@ * fence joins the blockers, the imported bindings poison at their use * sites (grouping with the import line), and every other statement still * counts — the report shows a percentage. Builds still fail on the import - * exactly as before. (net, then dgram, graduated to supported modules, so - * the out-of-scope "v8" carries this test now.) */ -import { getHeapStatistics } from "v8"; + * exactly as before. (net, dgram, then v8 graduated to supported modules, + * so the out-of-scope "inspector" carries this test now.) */ +import { open } from "inspector"; import { join } from "node:path"; const cmd = join("/usr", "bin", "afplay"); console.log(cmd); -getHeapStatistics(); +open(); console.log("after"); diff --git a/tests/fixtures/node-types/lookup-and-header-entries.ts b/tests/fixtures/node-types/lookup-and-header-entries.ts new file mode 100644 index 000000000..a87f612d7 --- /dev/null +++ b/tests/fixtures/node-types/lookup-and-header-entries.ts @@ -0,0 +1,47 @@ +// A dns lookup function whose options parameter is Node's lookup-option +// union (`LookupOneOptions | LookupAllOptions | number | undefined`) passes +// the options to dns.lookup as they are, and Object.entries over a header +// record cast to a narrower value type reads every entry. +import * as dns from "node:dns"; +import type { OutgoingHttpHeaders } from "node:http"; + +type LookupCallback = ( + err: NodeJS.ErrnoException | null, + address: string | dns.LookupAddress[], + family?: number, +) => void; + +function createLookup(): ( + hostname: string, + opts: dns.LookupOneOptions | dns.LookupAllOptions | number | undefined, + callback: LookupCallback, +) => void { + return function lookup(hostname, opts, callback) { + dns.lookup(hostname, opts as dns.LookupOneOptions, (err, address, family) => { + if (err) return callback(err, "", 0); + callback(null, address, family); + }); + }; +} + +type HeadersLike = Headers | [string, string][] | OutgoingHttpHeaders; + +function flatten(input: HeadersLike | undefined): string[] { + const out: string[] = []; + if (!input || input instanceof Headers || Array.isArray(input)) return out; + for (const [name, value] of Object.entries(input as Record)) { + if (value === undefined) continue; + if (Array.isArray(value)) for (const v of value) out.push(`${name}=${v}`); + else out.push(`${name}=${value}`); + } + return out; +} + +const lookup = createLookup(); +lookup("localhost", { family: 4 }, (err, address, family) => { + console.log("one", err, address, family); + lookup("127.0.0.1", 4, (err3, address3, family3) => { + console.log("family", err3, address3, family3); + console.log(flatten({ accept: "text/plain", "x-list": ["a", "b"], "x-skip": undefined }).join(",")); + }); +}); diff --git a/tests/fixtures/node-types/node-stream-interfaces.ts b/tests/fixtures/node-types/node-stream-interfaces.ts new file mode 100644 index 000000000..2809dc315 --- /dev/null +++ b/tests/fixtures/node-types/node-stream-interfaces.ts @@ -0,0 +1,61 @@ +// @types/node's stream interfaces fed by every producer: NodeJS.ReadableStream +// and stream.Readable slots take file streams, a gunzip Transform and a +// child's stdout; NodeJS.WritableStream and stream.Writable slots take file +// streams and process.stdout. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { Readable, Writable } from "node:stream"; +import * as zlib from "node:zlib"; + +function drain(label: string, src: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + let text = ""; + src.on("data", (chunk: Buffer | string) => { + text += chunk.toString(); + }); + src.on("error", reject); + src.on("end", () => { + console.log(label, "end"); + resolve(text); + }); + }); +} + +function emit(dst: NodeJS.WritableStream, line: string): void { + dst.write(`${line}\n`); +} + +function finish(dst: Writable): Promise { + return new Promise((resolve) => dst.end(() => resolve())); +} + +async function main(): Promise { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "scriptc-node-types-streams-")); + const plain = path.join(dir, "plain.txt"); + const packed = path.join(dir, "packed.gz"); + fs.writeFileSync(plain, "from a file\n"); + fs.writeFileSync(packed, zlib.gzipSync(Buffer.from("from gunzip\n"))); + const file: Readable = fs.createReadStream(plain); + console.log(JSON.stringify(await drain("file", file))); + console.log(JSON.stringify(await drain("gunzip", fs.createReadStream(packed).pipe(zlib.createGunzip())))); + const child = spawn("sh", ["-c", "printf 'from a child\\n'"], { stdio: ["ignore", "pipe", "inherit"] }); + if (child.stdout) console.log(JSON.stringify(await drain("child", child.stdout))); + + const target = path.join(dir, "out.txt"); + const out: Writable = fs.createWriteStream(target); + emit(out, "to a file"); + emit(process.stdout, "to stdout"); + await finish(out); + console.log(JSON.stringify(fs.readFileSync(target, "utf8"))); + await new Promise((resolve) => { + const piped = fs.createReadStream(plain); + piped.on("end", () => resolve()); + piped.pipe(process.stdout); + }); + emit(process.stdout, "stdout still open"); + fs.rmSync(dir, { recursive: true }); +} + +void main(); diff --git a/tests/fixtures/node-types/process-versions-absent.ts b/tests/fixtures/node-types/process-versions-absent.ts new file mode 100644 index 000000000..8a59f7a58 --- /dev/null +++ b/tests/fixtures/node-types/process-versions-absent.ts @@ -0,0 +1,35 @@ +/* @types/node declares http_parser, v8, uv, zlib, ares and modules as + * strings. A compiled binary provides only node and openssl, and Node 24 + * has no http_parser either, so those entries read undefined and compare + * like it. Checks on the others print agreement between equality, typeof + * and `in`, which holds whichever entries the running process has. */ +const versions = process.versions; + +console.log(process.versions.http_parser === undefined, process.versions['http_parser'] === undefined); +console.log(versions.http_parser == null, typeof versions.http_parser, versions.http_parser ?? 'none'); +const parser = versions.http_parser; +console.log(parser === undefined ? 'no http_parser' : parser); +if (versions.http_parser) console.log('http_parser truthy'); +else console.log('http_parser falsy'); + +console.log( + (process.versions.v8 === undefined) === !('v8' in process.versions), + (process.versions['uv'] !== undefined) === 'uv' in versions, + (versions.zlib == null) === (typeof versions.zlib === 'undefined'), + (versions.ares === undefined) === (typeof versions.ares !== 'string'), +); +const names = ['v8', 'uv', 'zlib', 'ares', 'modules', 'node', 'openssl'] as const; +for (const name of names) { + const value = versions[name]; + const absent = value === undefined; + console.log( + name, + absent === !(name in versions), + absent === (typeof value === 'undefined'), + ((value ?? '') === '') === absent, + ); +} +const v8 = versions.v8; +console.log(v8 === undefined || v8.length > 0); +console.log(typeof (versions.modules || 'fallback')); +console.log(typeof versions.node, versions.openssl !== undefined, versions.node === process.version.slice(1)); diff --git a/tests/fixtures/node-types/promisify-callback-functions.ts b/tests/fixtures/node-types/promisify-callback-functions.ts new file mode 100644 index 000000000..78be5e3bd --- /dev/null +++ b/tests/fixtures/node-types/promisify-callback-functions.ts @@ -0,0 +1,67 @@ +// util.promisify over typed callback-last functions with leading arguments +// under @types/node: bound methods, a function declaration, rejection from +// the callback error, a synchronous throw, and asynchronous settlement. +import { promisify } from "node:util"; + +class Client { + private closed = false; + private readonly name: string; + constructor(name: string) { + this.name = name; + } + close(callback: (error?: Error) => void): void { + setTimeout(() => { + if (this.closed) { + callback(new Error(`${this.name} already closed`)); + return; + } + this.closed = true; + callback(); + }, 1); + } + fetch(key: string, callback: (error: Error | null, value?: string) => void): void { + setImmediate(() => { + if (key === "") callback(new RangeError("empty key")); + else callback(null, `${this.name}:${key}`); + }); + } +} + +function sum(a: number, b: number, done: (err: Error | null, total: number) => void): void { + if (a < 0) throw new TypeError("negative"); + process.nextTick(() => done(null, a + b)); +} + +async function main(): Promise { + const c = new Client("db"); + const closeAsync = promisify(c.close.bind(c)); + await closeAsync(); + console.log("closed once"); + try { + await closeAsync(); + } catch (e) { + console.log("second close:", (e as Error).message); + } + const fetchAsync = promisify(c.fetch.bind(c)); + console.log(await fetchAsync("k")); + try { + await fetchAsync(""); + } catch (e) { + console.log("fetch:", (e as Error).name, (e as Error).message); + } + const sumAsync = promisify(sum); + console.log("sum", await sumAsync(2, 3)); + try { + await sumAsync(-1, 3); + } catch (e) { + console.log("sync throw:", (e as Error).name, (e as Error).message); + } + await promisify(new Client("inline").close.bind(new Client("inline")))(); + console.log("inline done"); + const order: string[] = []; + const p = sumAsync(1, 1).then((v) => order.push(`then ${v}`)); + order.push("after call"); + await p; + console.log(order.join(", ")); +} +void main(); diff --git a/tests/fixtures/node-types/stream-construction.ts b/tests/fixtures/node-types/stream-construction.ts new file mode 100644 index 000000000..a28643f7c --- /dev/null +++ b/tests/fixtures/node-types/stream-construction.ts @@ -0,0 +1,121 @@ +// Streams constructed under @types/node: option-bag Transform and +// PassThrough streams with @types/node's TransformCallback, a gunzip stage, +// fs.ReadStream listeners, and child-process listeners declared with +// unknown chunks and non-null exit types. +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { PassThrough, Transform } from "node:stream"; +import { createGunzip, gzipSync } from "node:zlib"; + +interface Progress { + bytes: number; + chunks: number; +} + +function createCounter(progress: Progress) { + return new Transform({ + transform(chunk, _encoding, callback) { + const length = typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length; + progress.bytes += length; + progress.chunks++; + callback(null, chunk); + }, + flush(callback) { + callback(); + }, + }); +} + +function gunzipThrough(payload: Buffer): Promise { + return new Promise((resolve) => { + const progress: Progress = { bytes: 0, chunks: 0 }; + const counter = createCounter(progress); + const gunzip = createGunzip(); + const out = new PassThrough(); + const parts: string[] = []; + counter.pipe(gunzip).pipe(out); + out.on("data", (d: Buffer) => parts.push(d.toString())); + out.on("end", () => resolve(`${parts.join("")} ${progress.bytes > 0} ${progress.chunks}`)); + counter.end(payload); + }); +} + +function readFileStream(file: string): Promise { + return new Promise((resolve) => { + const stream = fs.createReadStream(file); + let text = ""; + const release = () => console.log("released"); + stream.on("close", release); + stream.on("error", release); + stream.on("data", (chunk) => { + text += chunk.toString(); + }); + stream.on("end", () => resolve(text)); + }); +} + +function run(script: string): Promise { + return new Promise((resolve, reject) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + let stdout = ""; + let stderr = ""; + if (child.stdout) { + child.stdout.setEncoding("utf8"); + child.stdout.on("data", (c) => { + stdout += c; + }); + } + if (child.stderr) { + child.stderr.on("data", (c) => { + const chunk = String(c); + stderr += chunk; + }); + } + child.on("error", reject); + child.on("close", (code: number, signal: string) => { + if (code === 0) return resolve(stdout); + reject(new Error(`Exited with ${code || signal}: ${stderr.trim()}`)); + }); + }); +} + +// setEncoding returns its stream: a listener on the returned value, or on +// the stream after `const out = stream.setEncoding(...)`, receives strings. +function readEncoded(script: string): Promise { + return new Promise((resolve) => { + const child = spawn("/bin/sh", ["-c", script], { stdio: "pipe" }); + let outText = ""; + let errText = ""; + const out = child.stdout.setEncoding("utf8"); + out.on("data", (c: unknown) => { + outText += typeof c + ":" + String(c) + ";"; + }); + const err = child.stderr.setEncoding("utf8"); + child.stderr.on("data", (c: unknown) => { + errText += typeof c + ":" + String(c) + ";"; + }); + void err; + child.on("close", () => resolve(outText + " err " + errText)); + }); +} + +async function main(): Promise { + console.log("gunzip", await gunzipThrough(gzipSync(Buffer.from("payload")))); + const file = path.join(os.tmpdir(), `scriptc-stream-construction-${process.pid}.txt`); + fs.writeFileSync(file, "file text\n"); + console.log("file", JSON.stringify(await readFileStream(file))); + fs.unlinkSync(file); + console.log("run", JSON.stringify(await run("echo hi; echo warn >&2"))); + console.log("encoded", JSON.stringify(await readEncoded("printf hi; printf warn >&2"))); + for (const script of ["echo bad >&2; exit 3", "kill -TERM $$"]) { + try { + await run(script); + } catch (e) { + console.log("error", (e as Error).message); + } + } +} + +void main(); diff --git a/tests/fixtures/node-types/stream-subclass-hooks.ts b/tests/fixtures/node-types/stream-subclass-hooks.ts new file mode 100644 index 000000000..0e7cfbad5 --- /dev/null +++ b/tests/fixtures/node-types/stream-subclass-hooks.ts @@ -0,0 +1,46 @@ +// Stream subclasses under @types/node: a Writable whose hooks declare a +// supertype of Node's chunk type, a Transform, and an erroring Writable. +import { Transform, Writable } from "node:stream"; + +class LineSink extends Writable { + private rest = ""; + readonly lines: string[] = []; + _write(chunk: Buffer | string, encoding: BufferEncoding, cb: (err?: Error | null) => void): void { + console.log("chunk", typeof chunk, Buffer.isBuffer(chunk), encoding); + const parts = (this.rest + chunk.toString()).split("\n"); + this.rest = parts.pop() ?? ""; + this.lines.push(...parts); + cb(); + } + _final(cb: (err?: Error | null) => void): void { + if (this.rest !== "") this.lines.push(this.rest); + cb(); + } +} + +class Upper extends Transform { + _transform(chunk: string | Buffer, _enc: string, cb: (err?: Error | null, data?: string) => void): void { + cb(null, chunk.toString().toUpperCase()); + } +} + +class Failing extends Writable { + _write(chunk: Uint8Array | string, _enc: BufferEncoding, cb: (err?: Error | null) => void): void { + cb(new Error(`refused ${chunk.toString().length} bytes`)); + } +} + +const sink = new LineSink(); +sink.write("a\nb"); +sink.write(Buffer.from("c\nd")); +sink.end("e"); +sink.on("finish", () => console.log("lines", JSON.stringify(sink.lines))); + +const up = new Upper(); +up.on("data", (d: Buffer) => console.log("upper", d.toString())); +up.write("hello "); +up.end("world"); + +const bad = new Failing(); +bad.on("error", (e) => console.log("error", e.message)); +bad.write("xyz"); diff --git a/tests/fixtures/node-types/timeout-type.ts b/tests/fixtures/node-types/timeout-type.ts new file mode 100644 index 000000000..42daee86d --- /dev/null +++ b/tests/fixtures/node-types/timeout-type.ts @@ -0,0 +1,30 @@ +// NodeJS.Timeout as a declared type under @types/node: fields, clearTimeout, +// ref/unref/hasRef, refresh. +class Poller { + private timer: NodeJS.Timeout | undefined; + private count = 0; + start(): void { + this.timer = setInterval(() => { + this.count++; + console.log("poll", this.count); + if (this.count === 3) this.stop(); + }, 5); + } + stop(): void { + if (this.timer) { + clearInterval(this.timer); + this.timer = undefined; + console.log("stopped"); + } + } +} +new Poller().start(); +const t: NodeJS.Timeout = setTimeout(() => console.log("never"), 1000); +console.log(t.hasRef()); +t.unref(); +console.log(t.hasRef()); +t.ref(); +console.log(t.hasRef()); +clearTimeout(t); +const timers: NodeJS.Timeout[] = [setTimeout(() => console.log("a"), 30), setTimeout(() => console.log("b"), 31)]; +console.log(timers.length); diff --git a/tests/fixtures/node-types/tls-session-timings.ts b/tests/fixtures/node-types/tls-session-timings.ts new file mode 100644 index 000000000..7ebb53ce9 --- /dev/null +++ b/tests/fixtures/node-types/tls-session-timings.ts @@ -0,0 +1,31 @@ +// Under @types/node: a connector-style TLS client reads +// isSessionReused() through a TLSSocket assertion and keeps per-phase +// timings in a string-keyed record with `??=`. +import * as net from "node:net"; +import * as tls from "node:tls"; +import { readFileSync } from "node:fs"; + +const cert = readFileSync("tests/fixtures/server/certs/localhost.pem"); +const key = readFileSync("tests/fixtures/server/certs/localhost-key.pem"); +const ca = readFileSync("tests/fixtures/server/certs/ca.pem"); + +const timings: Record = {}; +function mark(phase: string, value: number): void { + timings[phase] ??= value; +} + +const server = tls.createServer({ key, cert }, (socket) => socket.end("ok")); +server.listen(0, "127.0.0.1", () => { + const port = (server.address() as net.AddressInfo).port; + const socket: net.Socket = tls.connect({ port, host: "127.0.0.1", servername: "localhost", ca }, () => { + mark("secure", 1); + mark("secure", 2); + console.log("reused", (socket as tls.TLSSocket).isSessionReused()); + }); + socket.on("data", (d: Buffer) => console.log("data", d.toString())); + socket.on("close", () => { + mark("close", 3); + console.log(JSON.stringify(timings)); + server.close(); + }); +}); diff --git a/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts b/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts new file mode 100644 index 000000000..47f003c12 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/@types/twinstale/index.d.ts @@ -0,0 +1 @@ +export function stale(text: string): string; diff --git a/tests/fixtures/npm-static/node_modules/@types/twinstale/package.json b/tests/fixtures/npm-static/node_modules/@types/twinstale/package.json new file mode 100644 index 000000000..7f7850ce6 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/@types/twinstale/package.json @@ -0,0 +1,5 @@ +{ + "name": "@types/twinstale", + "version": "2.0.4", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm-static/node_modules/twinstale/index.js b/tests/fixtures/npm-static/node_modules/twinstale/index.js new file mode 100644 index 000000000..6efa90723 --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/twinstale/index.js @@ -0,0 +1,7 @@ +// The 3.x release renamed its export: its @types twin still describes the +// 2.x name. +'use strict'; + +exports.render = function render(text) { + return 'stale:' + text; +}; diff --git a/tests/fixtures/npm-static/node_modules/twinstale/package.json b/tests/fixtures/npm-static/node_modules/twinstale/package.json new file mode 100644 index 000000000..9c104d6bf --- /dev/null +++ b/tests/fixtures/npm-static/node_modules/twinstale/package.json @@ -0,0 +1,5 @@ +{ + "name": "twinstale", + "version": "3.1.0", + "main": "index.js" +} diff --git a/tests/fixtures/npm-static/twinstale-cli.ts b/tests/fixtures/npm-static/twinstale-cli.ts new file mode 100644 index 000000000..f6c8a679c --- /dev/null +++ b/tests/fixtures/npm-static/twinstale-cli.ts @@ -0,0 +1,6 @@ +// twinstale's @types twin describes an older release than the installed +// package: the program typechecks against the twin, but not against the +// JavaScript's inferred surface (under Node the import is undefined). +import { stale } from "twinstale"; + +console.log(stale("ready")); diff --git a/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts b/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts new file mode 100644 index 000000000..5cbbc3dbc --- /dev/null +++ b/tests/fixtures/npm/cases/2558-bundled-chunks/main.ts @@ -0,0 +1,29 @@ +// esbuild's bundled CommonJS chunks: each package below is real esbuild +// output (0.25 unminified, 0.25 whitespace+syntax minified, 0.17) of one +// source that bundles CommonJS modules through the __commonJS helper. +// Every chunk compiles as its own CommonJS module: it runs once, at its +// first require (the top-level interop imports run while the bundle +// loads; `lazy` requires its chunk only when called), chunk-to-chunk +// requires share one module instance, an esbuild-built dist bundled as a +// chunk keeps its suffixed helpers, and the external dependency passes +// through the __toESM interop. gtcjsdist is a "type": "module" package +// whose CommonJS entry is a .cjs bundle using the node-mode interop. +import { describe, external, lazy, nested } from "gtchunk"; +import { describe as dws, external as ews, lazy as lws, nested as nws } from "gtchunkws"; +import { describe as d17, external as e17, lazy as l17, nested as n17 } from "gtchunk17"; +import { viaNodeMode } from "gtcjsdist"; + +console.log("main start"); +console.log("plain", describe()); +console.log("plain", external()); +console.log("plain", nested()); +console.log("plain", lazy(), lazy()); +console.log("minified", dws()); +console.log("minified", ews()); +console.log("minified", nws()); +console.log("minified", lws(), lws()); +console.log("older", d17()); +console.log("older", e17()); +console.log("older", n17()); +console.log("older", l17(), l17()); +console.log(viaNodeMode()); diff --git a/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts b/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts new file mode 100644 index 000000000..042102b64 --- /dev/null +++ b/tests/fixtures/npm/cases/2559-cjs-cycle/main.ts @@ -0,0 +1,11 @@ +// A require cycle between esbuild's per-file CommonJS outputs (index ↔ +// merge, index ↔ append). Each file installs its live getter table +// before its first require, so a partner that loads while index.js is +// still initializing sees index's partially initialized exports, and both +// partners call back into index from their functions. index.js re-exports +// mergeAll from merge.js through its getter table. +import { run, mergeAll } from "gtcycle"; + +console.log(run([" a ", "", "@x"], ["b", "@keep"])); +console.log(run(["c"], [])); +console.log(mergeAll(["@keep", " d"], ["@drop"]).join("|")); diff --git a/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts b/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts new file mode 100644 index 000000000..1ed90d432 --- /dev/null +++ b/tests/fixtures/npm/cases/2560-cjs-cycle-partner-first/main.ts @@ -0,0 +1,11 @@ +// The same esbuild CommonJS cycle entered through a partner: merge.js +// loads first, requires index.js, which requires merge.js back while +// merge is still initializing (its getter table is already installed) and +// re-exports mergeAll from it: the re-export reads merge's hoisted +// function while merge is still initializing. +import { mergeAll } from "gtcycle/dist/merge.js"; +import { run, mergeAll as reexported } from "gtcycle"; + +console.log(mergeAll([" x "], ["@keep"]).join(",")); +console.log(reexported === mergeAll); +console.log(run(["y"], ["@z"])); diff --git a/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts b/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts new file mode 100644 index 000000000..3efeb9eb1 --- /dev/null +++ b/tests/fixtures/npm/cases/2561-cjs-cycle-early-read/main.ts @@ -0,0 +1,6 @@ +// A require cycle whose partner reads a const export of index.js while +// index is still initializing: Node throws a ReferenceError (the binding +// is in its temporal dead zone), and scriptc refuses the cycle. +import { show } from "gtcyclebad"; + +console.log(show()); diff --git a/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts b/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts new file mode 100644 index 000000000..b34359cc7 --- /dev/null +++ b/tests/fixtures/npm/cases/2562-subpath-leaf/main.ts @@ -0,0 +1,10 @@ +// The program reaches gtsubpath only through its leaf data module (and a +// type from its main entry). The main entry reaches a file whose bundler +// interop the recognizer cannot verify, but no compiled code can load it, +// so the package compiles: a package is judged by what its imported +// subpaths reach. +import type { Manager } from "gtsubpath"; +import { managers } from "gtsubpath/dist/data.js"; + +const names: Manager[] = managers.filter((m) => m.lockfile.endsWith(".yaml")); +console.log(managers.length, names.map((m) => m.name).join(",")); diff --git a/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts b/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts new file mode 100644 index 000000000..02ac60cba --- /dev/null +++ b/tests/fixtures/npm/cases/2563-bundled-esm-inits/main.ts @@ -0,0 +1,16 @@ +// esbuild's lazily initialized ES modules: each package is real esbuild +// output (0.25 plain, and whitespace/syntax minified) of a source whose +// load() require()s an ES module. That module and the module it imports +// run behind the __esm helper: once, at the first load(), assigning +// their interop bindings (a builtin and an external CommonJS package) +// and live exports that the namespace's getters read. +import { describe, load } from "gtesm"; +import { describe as dws, load as lws } from "gtesmws"; + +console.log("main start"); +console.log(describe()); +console.log(load()); +console.log(load()); +console.log(dws()); +console.log(lws()); +console.log(lws()); diff --git a/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts b/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts new file mode 100644 index 000000000..d52b3731d --- /dev/null +++ b/tests/fixtures/npm/cases/2564-chunk-helper-shadowing/main.ts @@ -0,0 +1,7 @@ +// A bundled CommonJS chunk that calls the bundle's top-level `label` while +// a nested function of the chunk declares its own `label`: the chunk +// carries the top-level function along, and each reference keeps its own +// binding. +import { inner, outer } from "gtchunkshadow"; + +console.log(outer(), inner()); diff --git a/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts b/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts new file mode 100644 index 000000000..21ca81b40 --- /dev/null +++ b/tests/fixtures/npm/cases/2565-chunk-shared-function/main.ts @@ -0,0 +1,7 @@ +// Two bundled CommonJS chunks export the bundle's top-level `tally` and +// count calls on a property of it. Node prints "1 2 true": both chunks +// share one function. A per-chunk copy would split its identity and its +// properties, so the package stays refused. +import { counts, same } from "gtchunkshared"; + +console.log(counts(), same()); diff --git a/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts b/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts new file mode 100644 index 000000000..3cca145e0 --- /dev/null +++ b/tests/fixtures/npm/cases/2566-cjs-cycle-indirect-call/main.ts @@ -0,0 +1,7 @@ +// A require cycle whose partner calls an export of index.js with esbuild's +// `(0, import_index.peek)()` spelling while index is still initializing: +// peek reads a const in its temporal dead zone, so Node throws a +// ReferenceError, and scriptc refuses the cycle. +import { show } from "gtcyclecall"; + +console.log(show()); diff --git a/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts b/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts new file mode 100644 index 000000000..d6f084bc0 --- /dev/null +++ b/tests/fixtures/npm/cases/2567-esm-namespace-this/main.ts @@ -0,0 +1,7 @@ +// A lazily initialized ES module export that reads `this`, called through +// the module's namespace (`lazy.who()`): Node passes the namespace object, +// whose `__esModule` is true. A lowered namespace object would differ, so +// the package stays refused. +import { load } from "gtesmthis"; + +console.log(load()); diff --git a/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts b/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts new file mode 100644 index 000000000..ae2be341a --- /dev/null +++ b/tests/fixtures/npm/cases/2571-cjs-cycle-stored-function/main.ts @@ -0,0 +1,8 @@ +// Require cycles whose partner stores an export of index.js (in a const, +// or in an object literal) and calls it while index is still +// initializing: the export reads a const in its temporal dead zone, so +// Node throws a ReferenceError, and scriptc refuses both cycles. +import { show } from "gtcyclealias"; +import { show as showBox } from "gtcyclebox"; + +console.log(show(), showBox()); diff --git a/tests/fixtures/npm/cases/2580-lazy-barrel/main.ts b/tests/fixtures/npm/cases/2580-lazy-barrel/main.ts new file mode 100644 index 000000000..31c88db9b --- /dev/null +++ b/tests/fixtures/npm/cases/2580-lazy-barrel/main.ts @@ -0,0 +1,19 @@ +// A barrel whose re-exports load their modules lazily: a build plugin +// (real esbuild output, plain and whitespace/syntax minified) turns each +// re-export into a getter over a memoized loader of a lazily initialized +// ES module. Node's ES-module import of a CommonJS module reads every +// lexer-visible export when it links, so each module initializes once, +// before main runs, and the imports are snapshots (greet reassigns +// GREETING afterwards). +import { greet, GREETING, Counter, describe } from "gtlazy"; +import { describe as wsDescribe, GREETING as wsGreeting } from "gtlazyws"; + +console.log("main start"); +console.log(GREETING); +console.log(greet("a")); +console.log(GREETING); +const c = new Counter(); +console.log(c.next(), c.next()); +console.log(describe()); +console.log(wsDescribe()); +console.log(wsGreeting); diff --git a/tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts b/tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts new file mode 100644 index 000000000..42bed4781 --- /dev/null +++ b/tests/fixtures/npm/cases/2581-lazy-barrel-require/main.ts @@ -0,0 +1,9 @@ +// The lazy barrel (gtlazy) required from CommonJS: require() hands out the +// export object itself, so each re-export's getter runs at its read. A +// module initializes at the first read of one of its exports, once, and +// later reads see its live bindings (greet reassigns GREETING). +import { first, again } from "gtlazyuser"; + +console.log("main start"); +console.log(first()); +console.log(again()); diff --git a/tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts b/tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts new file mode 100644 index 000000000..6ccf96cdb --- /dev/null +++ b/tests/fixtures/npm/cases/2582-npm-alias-two-releases/main.ts @@ -0,0 +1,7 @@ +// gtroute requires two releases of one package: gtpattern 1.0.0 under its +// own name and 2.0.0 through an npm alias directory (gtpattern-next), +// whose manifest still names gtpattern. Both compile from their own +// JavaScript, as Node loads them. +import { compile } from "gtroute"; + +console.log(compile("/users/:id")); diff --git a/tests/fixtures/npm/cases/2582-npm-alias-two-releases/node_modules/gtroute b/tests/fixtures/npm/cases/2582-npm-alias-two-releases/node_modules/gtroute new file mode 120000 index 000000000..e0ddd1565 --- /dev/null +++ b/tests/fixtures/npm/cases/2582-npm-alias-two-releases/node_modules/gtroute @@ -0,0 +1 @@ +../../../store/gtroute@1.0.0/node_modules/gtroute \ No newline at end of file diff --git a/tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts b/tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts new file mode 100644 index 000000000..29e65cbfc --- /dev/null +++ b/tests/fixtures/npm/cases/2583-cjs-function-exports/main.ts @@ -0,0 +1,12 @@ +// CommonJS modules whose export object is one of their own functions: +// semver 5/6's `exports = module.exports = Version` with members attached +// through both `exports` and `module.exports` (and read back through +// `exports` inside the module), and a constructor function exported whole +// with function and value members attached afterwards. +import Version from "gtfnexports"; +import Point from "gtfnmembers"; + +const v = Version.parse("2.4.1"); +console.log(v === null ? "null" : v.major(), String(new Version("3.0.0"))); +console.log(Version.compare("5.0.0", "3.2.0"), Version.gt("1.0.0", "2.0.0"), Version.MAX_LENGTH); +console.log(Point.origin().sum(), new Point(2, 3).sum(), Point.dimensions); diff --git a/tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts b/tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts new file mode 100644 index 000000000..2c117e914 --- /dev/null +++ b/tests/fixtures/npm/cases/2584-cjs-conditional-exports/main.ts @@ -0,0 +1,7 @@ +// An environment-dependent export object (readable-stream 2.x's +// READABLE_STREAM=disable switch): module.exports is replaced inside an +// if/else, so the module's exports depend on which branch ran. scriptc +// names the construct; Node picks the branch at load. +import rs from "gtcondexports"; + +console.log(rs.Readable.name); diff --git a/tests/fixtures/npm/cases/cjs-exports-var/main.ts b/tests/fixtures/npm/cases/cjs-exports-var/main.ts new file mode 100644 index 000000000..ec72ba751 --- /dev/null +++ b/tests/fixtures/npm/cases/cjs-exports-var/main.ts @@ -0,0 +1,16 @@ +// A CommonJS module that names its export object through a variable +// (`var ip = exports; ip.isPrivate = function …`), the ip package's +// shape, imported by default as Node does: its members are the exports, +// including the ones it calls on itself through the alias. +import ip from "exportsvar"; + +for (const addr of ["10.0.0.1", "192.168.1.20", "127.0.0.1", "::1", "8.8.8.8"]) { + console.log(addr, ip.isPrivate(addr), ip.isLoopback(addr), ip.isPublic(addr)); +} +console.log(ip.toLong("192.168.1.1")); +// Members whose parameter shadows the alias name. +console.log(ip.isV4Format("1.2.3.4"), ip.isV4Format("::1"), ip.normalize(" FE80::1 ")); +// Members with a block-local `let` or a loop `const` of the alias name: +// only that block or loop names the local binding. +console.log(ip.classify("FE80::1"), ip.classify("10.1.2.3"), ip.classify("8.8.4.4")); +console.log(ip.countPrivate(["127.0.0.1", "10.0.0.2", "10.9.9.9"]), ip.countPrivate(["10.0.0.1"])); diff --git a/tests/fixtures/npm/cases/tsc-default-object/main.ts b/tests/fixtures/npm/cases/tsc-default-object/main.ts new file mode 100644 index 000000000..bcc650c06 --- /dev/null +++ b/tests/fixtures/npm/cases/tsc-default-object/main.ts @@ -0,0 +1,7 @@ +// tsc's CommonJS output for a module with named re-exports AND a default +// export object (`exports.default = { registry: registry_api_1.registry }`, +// the @opentelemetry/api index shape): the named exports still link, read +// through their defineProperty getters. +import { registry, VERSION } from "tscdefault"; + +console.log(VERSION, registry.count("a", 2), registry.count("a", 3), registry.count("b", 1)); diff --git a/tests/fixtures/npm/node_modules/exportsvar/index.d.ts b/tests/fixtures/npm/node_modules/exportsvar/index.d.ts new file mode 100644 index 000000000..fa65c9064 --- /dev/null +++ b/tests/fixtures/npm/node_modules/exportsvar/index.d.ts @@ -0,0 +1,8 @@ +export function isPrivate(addr: string): boolean; +export function isLoopback(addr: string): boolean; +export function isPublic(addr: string): boolean; +export function toLong(addr: string): number; +export function isV4Format(ip: string): boolean; +export function normalize(ip: string): string; +export function classify(addr: string): string; +export function countPrivate(addrs: string[]): number; diff --git a/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js b/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js new file mode 100644 index 000000000..074ed8318 --- /dev/null +++ b/tests/fixtures/npm/node_modules/exportsvar/lib/ip.js @@ -0,0 +1,49 @@ +'use strict'; + +var ip = exports; + +ip.isPrivate = function(addr) { + return /^(::f{4}:)?10\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || + /^(::f{4}:)?192\.168\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr); +}; + +ip.isLoopback = function(addr) { + return /^(::f{4}:)?127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})/.test(addr) || + /^::1$/.test(addr); +}; + +ip.isPublic = function(addr) { + return !ip.isPrivate(addr) && !ip.isLoopback(addr); +}; + +ip.toLong = function(addr) { + var n = 0; + addr.split('.').forEach(function(octet) { + n <<= 8; + n += parseInt(octet, 10); + }); + return n >>> 0; +}; + +ip.isV4Format = function(ip) { + return /^(\d{1,3}\.){3,3}\d{1,3}$/.test(ip); +}; + +ip.normalize = function(ip) { + var trimmed = ip.trim(); + return trimmed.toLowerCase(); +}; + +ip.classify = function(addr) { + if (addr.indexOf(':') !== -1) { + let ip = addr.toLowerCase(); + return 'v6 ' + ip; + } + return ip.isPrivate(addr) ? 'private' : 'public'; +}; + +ip.countPrivate = function(addrs) { + var n = 0; + for (const ip of addrs) if (/^10\./.test(ip)) n++; + return n + (ip.isLoopback(addrs[0]) ? 100 : 0); +}; diff --git a/tests/fixtures/npm/node_modules/exportsvar/package.json b/tests/fixtures/npm/node_modules/exportsvar/package.json new file mode 100644 index 000000000..04881bdbd --- /dev/null +++ b/tests/fixtures/npm/node_modules/exportsvar/package.json @@ -0,0 +1,6 @@ +{ + "name": "exportsvar", + "version": "1.0.0", + "main": "lib/ip", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtchunk/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunk/dist/index.d.ts new file mode 100644 index 000000000..bd3d4e582 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk/dist/index.d.ts @@ -0,0 +1,4 @@ +export declare function describe(): string; +export declare function external(): string; +export declare function nested(): string; +export declare function lazy(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunk/dist/index.js b/tests/fixtures/npm/node_modules/gtchunk/dist/index.js new file mode 100644 index 000000000..7820e3ae4 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk/dist/index.js @@ -0,0 +1,132 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// chunk/vendor/settings/counter.js +var require_counter = __commonJS({ + "chunk/vendor/settings/counter.js"(exports2, module2) { + var n = 0; + console.log("counter init"); + module2.exports = { + next() { + n += 1; + return n; + } + }; + } +}); + +// chunk/vendor/settings/index.js +var require_settings = __commonJS({ + "chunk/vendor/settings/index.js"(exports2) { + "use strict"; + console.log("settings init"); + var counter = require_counter(); + exports2.LABEL = "bundled"; + exports2.VERSION = 2; + exports2.bump = function() { + return counter.next(); + }; + } +}); + +// chunk/vendor/prebuilt/index.js +var require_prebuilt = __commonJS({ + "chunk/vendor/prebuilt/index.js"(exports2, module2) { + var __defProp2 = Object.defineProperty; + var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; + var __getOwnPropNames2 = Object.getOwnPropertyNames; + var __hasOwnProp2 = Object.prototype.hasOwnProperty; + var __export2 = (target, all) => { + for (var name in all) + __defProp2(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps2 = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames2(from)) + if (!__hasOwnProp2.call(to, key) && key !== except) + __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod); + var prebuilt_exports = {}; + __export2(prebuilt_exports, { + PREBUILT_TAG: () => PREBUILT_TAG2, + greet: () => greet2 + }); + module2.exports = __toCommonJS2(prebuilt_exports); + function greet2(name) { + return "hello " + name; + } + var PREBUILT_TAG2 = "prebuilt"; + } +}); + +// chunk/vendor/lazy.js +var require_lazy = __commonJS({ + "chunk/vendor/lazy.js"(exports2, module2) { + console.log("lazy init"); + module2.exports = { value: "lazy-value" }; + } +}); + +// chunk/src/index.ts +var index_exports = {}; +__export(index_exports, { + describe: () => describe, + external: () => external, + lazy: () => lazy, + nested: () => nested +}); +module.exports = __toCommonJS(index_exports); +var import_settings = __toESM(require_settings()); +var core = __toESM(require("gtcore")); +var import_gtcore = __toESM(require("gtcore")); +var import_prebuilt = __toESM(require_prebuilt()); +function describe() { + return import_settings.LABEL + "@" + import_settings.default.VERSION + " bump " + (0, import_settings.bump)() + " " + (0, import_settings.bump)(); +} +function external() { + return core.tag + ":" + core.label(1) + ":" + import_gtcore.default.label(2); +} +function nested() { + return (0, import_prebuilt.greet)("chunk") + " " + import_prebuilt.PREBUILT_TAG; +} +function lazy() { + return require_lazy().value; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + describe, + external, + lazy, + nested +}); diff --git a/tests/fixtures/npm/node_modules/gtchunk/package.json b/tests/fixtures/npm/node_modules/gtchunk/package.json new file mode 100644 index 000000000..2812a5188 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtchunk", + "version": "1.0.0", + "description": "esbuild 0.25.10 output (--bundle --platform=node --format=cjs --external:gtcore): bundled CommonJS modules behind the __commonJS helper, including an esbuild-built dist with suffixed helpers", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtchunk17/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunk17/dist/index.d.ts new file mode 100644 index 000000000..bd3d4e582 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk17/dist/index.d.ts @@ -0,0 +1,4 @@ +export declare function describe(): string; +export declare function external(): string; +export declare function nested(): string; +export declare function lazy(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunk17/dist/index.js b/tests/fixtures/npm/node_modules/gtchunk17/dist/index.js new file mode 100644 index 000000000..afda6af56 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk17/dist/index.js @@ -0,0 +1,132 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// chunk/vendor/settings/counter.js +var require_counter = __commonJS({ + "chunk/vendor/settings/counter.js"(exports, module2) { + var n = 0; + console.log("counter init"); + module2.exports = { + next() { + n += 1; + return n; + } + }; + } +}); + +// chunk/vendor/settings/index.js +var require_settings = __commonJS({ + "chunk/vendor/settings/index.js"(exports) { + "use strict"; + console.log("settings init"); + var counter = require_counter(); + exports.LABEL = "bundled"; + exports.VERSION = 2; + exports.bump = function() { + return counter.next(); + }; + } +}); + +// chunk/vendor/prebuilt/index.js +var require_prebuilt = __commonJS({ + "chunk/vendor/prebuilt/index.js"(exports, module2) { + var __defProp2 = Object.defineProperty; + var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; + var __getOwnPropNames2 = Object.getOwnPropertyNames; + var __hasOwnProp2 = Object.prototype.hasOwnProperty; + var __export2 = (target, all) => { + for (var name in all) + __defProp2(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps2 = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames2(from)) + if (!__hasOwnProp2.call(to, key) && key !== except) + __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable }); + } + return to; + }; + var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod); + var prebuilt_exports = {}; + __export2(prebuilt_exports, { + PREBUILT_TAG: () => PREBUILT_TAG2, + greet: () => greet2 + }); + module2.exports = __toCommonJS2(prebuilt_exports); + function greet2(name) { + return "hello " + name; + } + var PREBUILT_TAG2 = "prebuilt"; + } +}); + +// chunk/vendor/lazy.js +var require_lazy = __commonJS({ + "chunk/vendor/lazy.js"(exports, module2) { + console.log("lazy init"); + module2.exports = { value: "lazy-value" }; + } +}); + +// chunk/src/index.ts +var src_exports = {}; +__export(src_exports, { + describe: () => describe, + external: () => external, + lazy: () => lazy, + nested: () => nested +}); +module.exports = __toCommonJS(src_exports); +var import_settings = __toESM(require_settings()); +var core = __toESM(require("gtcore")); +var import_gtcore = __toESM(require("gtcore")); +var import_prebuilt = __toESM(require_prebuilt()); +function describe() { + return import_settings.LABEL + "@" + import_settings.default.VERSION + " bump " + (0, import_settings.bump)() + " " + (0, import_settings.bump)(); +} +function external() { + return core.tag + ":" + core.label(1) + ":" + import_gtcore.default.label(2); +} +function nested() { + return (0, import_prebuilt.greet)("chunk") + " " + import_prebuilt.PREBUILT_TAG; +} +function lazy() { + return require_lazy().value; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + describe, + external, + lazy, + nested +}); diff --git a/tests/fixtures/npm/node_modules/gtchunk17/package.json b/tests/fixtures/npm/node_modules/gtchunk17/package.json new file mode 100644 index 000000000..73761f181 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunk17/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtchunk17", + "version": "1.0.0", + "description": "the gtchunk source bundled by esbuild 0.17.19: chunk factories take (exports, module2)", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts new file mode 100644 index 000000000..238cf1744 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function outer(): string; +export declare function inner(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js new file mode 100644 index 000000000..0f89c7ec5 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/dist/index.js @@ -0,0 +1,52 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +function label(n) { + return "label " + n; +} + +// chunk/vendor/shadow.js +var require_shadow = __commonJS({ + "chunk/vendor/shadow.js"(exports2, module2) { + function nested() { + var label = "nested"; + return label; + } + module2.exports = { outer: label(1), inner: nested() }; + } +}); + +// chunk/src/index.ts +var index_exports = {}; +__export(index_exports, { + inner: () => inner, + outer: () => outer +}); +module.exports = __toCommonJS(index_exports); +function outer() { + return require_shadow().outer; +} +function inner() { + return require_shadow().inner; +} +0 && (module.exports = { + inner, + outer +}); diff --git a/tests/fixtures/npm/node_modules/gtchunkshadow/package.json b/tests/fixtures/npm/node_modules/gtchunkshadow/package.json new file mode 100644 index 000000000..6830fbd2c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshadow/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtchunkshadow", + "version": "1.0.0", + "description": "esbuild's CommonJS bundle shape: a chunk calls a top-level function whose name a nested scope of the chunk also declares", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts new file mode 100644 index 000000000..3a47a0495 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function same(): boolean; +export declare function counts(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js new file mode 100644 index 000000000..0d3aacec8 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/dist/index.js @@ -0,0 +1,56 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +function tally() { + tally.calls = (tally.calls || 0) + 1; + return tally.calls; +} + +// chunk/vendor/a.js +var require_a = __commonJS({ + "chunk/vendor/a.js"(exports2, module2) { + module2.exports = { tally, first: tally() }; + } +}); + +// chunk/vendor/b.js +var require_b = __commonJS({ + "chunk/vendor/b.js"(exports2, module2) { + module2.exports = { tally, second: tally() }; + } +}); + +// chunk/src/index.ts +var index_exports = {}; +__export(index_exports, { + counts: () => counts, + same: () => same +}); +module.exports = __toCommonJS(index_exports); +function same() { + return require_a().tally === require_b().tally; +} +function counts() { + return require_a().first + " " + require_b().second; +} +0 && (module.exports = { + counts, + same +}); diff --git a/tests/fixtures/npm/node_modules/gtchunkshared/package.json b/tests/fixtures/npm/node_modules/gtchunkshared/package.json new file mode 100644 index 000000000..bfc90c5d2 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkshared/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtchunkshared", + "version": "1.0.0", + "description": "esbuild's CommonJS bundle shape: two chunks export one top-level function and count calls on a property of it", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtchunkws/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtchunkws/dist/index.d.ts new file mode 100644 index 000000000..bd3d4e582 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkws/dist/index.d.ts @@ -0,0 +1,4 @@ +export declare function describe(): string; +export declare function external(): string; +export declare function nested(): string; +export declare function lazy(): string; diff --git a/tests/fixtures/npm/node_modules/gtchunkws/dist/index.js b/tests/fixtures/npm/node_modules/gtchunkws/dist/index.js new file mode 100644 index 000000000..f022baf09 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkws/dist/index.js @@ -0,0 +1 @@ +var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __commonJS=(cb,mod)=>function(){return mod||(0,cb[__getOwnPropNames(cb)[0]])((mod={exports:{}}).exports,mod),mod.exports};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var require_counter=__commonJS({"chunk/vendor/settings/counter.js"(exports2,module2){var n=0;console.log("counter init");module2.exports={next(){return n+=1,n}}}});var require_settings=__commonJS({"chunk/vendor/settings/index.js"(exports2){"use strict";console.log("settings init");var counter=require_counter();exports2.LABEL="bundled";exports2.VERSION=2;exports2.bump=function(){return counter.next()}}});var require_prebuilt=__commonJS({"chunk/vendor/prebuilt/index.js"(exports2,module2){var __defProp2=Object.defineProperty,__getOwnPropDesc2=Object.getOwnPropertyDescriptor,__getOwnPropNames2=Object.getOwnPropertyNames,__hasOwnProp2=Object.prototype.hasOwnProperty,__export2=(target,all)=>{for(var name in all)__defProp2(target,name,{get:all[name],enumerable:!0})},__copyProps2=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames2(from))!__hasOwnProp2.call(to,key)&&key!==except&&__defProp2(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc2(from,key))||desc.enumerable});return to},__toCommonJS2=mod=>__copyProps2(__defProp2({},"__esModule",{value:!0}),mod),prebuilt_exports={};__export2(prebuilt_exports,{PREBUILT_TAG:()=>PREBUILT_TAG2,greet:()=>greet2});module2.exports=__toCommonJS2(prebuilt_exports);function greet2(name){return"hello "+name}var PREBUILT_TAG2="prebuilt"}});var require_lazy=__commonJS({"chunk/vendor/lazy.js"(exports2,module2){console.log("lazy init");module2.exports={value:"lazy-value"}}});var index_exports={};__export(index_exports,{describe:()=>describe,external:()=>external,lazy:()=>lazy,nested:()=>nested});module.exports=__toCommonJS(index_exports);var import_settings=__toESM(require_settings()),core=__toESM(require("gtcore")),import_gtcore=__toESM(require("gtcore")),import_prebuilt=__toESM(require_prebuilt());function describe(){return import_settings.LABEL+"@"+import_settings.default.VERSION+" bump "+(0,import_settings.bump)()+" "+(0,import_settings.bump)()}function external(){return core.tag+":"+core.label(1)+":"+import_gtcore.default.label(2)}function nested(){return(0,import_prebuilt.greet)("chunk")+" "+import_prebuilt.PREBUILT_TAG}function lazy(){return require_lazy().value}0&&(module.exports={describe,external,lazy,nested}); diff --git a/tests/fixtures/npm/node_modules/gtchunkws/package.json b/tests/fixtures/npm/node_modules/gtchunkws/package.json new file mode 100644 index 000000000..5502f80a0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtchunkws/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtchunkws", + "version": "1.0.0", + "description": "the gtchunk source bundled by esbuild 0.25.10 with --minify-whitespace --minify-syntax: helpers share var statements", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.cjs b/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.cjs new file mode 100644 index 000000000..8836b5691 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.cjs @@ -0,0 +1,43 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// cjsdist/src/index.ts +var index_exports = {}; +__export(index_exports, { + viaNodeMode: () => viaNodeMode +}); +module.exports = __toCommonJS(index_exports); +var core = __toESM(require("gtcore"), 1); +var import_gtcore = __toESM(require("gtcore"), 1); +function viaNodeMode() { + return import_gtcore.default.tag + "/" + core.label(7) + "/" + import_gtcore.default.label(8); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + viaNodeMode +}); diff --git a/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.d.cts b/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.d.cts new file mode 100644 index 000000000..6f77b9943 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcjsdist/dist/index.d.cts @@ -0,0 +1 @@ +export declare function viaNodeMode(): string; diff --git a/tests/fixtures/npm/node_modules/gtcjsdist/package.json b/tests/fixtures/npm/node_modules/gtcjsdist/package.json new file mode 100644 index 000000000..8b416fca1 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcjsdist/package.json @@ -0,0 +1,11 @@ +{ + "name": "gtcjsdist", + "version": "1.0.0", + "description": "a type-module package whose CommonJS entry is an esbuild 0.25.10 .cjs bundle using the node-mode __toESM interop", + "type": "module", + "main": "./dist/index.cjs", + "types": "./dist/index.d.cts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtcondexports/index.d.ts b/tests/fixtures/npm/node_modules/gtcondexports/index.d.ts new file mode 100644 index 000000000..ba94e150b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcondexports/index.d.ts @@ -0,0 +1,2 @@ +declare const exported: { Readable: Function; Stream: unknown; name: string }; +export = exported; diff --git a/tests/fixtures/npm/node_modules/gtcondexports/index.js b/tests/fixtures/npm/node_modules/gtcondexports/index.js new file mode 100644 index 000000000..b4017d055 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcondexports/index.js @@ -0,0 +1,14 @@ +var Stream = require("stream"); +function Local(name) { + this.name = name; +} +if (process.env.GT_STREAMS === "native" && Stream) { + module.exports = Stream; + exports = module.exports = Stream.Readable; + exports.Readable = Stream.Readable; + exports.Stream = Stream; +} else { + exports = module.exports = Local; + exports.Stream = Stream || exports; + exports.Readable = exports; +} diff --git a/tests/fixtures/npm/node_modules/gtcondexports/package.json b/tests/fixtures/npm/node_modules/gtcondexports/package.json new file mode 100644 index 000000000..eab4647ed --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcondexports/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcondexports", + "version": "1.0.0", + "description": "readable-stream 2.x's environment-dependent export object: module.exports is replaced (and exports rebound) inside an if/else on an environment variable", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/append.d.ts b/tests/fixtures/npm/node_modules/gtcycle/dist/append.d.ts new file mode 100644 index 000000000..e33c31f90 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/append.d.ts @@ -0,0 +1 @@ +export declare function appendAll(xs: string[], tail: string): string[]; diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/append.js b/tests/fixtures/npm/node_modules/gtcycle/dist/append.js new file mode 100644 index 000000000..cdfeb6c6c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/append.js @@ -0,0 +1,30 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var append_exports = {}; +__export(append_exports, { + appendAll: () => appendAll +}); +module.exports = __toCommonJS(append_exports); +var import_index = require("./index"); +function appendAll(xs, tail) { + return xs.some(import_index.isHandler) ? xs : xs.concat([tail]); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + appendAll +}); diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcycle/dist/index.d.ts new file mode 100644 index 000000000..4b86308a9 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/index.d.ts @@ -0,0 +1,4 @@ +export { mergeAll } from "./merge"; +export declare function isHandler(x: string): boolean; +export declare function normalize(xs: string[]): string[]; +export declare function run(a: string[], b: string[]): string; diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/index.js b/tests/fixtures/npm/node_modules/gtcycle/dist/index.js new file mode 100644 index 000000000..92f6d2a39 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/index.js @@ -0,0 +1,44 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + isHandler: () => isHandler, + mergeAll: () => import_merge2.mergeAll, + normalize: () => normalize, + run: () => run +}); +module.exports = __toCommonJS(index_exports); +var import_append = require("./append"); +var import_merge = require("./merge"); +var import_merge2 = require("./merge"); +function isHandler(x) { + return x.startsWith("@"); +} +function normalize(xs) { + return xs.map((x) => x.trim()).filter((x) => x.length > 0); +} +function run(a, b) { + return (0, import_append.appendAll)((0, import_merge.mergeAll)(a, b), "@end").join(","); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + isHandler, + mergeAll, + normalize, + run +}); diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/merge.d.ts b/tests/fixtures/npm/node_modules/gtcycle/dist/merge.d.ts new file mode 100644 index 000000000..bbe6b6a4d --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/merge.d.ts @@ -0,0 +1 @@ +export declare function mergeAll(a: string[], b: string[]): string[]; diff --git a/tests/fixtures/npm/node_modules/gtcycle/dist/merge.js b/tests/fixtures/npm/node_modules/gtcycle/dist/merge.js new file mode 100644 index 000000000..114e7d8cf --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/dist/merge.js @@ -0,0 +1,30 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var merge_exports = {}; +__export(merge_exports, { + mergeAll: () => mergeAll +}); +module.exports = __toCommonJS(merge_exports); +var import_index = require("./index"); +function mergeAll(a, b) { + return (0, import_index.normalize)(a.concat(b)).filter((x) => !(0, import_index.isHandler)(x) || x === "@keep"); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + mergeAll +}); diff --git a/tests/fixtures/npm/node_modules/gtcycle/package.json b/tests/fixtures/npm/node_modules/gtcycle/package.json new file mode 100644 index 000000000..5b41b8a52 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcycle/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcycle", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output (no bundling) of three modules that import each other", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js new file mode 100644 index 000000000..943e61a0a --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/dist/reader.js @@ -0,0 +1,34 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const read = import_index.peek; +const captured = read(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclealias/package.json b/tests/fixtures/npm/node_modules/gtcyclealias/package.json new file mode 100644 index 000000000..9ee7441e0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclealias/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclealias", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner stores an exported function in a const and calls it while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.d.ts new file mode 100644 index 000000000..0fa366319 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare const LIMIT = 3; +export declare function show(): string; diff --git a/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.js new file mode 100644 index 000000000..5bd708a37 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebad/dist/index.js @@ -0,0 +1,33 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebad/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclebad/dist/reader.js new file mode 100644 index 000000000..a58bdaa51 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebad/dist/reader.js @@ -0,0 +1,33 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const captured = import_index.LIMIT; +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebad/package.json b/tests/fixtures/npm/node_modules/gtcyclebad/package.json new file mode 100644 index 000000000..793c97fc9 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebad/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclebad", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output of a cycle whose partner reads a const binding before its declaration runs", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js new file mode 100644 index 000000000..b8f8a927e --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/dist/reader.js @@ -0,0 +1,34 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const box = { read: import_index.peek }; +const captured = box.read(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclebox/package.json b/tests/fixtures/npm/node_modules/gtcyclebox/package.json new file mode 100644 index 000000000..67f943195 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclebox/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclebox", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner stores an exported function in an object literal and calls it while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts new file mode 100644 index 000000000..5f4373e2f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.d.ts @@ -0,0 +1,3 @@ +export declare const LIMIT = 3; +export declare function show(): string; +export declare function peek(): number; diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js new file mode 100644 index 000000000..77aca3c9b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/index.js @@ -0,0 +1,37 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var index_exports = {}; +__export(index_exports, { + LIMIT: () => LIMIT, + peek: () => peek, + show: () => show +}); +module.exports = __toCommonJS(index_exports); +var import_reader = require("./reader"); +const LIMIT = 3; +function peek() { + return LIMIT; +} +function show() { + return (0, import_reader.describe)(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + LIMIT, + show +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js b/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js new file mode 100644 index 000000000..c98d1ae46 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/dist/reader.js @@ -0,0 +1,33 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var reader_exports = {}; +__export(reader_exports, { + captured: () => captured, + describe: () => describe +}); +module.exports = __toCommonJS(reader_exports); +var import_index = require("./index"); +const captured = (0, import_index.peek)(); +function describe() { + return "limit " + captured; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + captured, + describe +}); diff --git a/tests/fixtures/npm/node_modules/gtcyclecall/package.json b/tests/fixtures/npm/node_modules/gtcyclecall/package.json new file mode 100644 index 000000000..cc1069a6f --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtcyclecall/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtcyclecall", + "version": "1.0.0", + "description": "esbuild 0.25.10 per-file CommonJS output shape of a cycle whose partner calls an exported function through (0, binding.fn)() while that function's const binding is uninitialized", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtesm/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtesm/dist/index.d.ts new file mode 100644 index 000000000..af4369397 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesm/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function describe(): string; +export declare function load(): string; diff --git a/tests/fixtures/npm/node_modules/gtesm/dist/index.js b/tests/fixtures/npm/node_modules/gtesm/dist/index.js new file mode 100644 index 000000000..241c5e048 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesm/dist/index.js @@ -0,0 +1,90 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/helper.ts +function helper(x) { + COUNT++; + return "[" + x + "]"; +} +var COUNT; +var init_helper = __esm({ + "src/helper.ts"() { + console.log("helper init"); + COUNT = 0; + } +}); + +// src/lazy.ts +var lazy_exports = {}; +__export(lazy_exports, { + VALUE: () => VALUE, + value: () => value +}); +function value() { + return VALUE + ":" + import_gtcore.default.tag + ":" + COUNT; +} +var path, import_gtcore, VALUE; +var init_lazy = __esm({ + "src/lazy.ts"() { + path = __toESM(require("path")); + import_gtcore = __toESM(require("gtcore")); + init_helper(); + console.log("lazy init"); + VALUE = helper(path.basename("/a/b.txt")); + } +}); + +// src/index.ts +var index_exports = {}; +__export(index_exports, { + describe: () => describe, + load: () => load +}); +module.exports = __toCommonJS(index_exports); + +// src/greet.ts +function greet(name) { + return "hello " + name; +} + +// src/index.ts +function describe() { + return greet("main"); +} +function load() { + const lazy = (init_lazy(), __toCommonJS(lazy_exports)); + return lazy.value() + " " + lazy.VALUE; +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + describe, + load +}); diff --git a/tests/fixtures/npm/node_modules/gtesm/package.json b/tests/fixtures/npm/node_modules/gtesm/package.json new file mode 100644 index 000000000..3913402f9 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesm/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtesm", + "version": "1.0.0", + "description": "esbuild 0.25.10 output (--bundle --platform=node --format=cjs --external:gtcore) of a source that require()s an ES module: lazily initialized ES modules behind the __esm helper", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtesmthis/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtesmthis/dist/index.d.ts new file mode 100644 index 000000000..af4369397 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmthis/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function describe(): string; +export declare function load(): string; diff --git a/tests/fixtures/npm/node_modules/gtesmthis/dist/index.js b/tests/fixtures/npm/node_modules/gtesmthis/dist/index.js new file mode 100644 index 000000000..46b81aa7d --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmthis/dist/index.js @@ -0,0 +1,94 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/helper.ts +function helper(x) { + COUNT++; + return "[" + x + "]"; +} +var COUNT; +var init_helper = __esm({ + "src/helper.ts"() { + console.log("helper init"); + COUNT = 0; + } +}); + +// src/lazy.ts +var lazy_exports = {}; +__export(lazy_exports, { + VALUE: () => VALUE, + value: () => value, + who: () => who +}); +function who() { + return [typeof this, this.__esModule, Object.getOwnPropertyNames(this).join(","), Object.keys(this).join(",")].join(" "); +} +function value() { + return VALUE + ":" + import_gtcore.default.tag + ":" + COUNT; +} +var path, import_gtcore, VALUE; +var init_lazy = __esm({ + "src/lazy.ts"() { + path = __toESM(require("path")); + import_gtcore = __toESM(require("gtcore")); + init_helper(); + console.log("lazy init"); + VALUE = helper(path.basename("/a/b.txt")); + } +}); + +// src/index.ts +var index_exports = {}; +__export(index_exports, { + describe: () => describe, + load: () => load +}); +module.exports = __toCommonJS(index_exports); + +// src/greet.ts +function greet(name) { + return "hello " + name; +} + +// src/index.ts +function describe() { + return greet("main"); +} +function load() { + const lazy = (init_lazy(), __toCommonJS(lazy_exports)); + return lazy.value() + " " + lazy.VALUE + " | " + lazy.who() + " | " + (init_lazy(), __toCommonJS(lazy_exports)).who(); +} +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + describe, + load +}); diff --git a/tests/fixtures/npm/node_modules/gtesmthis/package.json b/tests/fixtures/npm/node_modules/gtesmthis/package.json new file mode 100644 index 000000000..c6344fa8c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmthis/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtesmthis", + "version": "1.0.0", + "description": "gtesm's esbuild 0.25.10 output shape with a lazily initialized ES module export that reads `this`, called through the module's namespace", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtesmws/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtesmws/dist/index.d.ts new file mode 100644 index 000000000..af4369397 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmws/dist/index.d.ts @@ -0,0 +1,2 @@ +export declare function describe(): string; +export declare function load(): string; diff --git a/tests/fixtures/npm/node_modules/gtesmws/dist/index.js b/tests/fixtures/npm/node_modules/gtesmws/dist/index.js new file mode 100644 index 000000000..cef009700 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmws/dist/index.js @@ -0,0 +1 @@ +var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __esm=(fn,res)=>function(){return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);function helper(x){return COUNT++,"["+x+"]"}var COUNT,init_helper=__esm({"src/helper.ts"(){console.log("helper init");COUNT=0}});var lazy_exports={};__export(lazy_exports,{VALUE:()=>VALUE,value:()=>value});function value(){return VALUE+":"+import_gtcore.default.tag+":"+COUNT}var path,import_gtcore,VALUE,init_lazy=__esm({"src/lazy.ts"(){path=__toESM(require("path")),import_gtcore=__toESM(require("gtcore"));init_helper();console.log("lazy init");VALUE=helper(path.basename("/a/b.txt"))}});var index_exports={};__export(index_exports,{describe:()=>describe,load:()=>load});module.exports=__toCommonJS(index_exports);function greet(name){return"hello "+name}function describe(){return greet("main")}function load(){let lazy=(init_lazy(),__toCommonJS(lazy_exports));return lazy.value()+" "+lazy.VALUE}0&&(module.exports={describe,load}); diff --git a/tests/fixtures/npm/node_modules/gtesmws/package.json b/tests/fixtures/npm/node_modules/gtesmws/package.json new file mode 100644 index 000000000..3dfe90e90 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtesmws/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtesmws", + "version": "1.0.0", + "description": "the gtesm source bundled by esbuild 0.25.10 with --minify-whitespace --minify-syntax", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtfnexports/index.d.ts b/tests/fixtures/npm/node_modules/gtfnexports/index.d.ts new file mode 100644 index 000000000..c2ee082d3 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnexports/index.d.ts @@ -0,0 +1,13 @@ +declare class Version { + constructor(raw: string); + raw: string; + major(): number; + toString(): string; +} +declare namespace Version { + const MAX_LENGTH: number; + function parse(raw: string): Version | null; + function compare(a: string, b: string): number; + function gt(a: string, b: string): boolean; +} +export = Version; diff --git a/tests/fixtures/npm/node_modules/gtfnexports/index.js b/tests/fixtures/npm/node_modules/gtfnexports/index.js new file mode 100644 index 000000000..d64b58f0c --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnexports/index.js @@ -0,0 +1,25 @@ +exports = module.exports = Version; + +var label = process.env.GT_LABEL ? "[" + process.env.GT_LABEL + "]" : ""; + +function Version(raw) { + this.raw = raw; + this.parts = raw.split("."); +} +Version.prototype.major = function () { + return Number(this.parts[0]); +}; +Version.prototype.toString = function () { + return label + this.raw; +}; + +exports.MAX_LENGTH = 256; +exports.parse = function parse(raw) { + return raw.length > exports.MAX_LENGTH ? null : new Version(raw); +}; +exports.compare = function compare(a, b) { + return exports.parse(a).major() - exports.parse(b).major(); +}; +module.exports.gt = function gt(a, b) { + return exports.compare(a, b) > 0; +}; diff --git a/tests/fixtures/npm/node_modules/gtfnexports/package.json b/tests/fixtures/npm/node_modules/gtfnexports/package.json new file mode 100644 index 000000000..4524cd1bc --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnexports/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtfnexports", + "version": "1.0.0", + "description": "semver 5/6's export idiom: exports = module.exports = Version, then members attached through exports and module.exports", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtfnmembers/index.d.ts b/tests/fixtures/npm/node_modules/gtfnmembers/index.d.ts new file mode 100644 index 000000000..ea1888e0b --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnmembers/index.d.ts @@ -0,0 +1,11 @@ +declare class Point { + constructor(x: number, y: number); + x: number; + y: number; + sum(): number; +} +declare namespace Point { + function origin(): Point; + const dimensions: number; +} +export = Point; diff --git a/tests/fixtures/npm/node_modules/gtfnmembers/index.js b/tests/fixtures/npm/node_modules/gtfnmembers/index.js new file mode 100644 index 000000000..6eaf003f7 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnmembers/index.js @@ -0,0 +1,13 @@ +"use strict"; +function Point(x, y) { + this.x = x; + this.y = y; +} +Point.prototype.sum = function () { + return this.x + this.y; +}; +module.exports = Point; +module.exports.origin = function origin() { + return new Point(0, 0); +}; +module.exports.dimensions = 2; diff --git a/tests/fixtures/npm/node_modules/gtfnmembers/package.json b/tests/fixtures/npm/node_modules/gtfnmembers/package.json new file mode 100644 index 000000000..5179c6caf --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtfnmembers/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtfnmembers", + "version": "1.0.0", + "description": "a constructor function as module.exports with function and value members attached through module.exports afterwards", + "main": "index.js", + "types": "index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtlazy/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtlazy/dist/index.d.ts new file mode 100644 index 000000000..d3b4bac73 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazy/dist/index.d.ts @@ -0,0 +1,7 @@ +export declare let GREETING: string; +export declare function greet(name: string): string; +export declare class Counter { + n: number; + next(): number; +} +export declare function describe(): string; diff --git a/tests/fixtures/npm/node_modules/gtlazy/dist/index.js b/tests/fixtures/npm/node_modules/gtlazy/dist/index.js new file mode 100644 index 000000000..9a94faa11 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazy/dist/index.js @@ -0,0 +1,116 @@ +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __esm = (fn, res) => function __init() { + return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; +}; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/greet.ts +var greet_exports = {}; +__export(greet_exports, { + GREETING: () => GREETING, + greet: () => greet +}); +function greet(name) { + GREETING = "hi"; + return GREETING + " " + name; +} +var GREETING; +var init_greet = __esm({ + "src/greet.ts"() { + console.log("greet init"); + GREETING = "hello"; + } +}); + +// src/counter.ts +var counter_exports = {}; +__export(counter_exports, { + default: () => Counter +}); +var Counter; +var init_counter = __esm({ + "src/counter.ts"() { + console.log("counter init"); + Counter = class { + n = 0; + next() { + return ++this.n; + } + }; + } +}); + +// src/describe.ts +var describe_exports = {}; +__export(describe_exports, { + describe: () => describe +}); +function describe() { + return "described: " + greet("d"); +} +var init_describe = __esm({ + "src/describe.ts"() { + init_greet(); + console.log("describe init"); + } +}); + +// src/index.ts +var __create = Object.create; +var __defProp2 = Object.defineProperty; +var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor; +var __getOwnPropNames2 = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp2 = Object.prototype.hasOwnProperty; +var __export2 = (target, all) => { + for (var name in all) + __defProp2(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps2 = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames2(from)) + if (!__hasOwnProp2.call(to, key) && key !== except) + __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps2( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod); +var stdin_exports = {}; +__export2(stdin_exports, { + Counter: () => load_import_counter().default, + GREETING: () => load_import_greet().GREETING, + describe: () => load_import_describe().describe, + greet: () => load_import_greet().greet +}); +module.exports = __toCommonJS2(stdin_exports); +var import_greet2; +var load_import_greet = () => import_greet2 ??= (init_greet(), __toCommonJS(greet_exports)); +var import_counter; +var load_import_counter = () => import_counter ??= __toESM((init_counter(), __toCommonJS(counter_exports))); +var import_describe; +var load_import_describe = () => import_describe ??= (init_describe(), __toCommonJS(describe_exports)); + +0 && (module.exports = {"Counter": null,"GREETING": null,"describe": null,"greet": null}); diff --git a/tests/fixtures/npm/node_modules/gtlazy/package.json b/tests/fixtures/npm/node_modules/gtlazy/package.json new file mode 100644 index 000000000..e63f696a6 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazy/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtlazy", + "version": "1.0.0", + "description": "esbuild 0.25.10 output (--bundle --platform=node --format=cjs) of a barrel whose re-exports a build plugin turns into memoized lazy loaders: `load_x = () => x ??= (init_x(), __toCommonJS(x_exports))`", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtlazyuser/index.d.ts b/tests/fixtures/npm/node_modules/gtlazyuser/index.d.ts new file mode 100644 index 000000000..948fec847 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyuser/index.d.ts @@ -0,0 +1,2 @@ +export declare function first(): string; +export declare function again(): string; diff --git a/tests/fixtures/npm/node_modules/gtlazyuser/index.js b/tests/fixtures/npm/node_modules/gtlazyuser/index.js new file mode 100644 index 000000000..d5666d6e1 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyuser/index.js @@ -0,0 +1,10 @@ +"use strict"; +const lazy = require("gtlazy"); + +console.log("user loaded"); +exports.first = function first() { + return lazy.GREETING + "/" + lazy.greet("u"); +}; +exports.again = function again() { + return lazy.GREETING + "/" + lazy.describe(); +}; diff --git a/tests/fixtures/npm/node_modules/gtlazyuser/package.json b/tests/fixtures/npm/node_modules/gtlazyuser/package.json new file mode 100644 index 000000000..841bdc7b2 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyuser/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtlazyuser", + "version": "1.0.0", + "description": "a CommonJS package that require()s the gtlazy barrel: its lazy re-exports initialize at the first read and read live bindings", + "main": "index.js", + "types": "index.d.ts", + "dependencies": { + "gtlazy": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/gtlazyws/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtlazyws/dist/index.d.ts new file mode 100644 index 000000000..d3b4bac73 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyws/dist/index.d.ts @@ -0,0 +1,7 @@ +export declare let GREETING: string; +export declare function greet(name: string): string; +export declare class Counter { + n: number; + next(): number; +} +export declare function describe(): string; diff --git a/tests/fixtures/npm/node_modules/gtlazyws/dist/index.js b/tests/fixtures/npm/node_modules/gtlazyws/dist/index.js new file mode 100644 index 000000000..a472466b0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyws/dist/index.js @@ -0,0 +1,3 @@ +var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __hasOwnProp=Object.prototype.hasOwnProperty;var __esm=(fn,res)=>function(){return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res};var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var greet_exports={};__export(greet_exports,{GREETING:()=>GREETING,greet:()=>greet});function greet(name){return GREETING="hi",GREETING+" "+name}var GREETING,init_greet=__esm({"src/greet.ts"(){console.log("greet init");GREETING="hello"}});var counter_exports={};__export(counter_exports,{default:()=>Counter});var Counter,init_counter=__esm({"src/counter.ts"(){console.log("counter init");Counter=class{n=0;next(){return++this.n}}}});var describe_exports={};__export(describe_exports,{describe:()=>describe});function describe(){return"described: "+greet("d")}var init_describe=__esm({"src/describe.ts"(){init_greet();console.log("describe init")}});var __create=Object.create,__defProp2=Object.defineProperty,__getOwnPropDesc2=Object.getOwnPropertyDescriptor,__getOwnPropNames2=Object.getOwnPropertyNames,__getProtoOf=Object.getPrototypeOf,__hasOwnProp2=Object.prototype.hasOwnProperty,__export2=(target,all)=>{for(var name in all)__defProp2(target,name,{get:all[name],enumerable:!0})},__copyProps2=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames2(from))!__hasOwnProp2.call(to,key)&&key!==except&&__defProp2(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc2(from,key))||desc.enumerable});return to},__toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps2(isNodeMode||!mod||!mod.__esModule?__defProp2(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS2=mod=>__copyProps2(__defProp2({},"__esModule",{value:!0}),mod),stdin_exports={};__export2(stdin_exports,{Counter:()=>load_import_counter().default,GREETING:()=>load_import_greet().GREETING,describe:()=>load_import_describe().describe,greet:()=>load_import_greet().greet});module.exports=__toCommonJS2(stdin_exports);var import_greet2,load_import_greet=()=>import_greet2??=(init_greet(),__toCommonJS(greet_exports)),import_counter,load_import_counter=()=>import_counter??=__toESM((init_counter(),__toCommonJS(counter_exports))),import_describe,load_import_describe=()=>import_describe??=(init_describe(),__toCommonJS(describe_exports)); + +0 && (module.exports = {"Counter": null,"GREETING": null,"describe": null,"greet": null}); diff --git a/tests/fixtures/npm/node_modules/gtlazyws/package.json b/tests/fixtures/npm/node_modules/gtlazyws/package.json new file mode 100644 index 000000000..bf5ec5b93 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtlazyws/package.json @@ -0,0 +1,7 @@ +{ + "name": "gtlazyws", + "version": "1.0.0", + "description": "the gtlazy source and plugin bundled by esbuild 0.25.10 with --minify-whitespace --minify-syntax", + "main": "./dist/index.js", + "types": "./dist/index.d.ts" +} diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/data.d.ts b/tests/fixtures/npm/node_modules/gtsubpath/dist/data.d.ts new file mode 100644 index 000000000..356c8bbc9 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/data.d.ts @@ -0,0 +1,2 @@ +import type { Manager } from "./index.js"; +export declare const managers: Manager[]; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/data.js b/tests/fixtures/npm/node_modules/gtsubpath/dist/data.js new file mode 100644 index 000000000..918a296a8 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/data.js @@ -0,0 +1,6 @@ +"use strict"; +exports.managers = [ + { name: "npm", lockfile: "package-lock.json" }, + { name: "pnpm", lockfile: "pnpm-lock.yaml" }, + { name: "yarn", lockfile: "yarn.lock" }, +]; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/detect.js b/tests/fixtures/npm/node_modules/gtsubpath/dist/detect.js new file mode 100644 index 000000000..d129041fe --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/detect.js @@ -0,0 +1,11 @@ +// A hand-rolled __toESM the recognizer cannot verify (see gtdrift). +"use strict"; +var __toESM = (mod) => { + var ns = { default: mod }; + for (var key in mod) ns[key] = mod[key]; + return ns; +}; +var import_gtcore = __toESM(require("gtcore")); +exports.detect = function detect(files) { + return import_gtcore.default.label(files.length); +}; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/index.d.ts b/tests/fixtures/npm/node_modules/gtsubpath/dist/index.d.ts new file mode 100644 index 000000000..24b05d86d --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/index.d.ts @@ -0,0 +1,6 @@ +export interface Manager { + name: string; + lockfile: string; +} +export declare const managers: Manager[]; +export declare function detect(files: string[]): string; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/dist/index.js b/tests/fixtures/npm/node_modules/gtsubpath/dist/index.js new file mode 100644 index 000000000..a330d28c0 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/dist/index.js @@ -0,0 +1,5 @@ +"use strict"; +const detect = require("./detect.js"); +const data = require("./data.js"); +exports.detect = detect.detect; +exports.managers = data.managers; diff --git a/tests/fixtures/npm/node_modules/gtsubpath/package.json b/tests/fixtures/npm/node_modules/gtsubpath/package.json new file mode 100644 index 000000000..f785cb154 --- /dev/null +++ b/tests/fixtures/npm/node_modules/gtsubpath/package.json @@ -0,0 +1,10 @@ +{ + "name": "gtsubpath", + "version": "1.0.0", + "description": "a package whose main entry reaches a file with bundler interop the recognizer cannot verify, beside a leaf data module the program imports by subpath", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "gtcore": "1.0.0" + } +} diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/index.d.ts b/tests/fixtures/npm/node_modules/tscdefault/build/index.d.ts new file mode 100644 index 000000000..7fedd198f --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/index.d.ts @@ -0,0 +1,8 @@ +import { registry } from './registry-api'; +export { registry }; +export declare const VERSION = "1.0.0"; +declare const _default: { + registry: import("./registry").Registry; + version: string; +}; +export default _default; diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/index.js b/tests/fixtures/npm/node_modules/tscdefault/build/index.js new file mode 100644 index 000000000..0dff83d00 --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/index.js @@ -0,0 +1,11 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.VERSION = exports.registry = void 0; +const registry_api_1 = require("./registry-api"); +Object.defineProperty(exports, "registry", { enumerable: true, get: function () { return registry_api_1.registry; } }); +exports.VERSION = "1.0.0"; +// Default export. +exports.default = { + registry: registry_api_1.registry, + version: "1.0.0", +}; diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.d.ts b/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.d.ts new file mode 100644 index 000000000..2010f655e --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.d.ts @@ -0,0 +1,2 @@ +import { Registry } from './registry'; +export declare const registry: Registry; diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.js b/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.js new file mode 100644 index 000000000..209ad4e9a --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/registry-api.js @@ -0,0 +1,5 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.registry = void 0; +const registry_1 = require("./registry"); +exports.registry = registry_1.Registry.getInstance(); diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/registry.d.ts b/tests/fixtures/npm/node_modules/tscdefault/build/registry.d.ts new file mode 100644 index 000000000..e9957d79d --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/registry.d.ts @@ -0,0 +1,5 @@ +export declare class Registry { + private counts; + static getInstance(): Registry; + count(name: string, n: number): number; +} diff --git a/tests/fixtures/npm/node_modules/tscdefault/build/registry.js b/tests/fixtures/npm/node_modules/tscdefault/build/registry.js new file mode 100644 index 000000000..2797e469a --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/build/registry.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Registry = void 0; +class Registry { + constructor() { + this.counts = new Map(); + } + static getInstance() { + if (!this._instance) { + this._instance = new Registry(); + } + return this._instance; + } + count(name, n) { + const next = (this.counts.get(name) ?? 0) + n; + this.counts.set(name, next); + return next; + } +} +exports.Registry = Registry; diff --git a/tests/fixtures/npm/node_modules/tscdefault/package.json b/tests/fixtures/npm/node_modules/tscdefault/package.json new file mode 100644 index 000000000..230541aaf --- /dev/null +++ b/tests/fixtures/npm/node_modules/tscdefault/package.json @@ -0,0 +1,6 @@ +{ + "name": "tscdefault", + "version": "1.0.0", + "main": "build/index.js", + "types": "build/index.d.ts" +} diff --git a/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.d.ts b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.d.ts new file mode 100644 index 000000000..1ba7851fa --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.d.ts @@ -0,0 +1,2 @@ +export declare const version: string; +export declare function compile(path: string): string; diff --git a/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.js b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.js new file mode 100644 index 000000000..c76f7222f --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/lib/index.js @@ -0,0 +1,5 @@ +"use strict"; +exports.version = "v1"; +exports.compile = function compile(path) { + return path.split("/").filter((part) => part.length > 0).join("."); +}; diff --git a/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/package.json b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/package.json new file mode 100644 index 000000000..fd67b847e --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@1.0.0/node_modules/gtpattern/package.json @@ -0,0 +1,6 @@ +{ + "name": "gtpattern", + "version": "1.0.0", + "main": "lib/index.js", + "typings": "lib/index.d.ts" +} diff --git a/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.d.ts b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.d.ts new file mode 100644 index 000000000..1ba7851fa --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.d.ts @@ -0,0 +1,2 @@ +export declare const version: string; +export declare function compile(path: string): string; diff --git a/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.js b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.js new file mode 100644 index 000000000..a192ea3ab --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/lib/index.js @@ -0,0 +1,5 @@ +"use strict"; +exports.version = "v2"; +exports.compile = function compile(path) { + return path.split("/").filter((part) => part.length > 0).map((part) => (part.startsWith(":") ? "<" + part.slice(1) + ">" : part)).join("/"); +}; diff --git a/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/package.json b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/package.json new file mode 100644 index 000000000..6eb6c6d78 --- /dev/null +++ b/tests/fixtures/npm/store/gtpattern@2.0.0/node_modules/gtpattern/package.json @@ -0,0 +1,6 @@ +{ + "name": "gtpattern", + "version": "2.0.0", + "main": "lib/index.js", + "typings": "lib/index.d.ts" +} diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern new file mode 120000 index 000000000..25c5c39d5 --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern @@ -0,0 +1 @@ +../../gtpattern@1.0.0/node_modules/gtpattern \ No newline at end of file diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern-next b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern-next new file mode 120000 index 000000000..916b8c35f --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtpattern-next @@ -0,0 +1 @@ +../../gtpattern@2.0.0/node_modules/gtpattern \ No newline at end of file diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.d.ts b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.d.ts new file mode 100644 index 000000000..2d203bf4e --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.d.ts @@ -0,0 +1 @@ +export declare function compile(path: string): string; diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.js b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.js new file mode 100644 index 000000000..da52d5e12 --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/index.js @@ -0,0 +1,7 @@ +"use strict"; +const legacy = require("gtpattern"); +const next = require("gtpattern-next"); + +exports.compile = function compile(path) { + return legacy.version + ":" + legacy.compile(path) + " " + next.version + ":" + next.compile(path); +}; diff --git a/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/package.json b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/package.json new file mode 100644 index 000000000..10bee06e7 --- /dev/null +++ b/tests/fixtures/npm/store/gtroute@1.0.0/node_modules/gtroute/package.json @@ -0,0 +1,11 @@ +{ + "name": "gtroute", + "version": "1.0.0", + "description": "depends on two releases of gtpattern, the newer one through an npm alias (\"gtpattern-next\": \"npm:gtpattern@2.0.0\"), in a pnpm store layout", + "main": "index.js", + "types": "index.d.ts", + "dependencies": { + "gtpattern": "1.0.0", + "gtpattern-next": "npm:gtpattern@2.0.0" + } +} diff --git a/tests/fixtures/self-hosting/frontend-collections.ts b/tests/fixtures/self-hosting/frontend-collections.ts index 6b6654bb2..f52e1ecaa 100644 --- a/tests/fixtures/self-hosting/frontend-collections.ts +++ b/tests/fixtures/self-hosting/frontend-collections.ts @@ -8,7 +8,7 @@ import { isNpmStaticPackage, npmStaticActive, npmStaticOffenders, npmStaticPacka for (const specifier of SUPPORTED_NODE_MODULES) { console.log(specifier, canonicalBuiltinModule(specifier), builtinDefaultImportModule(specifier)); } -for (const specifier of ["test", "node:test", "node:missing", "missing", "v8", "node:inspector"]) { +for (const specifier of ["test", "node:test", "node:missing", "missing", "vm", "node:inspector", "graceful-fs", "node:graceful-fs"]) { console.log(canonicalBuiltinModule(specifier), unsupportedModuleFeatureOf(specifier)); } clearWorkspacePackages(); diff --git a/tests/fixtures/self-hosting/lowering-context-cases.ts b/tests/fixtures/self-hosting/lowering-context-cases.ts index efa27183d..a39063796 100644 --- a/tests/fixtures/self-hosting/lowering-context-cases.ts +++ b/tests/fixtures/self-hosting/lowering-context-cases.ts @@ -35,7 +35,7 @@ function runContextCase(services: FrontendServices, entry: string): string[] { const shadowSymbol = symbols[1]!; const unusedSymbol = symbols[2]!; requireValue(outerSymbol !== shadowSymbol, "shadowed bindings share identity"); - requireValue(outerSymbol.name === shadowSymbol.name, "fixture did not shadow a name"); + requireValue(ts.symbolName(outerSymbol) === ts.symbolName(shadowSymbol), "fixture did not shadow a name"); const source = bindingSource(declarations[0]!); const root = newFnCtx(false, null, null, VOID); const outer = declareContextLocal(root, "value", F64, true, outerSymbol, source); diff --git a/tests/fixtures/self-hosting/ts7-checker-cases.ts b/tests/fixtures/self-hosting/ts7-checker-cases.ts index da0a3dc52..664dccd50 100644 --- a/tests/fixtures/self-hosting/ts7-checker-cases.ts +++ b/tests/fixtures/self-hosting/ts7-checker-cases.ts @@ -2,7 +2,7 @@ import { AstNode } from "../../../packages/compiler/src/frontend/ts7/ast-node.js import type { SourceFile } from "../../../packages/compiler/src/frontend/ts7/ast-types.js"; import { CheckerFacade, constituentTypes } from "../../../packages/compiler/src/frontend/ts7/checker.js"; import { SemanticChecker } from "../../../packages/compiler/src/frontend/ts7/semantic-checker.js"; -import { SemanticType } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import { SemanticType, symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; import { SyntaxKind, TypeFlags } from "../../../packages/compiler/src/frontend/ts7/enums.js"; export function checkerSource(): string { @@ -67,8 +67,8 @@ export function checkCheckerFacade(raw: SemanticChecker, root: SourceFile, reque const box = declaration(root, "Box"); const boxSymbol = facade.getSymbolAtLocation(box.name!)!; const boxType = facade.getDeclaredTypeOfSymbol(boxSymbol); - check(facade.getPropertyOfType(boxType, "value")!.name === "value", "property lookup"); - check(facade.getPropertiesOfType(boxType).some((property) => property.name === "method"), "property list"); + check(symbolName(facade.getPropertyOfType(boxType, "value")!) === "value", "property lookup"); + check(facade.getPropertiesOfType(boxType).some((property) => symbolName(property) === "method"), "property list"); check(facade.getBaseTypes(boxType).length === 0, "empty class bases"); const child = facade.getDeclaredTypeOfSymbol(facade.getSymbolAtLocation(declaration(root, "Child").name!)!); check(facade.getBaseTypes(child)[0] === boxType, "class base identity"); @@ -123,7 +123,7 @@ export function checkCheckerFacade(raw: SemanticChecker, root: SourceFile, reque check(facade.getTypeArguments(number).length === 0, "nonreference arguments"); const dict = facade.getDeclaredTypeOfSymbol(facade.getSymbolAtLocation(declaration(root, "Dict").name!)!); const index = facade.getIndexInfosOfType(dict)[0]!; - check(index.keyType === string && index.valueType === number && index.isReadonly, "index signature"); + check(index.keyType === string && index.type === number && index.isReadonly, "index signature"); check(facade.getAwaitedType(aliasType(facade, root, "Promised")) === number, "await promise"); check(facade.getAwaitedType(aliasType(facade, root, "MaybePromised")) === number, "await collapsed union"); check(facade.getAwaitedType(aliasType(facade, root, "DifferentPromises")) === undefined, "await distinct union refusal"); diff --git a/tests/fixtures/self-hosting/ts7-client-cases.ts b/tests/fixtures/self-hosting/ts7-client-cases.ts index d97f90892..0feaee6c9 100644 --- a/tests/fixtures/self-hosting/ts7-client-cases.ts +++ b/tests/fixtures/self-hosting/ts7-client-cases.ts @@ -12,6 +12,7 @@ import { SyntaxKind, ScriptTarget, ModuleKind, ModuleResolutionKind, ModuleDetec import { Ts7Session } from "../../../packages/compiler/src/frontend/ts7/session.js"; import { Ts7Paths, ts7DocumentFile } from "../../../packages/compiler/src/frontend/ts7/session-path.js"; import { parseSemanticJson } from "../../../packages/compiler/src/frontend/ts7/semantic-json.js"; +import { symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; import { checkSemanticModel, semanticSource } from "./ts7-semantic-cases.js"; import { checkAstViews } from "./ts7-ast-views.js"; @@ -313,7 +314,7 @@ export function runTs7Client(client: Ts7RpcClient, directory: string, report: st const timing = client.timing(); check(timing.requests > 20 && timing.callbacks > 0 && reads > 0, "requests and filesystem callbacks executed"); writeFileSync(report, JSON.stringify({ - typeText, symbol: ownedSymbol.name, diagnostics: diagnostics.map((diagnostic) => diagnostic.code), + typeText, symbol: symbolName(ownedSymbol), diagnostics: diagnostics.map((diagnostic) => diagnostic.code), surrogateBoundary: checkSurrogateBoundary(), semanticSurrogateBoundary: checkSemanticSurrogateBoundary(), semanticModel: true, sessionLifecycle: true, echo: true, binaryAst: true, astIdentity: true, astViews: true, checkerFacade: true, checkerSnapshots: true, virtualFiles: true, retainedSnapshot: true, serverErrorRecovery: true, protocolFailures: true, diff --git a/tests/fixtures/self-hosting/ts7-semantic-cases.ts b/tests/fixtures/self-hosting/ts7-semantic-cases.ts index 41b61b824..07f2d7e14 100644 --- a/tests/fixtures/self-hosting/ts7-semantic-cases.ts +++ b/tests/fixtures/self-hosting/ts7-semantic-cases.ts @@ -1,7 +1,8 @@ import { AstFile, AstNode } from "../../../packages/compiler/src/frontend/ts7/ast-node.js"; import { AstKind } from "../../../packages/compiler/src/frontend/ts7/ast-schema.generated.js"; import { SemanticChecker } from "../../../packages/compiler/src/frontend/ts7/semantic-checker.js"; -import { SemanticSignature, SemanticSnapshot, SemanticSymbol, SemanticType } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import { SemanticSignature, SemanticSnapshot, SemanticSymbol, SemanticType, symbolName, symbolNameOf } from "../../../packages/compiler/src/frontend/ts7/semantic-model.js"; +import type { __String } from "../../../packages/compiler/src/frontend/ts7/ast-types.js"; import { SemanticSignatureKind, SemanticSymbolFlags, SemanticTypeFlags } from "../../../packages/compiler/src/frontend/ts7/semantic-schema.generated.js"; export function semanticSource(): string { @@ -113,7 +114,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const pairTarget = pair.getTarget()!; check(pairTarget.isTupleType() && pairTarget.fixedLength === 2 && pairTarget.readonly === true && pairTarget.elementFlags!.length === 2, "tuple target metadata"); check(checker.getTypeArguments(pair)[0] === intrinsicNumber && checker.getTypeArguments(pair)[1] === intrinsicString, "tuple argument identities"); - check(pair.getAliasSymbol()!.name === "Pair" && pair.getAliasTypeArguments()[0] === intrinsicNumber, "alias metadata"); + check(symbolName(pair.getAliasSymbol()!) === "Pair" && pair.getAliasTypeArguments()[0] === intrinsicNumber, "alias metadata"); const box = declaration(root, "Box"); const boxSymbol = symbolAt(checker, box.name!); @@ -121,21 +122,21 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic check(boxType.isClassOrInterface() && boxType.getSymbol() === boxSymbol, "declared class type"); // The checker instantiates a transient property symbol for a class type; // the binder's member symbol is distinct but refers to the same AST node. - const boundMember = boxSymbol.getMembers().get("value")!; + const boundMember = boxSymbol.getMembers().get("value" as __String)!; const checkedMember = checker.getPropertyOfType(boxType, "value")!; check(boundMember !== checkedMember && boundMember.declarationHandles[0]!.resolve() === checkedMember.declarationHandles[0]!.resolve(), "bound and instantiated member declarations"); check(boxSymbol.getMembers() === boxSymbol.getMembers() && boxSymbol.getExports() === boxSymbol.getExports(), "symbol table cache"); check(boxType.getBaseTypes()!.length === 0 && checker.getBaseTypes(boxType).length === 0, "empty base types"); const child = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Child").name!)); check(child.getBaseTypes()![0] === boxType && checker.getBaseTypes(child)[0] === boxType, "base type request identity"); - check(checker.getPropertiesOfType(boxType).some((symbol) => symbol.name === "method"), "property query"); + check(checker.getPropertiesOfType(boxType).some((symbol) => symbolName(symbol) === "method"), "property query"); check(checker.getPropertyOfType(boxType, "missing") === undefined, "absent property"); const nullable = typeAt(checker, declaration(root, "Nullable").type!); check(checker.getNonNullableType(nullable) === boxType, "non-nullable type"); const dict = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Dict").name!)); const index = checker.getIndexInfosOfType(dict)[0]!; - check(index.keyType === intrinsicString && index.valueType === intrinsicNumber && index.isReadonly, "index signature types"); - check(index.declaration!.resolve() === declaration(root, "Dict").members?.nodes[0], "index signature declaration"); + check(index.keyType === intrinsicString && index.type === intrinsicNumber && index.isReadonly, "index signature types"); + check(index.declaration === declaration(root, "Dict").members?.nodes[0], "index signature declaration"); const generic = checker.getDeclaredTypeOfSymbol(symbolAt(checker, declaration(root, "Generic").name!)); const genericParameter = generic.getTypeParameters()[0]!; check(genericParameter.isTypeParameter() && generic.getLocalTypeParameters()[0] === genericParameter && generic.getOuterTypeParameters().length === 0, "interface type parameters"); @@ -147,7 +148,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const signature = signatureAt(checker, identity); const signatureTypes = signature.getTypeParameters(); check(signatureTypes.length === 1 && signatureTypes[0]!.isTypeParameter(), "signature type parameters"); - check(signature.getParameters()[0]!.name === "value" && signature.getParameters()[0]!.declarationHandles[0]!.resolve() === identity.parameters?.nodes[0], "signature parameters"); + check(symbolName(signature.getParameters()[0]!) === "value" && signature.getParameters()[0]!.declarationHandles[0]!.resolve() === identity.parameters?.nodes[0], "signature parameters"); check(signature.declarationHandle!.resolve() === identity, "signature declaration"); check(checker.getReturnTypeOfSignature(signature) === signatureTypes[0], "generic return identity"); check(checker.getParameterType(signature, 0) === signatureTypes[0], "parameter type query"); @@ -159,7 +160,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const ctor = checker.getSignaturesOfType(checker.getTypeOfSymbol(boxSymbol)!, SemanticSignatureKind.Construct)[0]!; check(ctor.isConstruct && !ctor.isAbstract && checker.getReturnTypeOfSignature(ctor) === boxType, "construct signature"); const thisSignature = signatureAt(checker, declaration(root, "withThis")); - check(thisSignature.hasRestParameter && !thisSignature.isConstruct && thisSignature.getThisParameter()!.name === "this", "signature this/rest flags"); + check(thisSignature.hasRestParameter && !thisSignature.isConstruct && symbolName(thisSignature.getThisParameter()!) === "this", "signature this/rest flags"); check(checker.getRestTypeOfSignature(thisSignature) === intrinsicNumber && checker.isArrayLikeType(pair), "array predicates and rest element type"); const predicate = checker.getTypePredicateOfSignature(signatureAt(checker, declaration(root, "isBox")))!; check(predicate.type === boxType && predicate.parameterIndex === 0 && predicate.parameterName === "value", "type predicate"); @@ -169,7 +170,7 @@ export function checkSemanticModel(snapshot: SemanticSnapshot, checker: Semantic const moduleSymbol = symbolAt(checker, root); const exports = moduleSymbol.getExports(); - const renamed = exports.get("renamed")!; + const renamed = exports.get("renamed" as __String)!; check(checker.getAliasedSymbol(renamed) === answerSymbol && checker.getImmediateAliasedSymbol(renamed) === answerSymbol, "symbol alias targets"); check(checker.getMemberInModuleExports(moduleSymbol, "answer") === answerSymbol, "module export lookup"); check(checker.getExportsOfModule(moduleSymbol).some((symbol) => symbol === answerSymbol), "module exports identity"); diff --git a/tests/harness/__snapshots__/arrow-bind.ts.txt b/tests/harness/__snapshots__/arrow-bind.ts.txt index 4618bdfe2..d63201d59 100644 --- a/tests/harness/__snapshots__/arrow-bind.ts.txt +++ b/tests/harness/__snapshots__/arrow-bind.ts.txt @@ -1,17 +1,8 @@ -arrow-bind.ts:2:16 - error SC1100: passing 'unknown' values where '((null | undefined)[]) => number' is expected is not supported yet +arrow-bind.ts:4:14 - error SC1100: passing 'unknown' values where '(number[]) => number' is expected is not supported yet - 1 | // Typed preset and rest signatures still exceed the callable adapter. - 2 | const preset = ((value: number) => value).bind(null, 1); - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 3 | const rest = ((...values: number[]) => values.length).bind(null); - - hint: validate with 'as ' first — the cast checks the dynamic value at runtime and throws on mismatch - -arrow-bind.ts:3:14 - error SC1100: passing 'unknown' values where '(number[]) => number' is expected is not supported yet - - 2 | const preset = ((value: number) => value).bind(null, 1); - 3 | const rest = ((...values: number[]) => values.length).bind(null); + 3 | const preset = ((value: number) => value).bind(null, 1); + 4 | const rest = ((...values: number[]) => values.length).bind(null); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 4 | export {}; + 5 | export {}; hint: validate with 'as ' first — the cast checks the dynamic value at runtime and throws on mismatch \ No newline at end of file diff --git a/tests/harness/__snapshots__/boot-path-node-surface-fences.ts.txt b/tests/harness/__snapshots__/boot-path-node-surface-fences.ts.txt new file mode 100644 index 000000000..c287a1641 --- /dev/null +++ b/tests/harness/__snapshots__/boot-path-node-surface-fences.ts.txt @@ -0,0 +1,35 @@ +boot-path-node-surface-fences.ts:8:14 - error SC2020: 'process.send' is part of the standard library types but has no scriptc lowering yet + + 7 | + 8 | const send = process.send; + | ^~~~~~~~~~~~ + 9 | console.log(typeof send); + + hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) + +boot-path-node-surface-fences.ts:11:1 - error SC2020: 'a '_write' method whose parameter 0 is 'number' where Node passes 'Uint8Array'' is part of the standard library types but has no scriptc lowering yet + + 10 | + 11 | class Counter extends Writable { + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | _write(chunk: number, _encoding: BufferEncoding, cb: (err?: Error | null) => void): void { + + hint: annotate the parameter with the Node signature's type or a supertype of it (chunks are Buffers here) + +boot-path-node-surface-fences.ts:22:31 - error SC2020: 'util.promisify of this target' is part of the standard library types but has no scriptc lowering yet + + 21 | } + 22 | const legacyAsync = promisify(legacy); + | ^~~~~~ + 23 | legacyAsync().then((v) => console.log(v)); + + hint: child_process.exec, child_process.execFile, setTimeout, setImmediate, stream.finished, stream.pipeline, the zlib callback one-shots, the crypto callback functions (randomBytes, pbkdf2, scrypt, hkdf, randomFill, randomInt, generateKey), dns.lookup, dns.lookupService, the fs callback functions with an fs/promises counterpart, and typed callback-last functions whose callback takes (error?: Error | null, value?) are the supported targets + +boot-path-node-surface-fences.ts:23:1 - error SC2004: uses of 'legacyAsync' inherit the blocker on its declaration + + 22 | const legacyAsync = promisify(legacy); + 23 | legacyAsync().then((v) => console.log(v)); + | ^~~~~~~~~~~ + 24 | + + hint: the declaration of 'legacyAsync' did not compile — fix the diagnostic reported there and these sites clear with it \ No newline at end of file diff --git a/tests/harness/__snapshots__/builtin-base-class-value-arguments.ts.txt b/tests/harness/__snapshots__/builtin-base-class-value-arguments.ts.txt new file mode 100644 index 000000000..5e3af4f45 --- /dev/null +++ b/tests/harness/__snapshots__/builtin-base-class-value-arguments.ts.txt @@ -0,0 +1,17 @@ +builtin-base-class-value-arguments.ts:9:34 - error SC2020: 'new Upper with arguments through an inherited stream constructor' is part of the standard library types but has no scriptc lowering yet + + 8 | const registry = { upper: Upper }; + 9 | const upper = new registry.upper({ objectMode: true }); + | ^~~~~~~~~~~~~~~~~~~~ + 10 | upper.destroy(); + + hint: declare a constructor that passes an inline options object to super(...) + +builtin-base-class-value-arguments.ts:13:24 - error SC2020: 'new Upper with arguments through an inherited stream constructor' is part of the standard library types but has no scriptc lowering yet + + 12 | const Kind = Upper; + 13 | const other = new Kind({ highWaterMark: 4 }); + | ^~~~~~~~~~~~~~~~~~~~ + 14 | other.destroy(); + + hint: declare a constructor that passes an inline options object to super(...) \ No newline at end of file diff --git a/tests/harness/__snapshots__/builtin-constructor-members.ts.txt b/tests/harness/__snapshots__/builtin-constructor-members.ts.txt new file mode 100644 index 000000000..c0e45edea --- /dev/null +++ b/tests/harness/__snapshots__/builtin-constructor-members.ts.txt @@ -0,0 +1,71 @@ +builtin-constructor-members.ts:5:22 - error SC2020: 'Map.prototype' is part of the standard library types but has no scriptc lowering yet + + 4 | // through globalThis or through an alias; it never reads undefined. + 5 | const mapPrototype = Map.prototype; + | ^~~~~~~~~~~~~ + 6 | const datePrototype = Date.prototype; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:6:23 - error SC2020: 'Date.prototype' is part of the standard library types but has no scriptc lowering yet + + 5 | const mapPrototype = Map.prototype; + 6 | const datePrototype = Date.prototype; + | ^~~~~~~~~~~~~~ + 7 | const regExpPrototype = RegExp.prototype; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:7:25 - error SC2020: 'RegExp.prototype' is part of the standard library types but has no scriptc lowering yet + + 6 | const datePrototype = Date.prototype; + 7 | const regExpPrototype = RegExp.prototype; + | ^~~~~~~~~~~~~~~~ + 8 | const promisePrototype = Promise.prototype; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:8:26 - error SC2020: 'Promise.prototype' is part of the standard library types but has no scriptc lowering yet + + 7 | const regExpPrototype = RegExp.prototype; + 8 | const promisePrototype = Promise.prototype; + | ^~~~~~~~~~~~~~~~~ + 9 | const weakSetPrototype = WeakSet.prototype; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:9:26 - error SC2020: 'WeakSet.prototype' is part of the standard library types but has no scriptc lowering yet + + 8 | const promisePrototype = Promise.prototype; + 9 | const weakSetPrototype = WeakSet.prototype; + | ^~~~~~~~~~~~~~~~~ + 10 | const frozen = Object.isFrozen; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:10:16 - error SC2020: 'Object.isFrozen' is part of the standard library types but has no scriptc lowering yet + + 9 | const weakSetPrototype = WeakSet.prototype; + 10 | const frozen = Object.isFrozen; + | ^~~~~~~~~~~~~~~ + 11 | const setPrototype = globalThis.Set.prototype; + + hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) + +builtin-constructor-members.ts:11:22 - error SC2020: 'SetConstructor.prototype' is part of the standard library types but has no scriptc lowering yet + + 10 | const frozen = Object.isFrozen; + 11 | const setPrototype = globalThis.Set.prototype; + | ^~~~~~~~~~~~~~~~~~~~~~~~ + 12 | const D = Date; + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead + +builtin-constructor-members.ts:13:24 - error SC2020: 'DateConstructor.prototype' is part of the standard library types but has no scriptc lowering yet + + 12 | const D = Date; + 13 | const aliasPrototype = D.prototype; + | ^~~~~~~~~~~ + 14 | // End with a nonblank context line so diagnostic snapshots carry no trailing whitespace. + + hint: prototype objects are not values here (method lookup is static) — call the method on an instance instead \ No newline at end of file diff --git a/tests/harness/__snapshots__/child-process.ts.txt b/tests/harness/__snapshots__/child-process.ts.txt index 2d83132f5..f5f9f8f86 100644 --- a/tests/harness/__snapshots__/child-process.ts.txt +++ b/tests/harness/__snapshots__/child-process.ts.txt @@ -91,4 +91,18 @@ child-process.ts:43:64 - error SC2020: 'fork without a fourth-slot IPC channel' | ^~~~~~~~ 44 | // Keep each fence on its own statement so diagnostics remain site-specific. - hint: the fourth stdio entry must be "ipc" \ No newline at end of file + hint: the fourth stdio entry must be "ipc" + +child-process.ts:48:21 - error SC1090: untyped child-stream 'data' listeners whose setEncoding state is unclear at their registration sites (declare the chunk as 'Buffer' or 'string') are not supported yet + + 47 | const pipedOut = piped.stdout!; + 48 | pipedOut.on("data", (chunk: unknown) => console.log(String(chunk))); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 49 | pipedOut.setEncoding("utf8"); + +child-process.ts:54:26 - error SC1090: untyped child-stream 'data' listeners whose setEncoding state is unclear at their registration sites (declare the chunk as 'Buffer' or 'string') are not supported yet + + 53 | pipedErr.setEncoding("utf8"); + 54 | piped.stderr!.on("data", (chunk: unknown) => console.log(String(chunk))); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 55 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/cjs-member-alias-writes/main.ts.txt b/tests/harness/__snapshots__/cjs-member-alias-writes/main.ts.txt new file mode 100644 index 000000000..1682e9bfb --- /dev/null +++ b/tests/harness/__snapshots__/cjs-member-alias-writes/main.ts.txt @@ -0,0 +1,13 @@ +cjs-member-alias-writes/main.ts:5:13 - error SC1090: the reference to 'counter' (a binding form with no lowering) is not supported yet + + 4 | import { counter, version } from "metrics"; + 5 | console.log(counter); + | ^~~~~~~ + 6 | console.log(version); + +cjs-member-alias-writes/main.ts:6:13 - error SC1090: the reference to 'version' (a binding form with no lowering) is not supported yet + + 5 | console.log(counter); + 6 | console.log(version); + | ^~~~~~~ + 7 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/class-field-accessor-descriptor.ts.txt b/tests/harness/__snapshots__/class-field-accessor-descriptor.ts.txt new file mode 100644 index 000000000..986e0d023 --- /dev/null +++ b/tests/harness/__snapshots__/class-field-accessor-descriptor.ts.txt @@ -0,0 +1,6 @@ +class-field-accessor-descriptor.ts:9:1 - error SC2020: Object.defineProperty with an accessor descriptor for the declared field 'x' is not supported yet + + 8 | const value = new Value(); + 9 | Object.defineProperty(value, "x", { + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 10 | get() { \ No newline at end of file diff --git a/tests/harness/__snapshots__/comptime.ts.txt b/tests/harness/__snapshots__/comptime.ts.txt index 2b1411ae5..757c30e6c 100644 --- a/tests/harness/__snapshots__/comptime.ts.txt +++ b/tests/harness/__snapshots__/comptime.ts.txt @@ -45,4 +45,18 @@ comptime.ts:22:23 - error SC1110: comptime evaluation failed: expected 'string' 21 | 22 | const lied = comptime(() => 1 as unknown as string); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 23 | \ No newline at end of file + 23 | + +comptime.ts:29:22 - error SC1090: baking comptime results of type '{ next: null | ...; value: number }' into the binary (number, string, boolean, and arrays, records and unions of those lower to literals; a record or union type reachable from itself does not) is not supported yet + + 28 | } + 29 | const chain: Chain = comptime(() => ({ value: 1, next: null }) as Chain); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 30 | + +comptime.ts:33:20 - error SC1090: baking comptime results of type '(...)[] | number' into the binary (number, string, boolean, and arrays, records and unions of those lower to literals; a record or union type reachable from itself does not) is not supported yet + + 32 | type Tree = number | Tree[]; + 33 | const tree: Tree = comptime((): Tree => [1, [2, [3]]]); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 34 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-divergences.txt b/tests/harness/__snapshots__/coverage-divergences.txt index 2f811f892..66373655f 100644 --- a/tests/harness/__snapshots__/coverage-divergences.txt +++ b/tests/harness/__snapshots__/coverage-divergences.txt @@ -6,7 +6,7 @@ scriptc coverage tests/coverage-fixtures/divergences.ts fully static — all analyzed statements compile to native code. - differs from Node 8 sites (compiles; the native program can behave differently) + differs from Node 5 sites (compiles; the native program can behave differently) ×1 SC6001 passing a 'Tagged' where 'Labeled' is expected copies the object (converting to a record type with other properties compiles as a field copy): the write to 'target.label' updates only the copy, while… at tests/coverage-fixtures/divergences.ts:16:9 hint: declare the parameter or binding with the value's full type ('Tagged'), or make the shape a class — class instances keep their identity when passed as a base class @@ -21,13 +21,4 @@ scriptc coverage tests/coverage-fixtures/divergences.ts hint: results agree with Node for Latin-script text; for other scripts compare explicitly (for example by code point with < and >) so both runtimes agree ×1 SC6004 this 'Circle' reaches a cast to the unrelated class 'Square' in the callee: natively the checked cast throws a TypeError when it executes, while Node reads the members structurally at tests/coverage-fixtures/divergences.ts:48:20 - hint: narrow with 'instanceof Square' before casting, or cast to a base class or interface both classes share - ×1 SC6005 the date string 'Jan 2, 2024' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', and… - at tests/coverage-fixtures/divergences.ts:51:24 - hint: write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC - ×1 SC6005 the date string '2024/01/02' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', and … - at tests/coverage-fixtures/divergences.ts:51:49 - hint: write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC - ×1 SC6005 the date string 'March 4, 2024' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', a… - at tests/coverage-fixtures/divergences.ts:52:41 - hint: write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC \ No newline at end of file + hint: narrow with 'instanceof Square' before casting, or cast to a base class or interface both classes share \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-import-fences.txt b/tests/harness/__snapshots__/coverage-import-fences.txt index 388647c55..790c36c86 100644 --- a/tests/harness/__snapshots__/coverage-import-fences.txt +++ b/tests/harness/__snapshots__/coverage-import-fences.txt @@ -4,6 +4,6 @@ scriptc coverage tests/fixtures/node-types/import-fences.ts compile statically 3 (75%) blockers: - ×2 SC1010 the 'v8' module (it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe) + ×2 SC1010 the 'inspector' module (it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect) at tests/fixtures/node-types/import-fences.ts:7:1, tests/fixtures/node-types/import-fences.ts:12:1 - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported \ No newline at end of file + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported \ No newline at end of file diff --git a/tests/harness/__snapshots__/coverage-wasm.txt b/tests/harness/__snapshots__/coverage-wasm.txt index 5b5bea36a..9d0ca414a 100644 --- a/tests/harness/__snapshots__/coverage-wasm.txt +++ b/tests/harness/__snapshots__/coverage-wasm.txt @@ -1,19 +1,22 @@ 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) + statements analyzed 9 + compile statically 9 (100%) + in dynamic tier 4 (operate on values typed only at runtime; --explain-dynamic lists them) npm package compilation: wasm-fixture-pkg static - WebAssembly modules: (found at build time; none compile to native code yet) + WebAssembly modules: (found at build time; valid ones compile to native code) inline module 39 bytes valid 1 function, 0 imports, 1 export found: inline at tests/coverage-fixtures/wasm/glue.js:5 tests/coverage-fixtures/wasm/add.wasm 41 bytes valid 1 function, 0 imports, 1 export found: path at tests/coverage-fixtures/wasm/glue.js:8 + tests/coverage-fixtures/wasm/ext.so 36 bytes valid + 1 function, 0 imports, 1 export + found: path at tests/coverage-fixtures/wasm/glue.js:15 tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm 27 bytes invalid: type mismatch (at byte 27) 1 function, 0 imports, 0 exports found: shipped by the package at tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg @@ -22,10 +25,4 @@ scriptc coverage tests/coverage-fixtures/wasm/main.ts features: simd found: configured by tests/coverage-fixtures/wasm/package.json - in unreached code (never lowered — cannot fail a build) - ×1 SC2004 uses of 'instance' inherit the blocker on its declaration - at tests/coverage-fixtures/wasm/glue.js:17:10 - hint: the declaration of 'instance' did not compile — fix the diagnostic reported there and these sites clear with it - ×1 SC2020 'WebAssembly.instantiate' is not supported yet: scriptc does not compile WebAssembly modules to native code yet - at tests/coverage-fixtures/wasm/glue.js:16:30 - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) \ No newline at end of file + fully static — all analyzed statements compile to native code. \ No newline at end of file diff --git a/tests/harness/__snapshots__/events-once-fences.ts.txt b/tests/harness/__snapshots__/events-once-fences.ts.txt index ffab538b0..60c499cfe 100644 --- a/tests/harness/__snapshots__/events-once-fences.ts.txt +++ b/tests/harness/__snapshots__/events-once-fences.ts.txt @@ -1,17 +1,26 @@ -events-once-fences.ts:8:27 - error SC2020: 'events.once with options' is part of the standard library types but has no scriptc lowering yet +events-once-fences.ts:10:27 - error SC2020: 'events.once options that are not an object literal' is part of the standard library types but has no scriptc lowering yet - 7 | async function main(): Promise { - 8 | await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 9 | await once(ee, name); // computed event name + 9 | async function main(): Promise { + 10 | await once(ee, "ready", options); // options that are not an object literal + | ^~~~~~~ + 11 | await once(ee, name); // computed event name - hint: the lowered form is events.once(emitter, name); AbortSignal options are not supported yet + hint: pass the options as an object literal: { signal } -events-once-fences.ts:9:18 - error SC2020: 'events.once with a computed or meta event name' is part of the standard library types but has no scriptc lowering yet +events-once-fences.ts:11:18 - error SC2020: 'events.once with a computed or meta event name' is part of the standard library types but has no scriptc lowering yet - 8 | await once(ee, "ready", { signal: AbortSignal.timeout(5) }); // options - 9 | await once(ee, name); // computed event name + 10 | await once(ee, "ready", options); // options that are not an object literal + 11 | await once(ee, name); // computed event name | ^~~~ - 10 | } + 12 | await once(new PassThrough(), "data"); // a stream's 'data' event - hint: name the awaited event with a string literal \ No newline at end of file + hint: name the awaited event with a string literal + +events-once-fences.ts:12:33 - error SC2020: 'events.once of a stream's 'data' event' is part of the standard library types but has no scriptc lowering yet + + 11 | await once(ee, name); // computed event name + 12 | await once(new PassThrough(), "data"); // a stream's 'data' event + | ^~~~~~ + 13 | } + + hint: the chunk has no checked-dynamic boxing here; register stream.once('data', listener) instead \ No newline at end of file diff --git a/tests/harness/__snapshots__/fromentries-aliased-entries.ts.txt b/tests/harness/__snapshots__/fromentries-aliased-entries.ts.txt new file mode 100644 index 000000000..7b0f02266 --- /dev/null +++ b/tests/harness/__snapshots__/fromentries-aliased-entries.ts.txt @@ -0,0 +1,8 @@ +fromentries-aliased-entries.ts:8:16 - error SC2002: record shapes must match exactly or width-coerce: expected '{ a: number[]; b: number[] }', got '{ [key: string]: number[] }' — 'a' is not a declared field of the source, and the source's index signature could hold it at runtime (a completed undefined would drop that value) + + 7 | const shared = [10]; + 8 | const scores = Object.fromEntries([ + | ^~~~~~~~~~~~~~~~~~~~ + 9 | ["a", shared], + + hint: width subtyping compiles as a copy (each expected field copied off the source; extra fields drop) — this pair is outside the copy's rules; build a literal with exactly the expected fields instead \ No newline at end of file diff --git a/tests/harness/__snapshots__/imports.ts.txt b/tests/harness/__snapshots__/imports.ts.txt index ef48d5031..9db4b4f6f 100644 --- a/tests/harness/__snapshots__/imports.ts.txt +++ b/tests/harness/__snapshots__/imports.ts.txt @@ -4,7 +4,7 @@ imports.ts:1:1 - error SC1010: the 'some-package' package (nothing installed res | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 | import { join } from "node:path"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported imports.ts:1:27 - error SC0001: Cannot find module 'some-package' or its corresponding type declarations. diff --git a/tests/harness/__snapshots__/inferred-callback-drops-conditional.ts.txt b/tests/harness/__snapshots__/inferred-callback-drops-conditional.ts.txt new file mode 100644 index 000000000..a14f19317 --- /dev/null +++ b/tests/harness/__snapshots__/inferred-callback-drops-conditional.ts.txt @@ -0,0 +1,6 @@ +inferred-callback-drops-conditional.ts:10:32 - error SC1090: returning '{ extra: boolean; n: number; name: string }[]' from a callback whose result the checker infers as the narrower '{ n: number; name: string }[]' (the conversion would drop 'extra', which the object keeps at run time — annotate the callback's return type or the call's type argument with a type that declares them) is not supported yet + + 9 | const items: Item[] = run(() => + 10 | plain.length === 0 ? plain : plain.map((x) => ({ ...x, extra: true })), + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 11 | ); \ No newline at end of file diff --git a/tests/harness/__snapshots__/inferred-callback-drops-satisfies.ts.txt b/tests/harness/__snapshots__/inferred-callback-drops-satisfies.ts.txt new file mode 100644 index 000000000..ea1e37549 --- /dev/null +++ b/tests/harness/__snapshots__/inferred-callback-drops-satisfies.ts.txt @@ -0,0 +1,6 @@ +inferred-callback-drops-satisfies.ts:11:10 - error SC1090: returning '{ extra: boolean; n: number; name: string }[]' from a callback whose result the checker infers as the narrower '{ n: number; name: string }[]' (the conversion would drop 'extra', which the object keeps at run time — annotate the callback's return type or the call's type argument with a type that declares them) is not supported yet + + 10 | if (plain.length === 0) return plain; + 11 | return plain.map((x) => ({ ...x, extra: true })) satisfies Plain[]; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | }); \ No newline at end of file diff --git a/tests/harness/__snapshots__/inferred-callback-drops.ts.txt b/tests/harness/__snapshots__/inferred-callback-drops.ts.txt new file mode 100644 index 000000000..daa62f484 --- /dev/null +++ b/tests/harness/__snapshots__/inferred-callback-drops.ts.txt @@ -0,0 +1,6 @@ +inferred-callback-drops.ts:11:10 - error SC1090: returning '{ extra: boolean; n: number; name: string }[]' from a callback whose result the checker infers as the narrower '{ n: number; name: string }[]' (the conversion would drop 'extra', which the object keeps at run time — annotate the callback's return type or the call's type argument with a type that declares them) is not supported yet + + 10 | if (plain.length === 0) return plain; + 11 | return plain.map((x) => ({ ...x, extra: true })); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | }); \ No newline at end of file diff --git a/tests/harness/__snapshots__/json-shape/main.ts.txt b/tests/harness/__snapshots__/json-shape/main.ts.txt index b03f098a6..85f5d2917 100644 --- a/tests/harness/__snapshots__/json-shape/main.ts.txt +++ b/tests/harness/__snapshots__/json-shape/main.ts.txt @@ -1,6 +1,6 @@ -json-shape/main.ts:5:8 - error SC2001: values of type '{ name: string; deprecated: null; }' cannot be compiled yet (supported: number, string, boolean, arrays, Maps, Sets, RegExp, read-only Date values, functions, classes, records, unions of those, and 'unknown') +json-shape/main.ts:5:8 - error SC1110: comptime evaluation failed: expected 'number' at $.limit, got Infinity (only finite numbers can be written as literals) - 4 | * engine) and uses of the binding poison. */ + 4 | * field bakes as null.) */ 5 | import bad from "./nulled.json" with { type: "json" }; | ^~~ 6 | diff --git a/tests/harness/__snapshots__/local-class-boundaries.ts.txt b/tests/harness/__snapshots__/local-class-boundaries.ts.txt index 04dbc336c..6706a1278 100644 --- a/tests/harness/__snapshots__/local-class-boundaries.ts.txt +++ b/tests/harness/__snapshots__/local-class-boundaries.ts.txt @@ -1,12 +1,12 @@ local-class-boundaries.ts:1:34 - error SC1090: function-local classes with private brands are not supported yet - 1 | function privateBrand() { return class { #value = 1; read(): number { return this.#value; } }; } - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 | function privateBrand() { return class { #value = 1; has(other: object): boolean { return #value in other; } }; } + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 2 | function selfName() { return class Local { copy() { return new Local(); } }; } local-class-boundaries.ts:2:64 - error SC1090: named self references inside function-local classes are not supported yet - 1 | function privateBrand() { return class { #value = 1; read(): number { return this.#value; } }; } + 1 | function privateBrand() { return class { #value = 1; has(other: object): boolean { return #value in other; } }; } 2 | function selfName() { return class Local { copy() { return new Local(); } }; } | ^~~~~ 3 | class Base {} diff --git a/tests/harness/__snapshots__/local-class-type-query-brand.ts.txt b/tests/harness/__snapshots__/local-class-type-query-brand.ts.txt index 6db3418a8..ea877dec3 100644 --- a/tests/harness/__snapshots__/local-class-type-query-brand.ts.txt +++ b/tests/harness/__snapshots__/local-class-type-query-brand.ts.txt @@ -1,6 +1,6 @@ -local-class-type-query-brand.ts:4:3 - error SC1090: function-local classes with private brands are not supported yet +local-class-type-query-brand.ts:5:3 - error SC1090: function-local classes with private brands are not supported yet - 3 | function make() { - 4 | class Box { + 4 | function make() { + 5 | class Box { | ^~~~~~~~~~~ - 5 | #v = 1; \ No newline at end of file + 6 | #v = 1; \ No newline at end of file diff --git a/tests/harness/__snapshots__/maybe-promise-fences.ts.txt b/tests/harness/__snapshots__/maybe-promise-fences.ts.txt index 58fb23517..159e88f76 100644 --- a/tests/harness/__snapshots__/maybe-promise-fences.ts.txt +++ b/tests/harness/__snapshots__/maybe-promise-fences.ts.txt @@ -55,11 +55,4 @@ maybe-promise-fences.ts:32:13 - error SC1090: 'instanceof Promise' on '{ v: numb 31 | const record = { v: 1 }; 32 | console.log(record instanceof Promise); | ^~~~~~~~~~~~~~~~~~~~~~~~~ - 33 | - -maybe-promise-fences.ts:36:26 - error SC1090: '(unknown) => Promise' values where '() => Promise' is expected is not supported yet - - 35 | type HandlerFn = (...args: unknown[]) => Promise; - 36 | const fixed: HandlerFn = async (first: unknown) => first; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 37 | fixed(1, 2).then((v) => console.log(v)); \ No newline at end of file + 33 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/mock-fences.ts.txt b/tests/harness/__snapshots__/mock-fences.ts.txt index 6b4defcbb..d4b8115e8 100644 --- a/tests/harness/__snapshots__/mock-fences.ts.txt +++ b/tests/harness/__snapshots__/mock-fences.ts.txt @@ -1,62 +1,62 @@ -mock-fences.ts:14:15 - error SC2020: 'mock.fn of this function type' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:18:17 - error SC2020: 'mock.fn() of a function returning 'number'' is part of the standard library types but has no scriptc lowering yet - 13 | - 14 | const empty = mock.fn(); // no original function - | ^~~~~~~~~ - 15 | empty(); + 17 | + 18 | const counted = mock.fn<() => number>(); // no original, returns number + | ^~~~~~~~~~~~~~~~~~~~~~~ + 19 | counted(); - hint: mock functions lower for functions with fixed parameters, or a single rest array parameter, and an implementation of the same type + hint: mock.fn() without an original returns undefined: pass an original, or use a function type whose return type includes undefined -mock-fences.ts:27:13 - error SC2020: 'mock.method on 'Counter'' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:31:13 - error SC2020: 'mock.method on 'Counter'' is part of the standard library types but has no scriptc lowering yet - 26 | } - 27 | mock.method(new Counter(), "count"); // class instance + 30 | } + 31 | mock.method(new Counter(), "count"); // class instance | ^~~~~~~~~~~~~ - 28 | mock.method(console, "log"); // builtin object + 32 | mock.method(console, "log"); // builtin object hint: mock.method lowers for objects whose type maps to a record (object literals and interface-typed objects) and a function-valued field named by a string literal -mock-fences.ts:28:13 - error SC2020: 'mock.method on this object' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:32:13 - error SC2020: 'mock.method on this object' is part of the standard library types but has no scriptc lowering yet - 27 | mock.method(new Counter(), "count"); // class instance - 28 | mock.method(console, "log"); // builtin object + 31 | mock.method(new Counter(), "count"); // class instance + 32 | mock.method(console, "log"); // builtin object | ^~~~~~~ - 29 | mock.method(obj, "value", { times: 1 }); // options + 33 | mock.method(obj, "value", { times: 1 }); // options hint: mock.method lowers for objects whose type maps to a record (object literals and interface-typed objects) and a function-valued field named by a string literal -mock-fences.ts:29:27 - error SC2020: 'mock.method with an options object' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:33:27 - error SC2020: 'mock.method with an options object' is part of the standard library types but has no scriptc lowering yet - 28 | mock.method(console, "log"); // builtin object - 29 | mock.method(obj, "value", { times: 1 }); // options + 32 | mock.method(console, "log"); // builtin object + 33 | mock.method(obj, "value", { times: 1 }); // options | ^~~~~~~~~~~~ - 30 | const other = mock.method(obj, "value", () => "one"); // implementation of another type + 34 | const other = mock.method(obj, "value", () => "one"); // implementation of another type hint: mock options (times, getter and setter) have no lowering -mock-fences.ts:30:41 - error SC2020: 'mock.method with an implementation of another type' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:34:41 - error SC2020: 'mock.method with an implementation of another type' is part of the standard library types but has no scriptc lowering yet - 29 | mock.method(obj, "value", { times: 1 }); // options - 30 | const other = mock.method(obj, "value", () => "one"); // implementation of another type + 33 | mock.method(obj, "value", { times: 1 }); // options + 34 | const other = mock.method(obj, "value", () => "one"); // implementation of another type | ^~~~~~~~~~~ - 31 | other(); + 35 | other(); hint: give the implementation the method's parameter and return types, or discard the result -mock-fences.ts:35:31 - error SC2020: 't.mock.method with an options object' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:39:31 - error SC2020: 't.mock.method with an options object' is part of the standard library types but has no scriptc lowering yet - 34 | t.mock.method(obj, "value"); // lowers - 35 | t.mock.method(obj, "value", { times: 2 }); // options + 38 | t.mock.method(obj, "value"); // lowers + 39 | t.mock.method(obj, "value", { times: 2 }); // options | ^~~~~~~~~~~~ - 36 | }); + 40 | }); hint: mock options (times, getter and setter) have no lowering -mock-fences.ts:38:14 - error SC2020: 'mock.fn of a function returning 'number | string'' is part of the standard library types but has no scriptc lowering yet +mock-fences.ts:42:14 - error SC2020: 'mock.fn of a function returning 'number | string'' is part of the standard library types but has no scriptc lowering yet - 37 | - 38 | const pick = mock.fn((flag: boolean): number | string => (flag ? 1 : "one")); // union without undefined + 41 | + 42 | const pick = mock.fn((flag: boolean): number | string => (flag ? 1 : "one")); // union without undefined | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 39 | pick(true); + 43 | pick(true); hint: a union return type needs an undefined arm here (the calls log records undefined for calls that throw) \ No newline at end of file diff --git a/tests/harness/__snapshots__/module-out-of-scope.ts.txt b/tests/harness/__snapshots__/module-out-of-scope.ts.txt index 47e241459..a5ef44c6b 100644 --- a/tests/harness/__snapshots__/module-out-of-scope.ts.txt +++ b/tests/harness/__snapshots__/module-out-of-scope.ts.txt @@ -1,31 +1,31 @@ -module-out-of-scope.ts:6:1 - error SC1010: the 'node:v8' module (it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe) is not supported yet +module-out-of-scope.ts:6:1 - error SC1010: the 'node:vm' module (it compiles and runs JavaScript source at runtime, and a compiled binary embeds no JavaScript engine to run it) is not supported yet - 5 | // (stream/web here) keeps the plain not-yet wording. - 6 | import { getHeapStatistics } from "node:v8"; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 5 | // surface (repl here) keeps the plain not-yet wording. + 6 | import { runInNewContext } from "node:vm"; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 7 | import { create } from "domain"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported -module-out-of-scope.ts:6:35 - error SC0001: Cannot find name 'node:v8'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. +module-out-of-scope.ts:6:33 - error SC0001: Cannot find name 'node:vm'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. - 5 | // (stream/web here) keeps the plain not-yet wording. - 6 | import { getHeapStatistics } from "node:v8"; - | ^~~~~~~~~ + 5 | // surface (repl here) keeps the plain not-yet wording. + 6 | import { runInNewContext } from "node:vm"; + | ^~~~~~~~~ 7 | import { create } from "domain"; module-out-of-scope.ts:7:1 - error SC1010: the 'domain' module (deprecated in Node and slated for removal; its implicit error interception hooks every async callback at engine level, which is not modeled) is not supported yet - 6 | import { getHeapStatistics } from "node:v8"; + 6 | import { runInNewContext } from "node:vm"; 7 | import { create } from "domain"; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | import { DatabaseSync } from "node:sqlite"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported module-out-of-scope.ts:7:24 - error SC0001: Cannot find name 'domain'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. - 6 | import { getHeapStatistics } from "node:v8"; + 6 | import { runInNewContext } from "node:vm"; 7 | import { create } from "domain"; | ^~~~~~~~ 8 | import { DatabaseSync } from "node:sqlite"; @@ -37,7 +37,7 @@ module-out-of-scope.ts:8:1 - error SC1010: the 'node:sqlite' module (it wraps th | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | import { HTTPParser } from "_http_common"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported module-out-of-scope.ts:8:30 - error SC0001: Cannot find name 'node:sqlite'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. @@ -53,7 +53,7 @@ module-out-of-scope.ts:9:1 - error SC1010: the '_http_common' module (a Node-int | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | import { open } from "node:inspector"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported module-out-of-scope.ts:9:28 - error SC0001: Cannot find module '_http_common' or its corresponding type declarations. @@ -67,29 +67,29 @@ module-out-of-scope.ts:10:1 - error SC1010: the 'node:inspector' module (it driv 9 | import { HTTPParser } from "_http_common"; 10 | import { open } from "node:inspector"; | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 11 | import { ReadableStream } from "node:stream/web"; + 11 | import { start } from "node:repl"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported module-out-of-scope.ts:10:22 - error SC0001: Cannot find name 'node:inspector'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. 9 | import { HTTPParser } from "_http_common"; 10 | import { open } from "node:inspector"; | ^~~~~~~~~~~~~~~~ - 11 | import { ReadableStream } from "node:stream/web"; + 11 | import { start } from "node:repl"; -module-out-of-scope.ts:11:1 - error SC1010: the 'node:stream/web' module is not supported yet +module-out-of-scope.ts:11:1 - error SC1010: the 'node:repl' module is not supported yet 10 | import { open } from "node:inspector"; - 11 | import { ReadableStream } from "node:stream/web"; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 11 | import { start } from "node:repl"; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported -module-out-of-scope.ts:11:32 - error SC0001: Cannot find name 'node:stream/web'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. +module-out-of-scope.ts:11:23 - error SC0001: Cannot find name 'node:repl'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. 10 | import { open } from "node:inspector"; - 11 | import { ReadableStream } from "node:stream/web"; - | ^~~~~~~~~~~~~~~~~ + 11 | import { start } from "node:repl"; + | ^~~~~~~~~~~ 12 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/object-assign-class-copy.ts.txt b/tests/harness/__snapshots__/object-assign-class-copy.ts.txt new file mode 100644 index 000000000..ce25cc38f --- /dev/null +++ b/tests/harness/__snapshots__/object-assign-class-copy.ts.txt @@ -0,0 +1,6 @@ +object-assign-class-copy.ts:10:32 - error SC1090: Object.assign copying a 'Config' instance into a fresh object typed as the class (the copy is a plain object holding the instance's own fields, not an instance — build the object literal from the fields you need) is not supported yet + + 9 | const config = new Config(); + 10 | const copy = Object.assign({}, config); + | ^~~~~~ + 11 | console.log(JSON.stringify(copy), Object.keys(copy).join(",")); \ No newline at end of file diff --git a/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt b/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt new file mode 100644 index 000000000..372849388 --- /dev/null +++ b/tests/harness/__snapshots__/object-literal-accessor-values.ts.txt @@ -0,0 +1,6 @@ +object-literal-accessor-values.ts:13:28 - error SC1090: JSON.stringify of '{ get doubled(): number; count: number }' values (only number, string, boolean, records, arrays, unions of those, and 'unknown' stringify) are not supported yet + + 12 | // JSON.stringify of the typed value would omit the getter's property. + 13 | console.log(JSON.stringify(counter)); + | ^~~~~~~ + 14 | \ No newline at end of file diff --git a/tests/harness/__snapshots__/options-record.ts.txt b/tests/harness/__snapshots__/options-record.ts.txt index ab89613c0..26ca22db3 100644 --- a/tests/harness/__snapshots__/options-record.ts.txt +++ b/tests/harness/__snapshots__/options-record.ts.txt @@ -25,15 +25,6 @@ options-record.ts:20:22 - error SC2020: 'get with agent: false and a non-literal hint: agent: false lowers by injecting Connection: close into a LITERAL headers object — add connection: "close" to the record instead, or write the headers inline -options-record.ts:23:22 - error SC2020: 'get option 'socketPath'' is part of the standard library types but has no scriptc lowering yet - - 22 | // Documented client options with no lowering fence by name. - 23 | http.get({ port: 80, socketPath: "/tmp/sock.s" }, () => {}); - | ^~~~~~~~~~~~~~~~~~~~~~~~~ - 24 | http.get({ port: 80, localAddress: "127.0.0.1" }, () => {}); - - hint: hostname/host, port, path, method, timeout, headers, agent, signal, and maxHeaderSize are the supported options - options-record.ts:24:22 - error SC2020: 'get option 'localAddress'' is part of the standard library types but has no scriptc lowering yet 23 | http.get({ port: 80, socketPath: "/tmp/sock.s" }, () => {}); diff --git a/tests/harness/__snapshots__/pkg-import-fences/main.ts.txt b/tests/harness/__snapshots__/pkg-import-fences/main.ts.txt index f99018eb4..280743462 100644 --- a/tests/harness/__snapshots__/pkg-import-fences/main.ts.txt +++ b/tests/harness/__snapshots__/pkg-import-fences/main.ts.txt @@ -5,7 +5,7 @@ pkg-import-fences/main.ts:7:1 - error SC1010: the 'ambientonly' module (it exist | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 8 | import { y } from "patternXmod"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported pkg-import-fences/main.ts:8:1 - error SC1010: the 'patternXmod' module (it exists only as an ambient 'declare module' type surface — there is no runtime module to import) is not supported yet @@ -14,7 +14,7 @@ pkg-import-fences/main.ts:8:1 - error SC1010: the 'patternXmod' module (it exist | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 9 | import { t } from "#types"; - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported pkg-import-fences/main.ts:9:1 - error SC1010: the '#types' import (it resolves only to type declarations — there is no runtime implementation to compile) is not supported yet @@ -23,4 +23,4 @@ pkg-import-fences/main.ts:9:1 - error SC1010: the '#types' import (it resolves o | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | - hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported \ No newline at end of file + hint: relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, v8, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported \ No newline at end of file diff --git a/tests/harness/__snapshots__/promise-all-fences.ts.txt b/tests/harness/__snapshots__/promise-all-fences.ts.txt index 8d17f3f57..18018a8b5 100644 --- a/tests/harness/__snapshots__/promise-all-fences.ts.txt +++ b/tests/harness/__snapshots__/promise-all-fences.ts.txt @@ -1,26 +1,17 @@ -promise-all-fences.ts:6:34 - error SC2020: 'Promise.all over this argument shape' is part of the standard library types but has no scriptc lowering yet +promise-all-fences.ts:7:44 - error SC2020: 'Promise.allSettled over this argument shape' is part of the standard library types but has no scriptc lowering yet - 5 | async function hetero(): Promise { - 6 | const pair = await Promise.all([ - | ^ - 7 | new Promise((resolve) => resolve("s")), - - hint: the entries must share ONE promise type — Promise.all([p, q]) lowers when p and q are the same Promise - -promise-all-fences.ts:13:44 - error SC2020: 'Promise.allSettled over this argument shape' is part of the standard library types but has no scriptc lowering yet - - 12 | async function settled(): Promise { - 13 | const results = await Promise.allSettled([ - | ^ - 14 | new Promise((resolve) => resolve("s")), + 6 | async function settled(): Promise { + 7 | const results = await Promise.allSettled([ + | ^ + 8 | new Promise((resolve) => resolve("s")), hint: the entries must share ONE promise type — Promise.allSettled([p, q]) lowers when p and q are the same Promise -promise-all-fences.ts:23:33 - error SC2020: 'Promise.any over this argument shape' is part of the standard library types but has no scriptc lowering yet +promise-all-fences.ts:17:33 - error SC2020: 'Promise.any over this argument shape' is part of the standard library types but has no scriptc lowering yet - 22 | async function anyOfGenerator(): Promise { - 23 | console.log(await Promise.any(pending())); + 16 | async function anyOfGenerator(): Promise { + 17 | console.log(await Promise.any(pending())); | ^~~~~~~~~ - 24 | } + 18 | } hint: an array of promises (Promise[]) is the supported form \ No newline at end of file diff --git a/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt b/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt index 531002a4b..4bbff5a33 100644 --- a/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt +++ b/tests/harness/__snapshots__/retired-engine-2083-destructuring-island-decl.ts.txt @@ -1,66 +1,3 @@ -retired-engine-2083-destructuring-island-decl.ts:9:5 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 8 | const a: any = [1, "two", true, 4, 5]; - 9 | var [x, , y, ...rest] = a; - | ^~~~~~~~~~~~~~~~~~~~~ - 10 | console.log(`${x} ${y} ${rest.length} ${rest[0]} ${rest[1]}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:12:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 11 | const o: any = { p: 1, q: { r: "deep" } }; - 12 | const { p, q: { r } } = o; - | ^~~~~~~~~~~~~~~~~~~ - 13 | console.log(`${p} ${r}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:14:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 13 | console.log(`${p} ${r}`); - 14 | const { missing = 42 } = o; - | ^~~~~~~~~~~~~~~~~~~~ - 15 | console.log(`${missing}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:16:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 15 | console.log(`${missing}`); - 16 | const { p: p2 = 9, ...others } = o; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 17 | console.log(`${p2} ${others.q.r}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:17:22 - error SC2004: uses of 'others' inherit the blocker on its declaration - - 16 | const { p: p2 = 9, ...others } = o; - 17 | console.log(`${p2} ${others.q.r}`); - | ^~~~~~~~~~ - 18 | var [] = [1, 2] as any; - - hint: the declaration of 'others' did not compile — fix the diagnostic reported there and these sites clear with it - -retired-engine-2083-destructuring-island-decl.ts:19:5 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 18 | var [] = [1, 2] as any; - 19 | var {} = o; - | ^~~~~~ - 20 | console.log("empties ok"); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - -retired-engine-2083-destructuring-island-decl.ts:22:11 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 21 | const u: any = undefined; - 22 | try { var {} = u; } catch (e) { console.log("obj-undef throws"); } - | ^~~~~~ - 23 | try { var [] = 0 as any; } catch (e) { console.log("arr-num throws"); } - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2083-destructuring-island-decl.ts:23:11 - error SC2011: destructuring on 'any'-typed values cannot compile statically 22 | try { var {} = u; } catch (e) { console.log("obj-undef throws"); } diff --git a/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt b/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt index b41cd4d10..415eb5895 100644 --- a/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt +++ b/tests/harness/__snapshots__/retired-engine-2104-computed-key-destructuring.ts.txt @@ -1,12 +1,10 @@ -retired-engine-2104-computed-key-destructuring.ts:12:9 - error SC2011: destructuring on 'any'-typed values cannot compile statically +retired-engine-2104-computed-key-destructuring.ts:12:11 - error SC1031: destructuring with computed keys that do not fold to one property name is not supported yet 11 | function pick(key: string) { 12 | const { [key]: got } = src; - | ^~~~~~~~~~~~~~~~~~~~ + | ^~~~~~~~~~ 13 | return got; - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:13:10 - error SC2004: uses of 'got' inherit the blocker on its declaration 12 | const { [key]: got } = src; @@ -16,15 +14,13 @@ retired-engine-2104-computed-key-destructuring.ts:13:10 - error SC2004: uses of hint: the declaration of 'got' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2104-computed-key-destructuring.ts:17:9 - error SC2011: destructuring on 'any'-typed values cannot compile statically +retired-engine-2104-computed-key-destructuring.ts:17:11 - error SC1031: destructuring with computed keys that do not fold to one property name is not supported yet 16 | function pick2(key: string) { 17 | const { [key]: got = "dflt" } = src; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + | ^~~~~~~~~~~~~~~~~~~ 18 | return got; - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:18:10 - error SC2004: uses of 'got' inherit the blocker on its declaration 17 | const { [key]: got = "dflt" } = src; @@ -34,15 +30,6 @@ retired-engine-2104-computed-key-destructuring.ts:18:10 - error SC2004: uses of hint: the declaration of 'got' did not compile — fix the diagnostic reported there and these sites clear with it -retired-engine-2104-computed-key-destructuring.ts:23:7 - error SC2011: destructuring on 'any'-typed values cannot compile statically - - 22 | const key2 = "y"; - 23 | const { x: sx, [key2]: sy } = src; - | ^~~~~~~~~~~~~~~~~~~~~~~~~~~ - 24 | console.log(`${sx} ${sy}`); - - hint: use a supported typed operation, or 'unknown' with a checked cast ('x as T') - retired-engine-2104-computed-key-destructuring.ts:30:38 - error SC2009: values of type '[any?]' cannot be compiled: the tuple shape is supported, but its element type 'any' does not compile 29 | let order = (n: any): any => { traceS += `${n},`; return n; }; diff --git a/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt b/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt deleted file mode 100644 index 008901f2d..000000000 --- a/tests/harness/__snapshots__/retired-engine-761-any-objects.ts.txt +++ /dev/null @@ -1,6 +0,0 @@ -retired-engine-761-any-objects.ts:13:38 - error SC1090: reading 'length' from a value of type 'any' is not supported yet - - 12 | const s: any = "hello world"; - 13 | console.log(`${s.toUpperCase()}`, `${s.split(" ").length}`, `${s.charAt(4)}`); - | ^~~~~~~~~~~~~~~~~~~ - 14 | const num: any = 3.14159; \ No newline at end of file diff --git a/tests/harness/__snapshots__/speculative-function-refusal.ts.txt b/tests/harness/__snapshots__/speculative-function-refusal.ts.txt index c8b0cd28c..f0030d432 100644 --- a/tests/harness/__snapshots__/speculative-function-refusal.ts.txt +++ b/tests/harness/__snapshots__/speculative-function-refusal.ts.txt @@ -1,17 +1,8 @@ -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 +speculative-function-refusal.ts:11:15 - error SC2020: '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 + 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__/stdlib-fence.ts.txt b/tests/harness/__snapshots__/stdlib-fence.ts.txt index b813840d8..b0ebed494 100644 --- a/tests/harness/__snapshots__/stdlib-fence.ts.txt +++ b/tests/harness/__snapshots__/stdlib-fence.ts.txt @@ -50,14 +50,14 @@ stdlib-fence.ts:20:13 - error SC2020: 'new Intl.NumberFormat' is part of the sta hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) -stdlib-fence.ts:23:11 - error SC2020: 'new Date with 2 arguments' is part of the standard library types but has no scriptc lowering yet +stdlib-fence.ts:23:11 - error SC2020: 'new Date with spread arguments' is part of the standard library types but has no scriptc lowering yet 22 | // Constructors. - 23 | const d = new Date(2024, 0); // the local-time year/month field constructor stays fenced - | ^~~~~~~~~~~~~~~~~ + 23 | const d = new Date(...([2024, 0] as const)); // spread field arguments stay fenced + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | const mutableDate = new Date(0); - hint: new Date(), new Date(milliseconds), and new Date(dateString) are supported; the local-time year/month field constructor has no lowering + hint: new Date(), new Date(milliseconds), new Date(dateString), and new Date(year, monthIndex, ...) with listed arguments are supported stdlib-fence.ts:25:1 - error SC2020: 'Date.prototype.setUTCFullYear with arguments' is part of the standard library types but has no scriptc lowering yet diff --git a/tests/harness/__snapshots__/stream-completion-callback-error.ts.txt b/tests/harness/__snapshots__/stream-completion-callback-error.ts.txt new file mode 100644 index 000000000..f3b876cc7 --- /dev/null +++ b/tests/harness/__snapshots__/stream-completion-callback-error.ts.txt @@ -0,0 +1,17 @@ +stream-completion-callback-error.ts:10:17 - error SC2020: 'write completion callbacks taking the error argument' is part of the standard library types but has no scriptc lowering yet + + 9 | }); + 10 | sink.write("a", (err) => console.log("written", err)); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 11 | sink.end((err?: Error | null) => console.log("ended", err)); + + hint: () => void callbacks are supported (listen for 'error' to observe failures) + +stream-completion-callback-error.ts:11:10 - error SC2020: 'end callbacks with parameters' is part of the standard library types but has no scriptc lowering yet + + 10 | sink.write("a", (err) => console.log("written", err)); + 11 | sink.end((err?: Error | null) => console.log("ended", err)); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 12 | + + hint: () => void callbacks are supported \ No newline at end of file diff --git a/tests/harness/__snapshots__/thread-slots-fences.ts.txt b/tests/harness/__snapshots__/thread-slots-fences.ts.txt index 566ddfba6..9d4648c27 100644 --- a/tests/harness/__snapshots__/thread-slots-fences.ts.txt +++ b/tests/harness/__snapshots__/thread-slots-fences.ts.txt @@ -59,4 +59,13 @@ thread-slots-fences.ts:28:16 - error SC2020: 'ThreadSlots.get (@scriptc/experime | ^~~~~~~~~ 29 | console.log(named, second, shaped, callbacks, local(first, new Key()), ctor, getter); - hint: call slots.get(...) directly \ No newline at end of file + hint: call slots.get(...) directly + +thread-slots-fences.ts:34:13 - error SC2003: union conversion has no unambiguous layout mapping: expected 'State | undefined', got '{ count: number } | undefined' (each source arm needs an identical destination arm, a unique compatible record/array layout, or a shared literal discriminant selecting compatible layouts) + + 33 | const records: ThreadSlots = new ThreadSlots("fences.records"); + 34 | console.log(records.get(new Key())); + | ^~~~~~~~~~~~~~~~~~~~~~ + 35 | + + hint: narrow the value to a SINGLE arm first (a discriminant check, or '!== undefined'/'!== null' for unit arms); a plain arm value widens into any union that contains it, and a whole union widens into any union whose arms include the source's \ No newline at end of file diff --git a/tests/harness/__snapshots__/variadic-wrapper-fences.ts.txt b/tests/harness/__snapshots__/variadic-wrapper-fences.ts.txt new file mode 100644 index 000000000..0cb2774d2 --- /dev/null +++ b/tests/harness/__snapshots__/variadic-wrapper-fences.ts.txt @@ -0,0 +1,29 @@ +variadic-wrapper-fences.ts:6:7 - error SC2009: values of type '() => any' cannot be compiled: the function shape is supported, but its signature is variadic ('arguments'-reading), and a compiled signature is fixed-arity + + 5 | // `arguments` with no fixed signature to lower against. + 6 | const loose = function () { + | ^~~~~ + 7 | return arguments[0]; + + hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') + +variadic-wrapper-fences.ts:7:10 - error SC1090: 'arguments' in a function expression whose value is not asserted to one fixed-arity signature (assert it, as in `function () { … } as F`, and the function lowers against that signature) is not supported yet + + 6 | const loose = function () { + 7 | return arguments[0]; + | ^~~~~~~~~ + 8 | }; + +variadic-wrapper-fences.ts:12:10 - error SC1090: reading 'fn.name' when the call passes a function whose name is only known at run time (generic wrappers fold the name of a function declaration, a const-bound function, or a wrapper call's result) is not supported yet (instantiating 'named' with <() => number>) + + 11 | function named(fn: F): string { + 12 | return fn.name; + | ^~~~~~~ + 13 | } + +variadic-wrapper-fences.ts:25:10 - error SC1090: forwarding a fixed argument tuple through 'apply' to a variadic function value is not supported yet (instantiating 'forward' with <[number, number]>) + + 24 | function forward(sink: (...xs: number[]) => number, ...args: A): number { + 25 | return sink.apply(null, args); + | ^~~~~~~~~~~~~~~~~~~~~~ + 26 | } \ No newline at end of file diff --git a/tests/harness/__snapshots__/webassembly-fences.ts.txt b/tests/harness/__snapshots__/webassembly-fences.ts.txt index da38a2c57..49c423707 100644 --- a/tests/harness/__snapshots__/webassembly-fences.ts.txt +++ b/tests/harness/__snapshots__/webassembly-fences.ts.txt @@ -1,98 +1,26 @@ -webassembly-fences.ts:10:15 - error SC2020: 'WebAssembly.validate' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:9:26 - error SC2020: 'WebAssembly.instantiateStreaming' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API - 9 | async function load(options: LoaderOptions, bytes: Uint8Array): Promise { - 10 | console.log(WebAssembly.validate(bytes)); - | ^~~~~~~~~~~~~~~~~~~~ - 11 | const module = await WebAssembly.compile(bytes); + 8 | console.log(WebAssembly.Module.imports(module).length); + 9 | const streamed = await WebAssembly.instantiateStreaming(fetch("https://example.com/x.wasm")); + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 10 | console.log(streamed.instance); - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) + hint: the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) -webassembly-fences.ts:11:24 - error SC2020: 'WebAssembly.compile' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:11:16 - error SC2020: 'WebAssembly.Module' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API - 10 | console.log(WebAssembly.validate(bytes)); - 11 | const module = await WebAssembly.compile(bytes); - | ^~~~~~~~~~~~~~~~~~~ - 12 | const { instance } = await WebAssembly.instantiate(bytes, options.imports); + 10 | console.log(streamed.instance); + 11 | const Ctor = WebAssembly.Module; + | ^~~~~~~~~~~~~~~~~~ + 12 | console.log(Ctor); - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) + hint: the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) -webassembly-fences.ts:12:30 - error SC2020: 'WebAssembly.instantiate' is not supported yet: scriptc does not compile WebAssembly modules to native code yet +webassembly-fences.ts:13:18 - error SC2020: 'WebAssembly.Memory' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API - 11 | const module = await WebAssembly.compile(bytes); - 12 | const { instance } = await WebAssembly.instantiate(bytes, options.imports); - | ^~~~~~~~~~~~~~~~~~~~~~~ - 13 | console.log(instance instanceof WebAssembly.Instance, module); + 12 | console.log(Ctor); + 13 | const Memory = WebAssembly.Memory; + | ^~~~~~~~~~~~~~~~~~ + 14 | console.log(new Memory({ initial: 1 })); - hint: WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations) - -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 + hint: the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations) \ No newline at end of file diff --git a/tests/harness/bench-dynamic-tier.test.ts b/tests/harness/bench-dynamic-tier.test.ts new file mode 100644 index 000000000..de19f2b21 --- /dev/null +++ b/tests/harness/bench-dynamic-tier.test.ts @@ -0,0 +1,65 @@ +/* Two-tier invariant I3 for the runtime benchmark workloads, in CI: the + * release executable of a fully typed workload defines no dynamic-tier + * runtime symbol (DYNAMIC_TIER_PREFIXES), and every workload the zero-cost + * gate lists as dynamic (DYNAMIC_WORKLOADS) still defines one, so the list + * shrinks as leaks close. The gate (scripts/zero-cost-gate.mjs) checks the + * same contract, but only when someone runs it. The contract is the Linux + * release binary the gate measures, so other hosts and the sanitized lane + * skip the builds. */ +import { execFile } from "node:child_process"; +import { mkdirSync, readFileSync } from "node:fs"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; +import { + DYNAMIC_WORKLOADS, + definedFunctionNames, + dynamicTierSymbols, +} from "../../scripts/zero-cost-compare.mjs"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const suite = join(repoRoot, "benchmarks/runtime"); +const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests/bench-dynamic-tier"); +const skip = process.platform !== "linux" || process.env["SCRIPTC_SAN"] === "1"; + +interface Workload { + name: string; + entry: string; +} + +// One program per entry file, named after its first workload, as the gate +// names them. +const manifest = JSON.parse(readFileSync(join(suite, "workloads.json"), "utf8")) as { + workloads: Workload[]; +}; +const programs = new Map(); +for (const w of manifest.workloads) + if (![...programs.values()].includes(w.entry)) programs.set(`bench/${w.name}`, w.entry); + +test("every workload the gate lists as dynamic is a bench program", () => { + for (const id of DYNAMIC_WORKLOADS) expect(programs.has(id), id).toBe(true); +}); + +test.skipIf(skip).each([...programs])( + "%s links the dynamic tier only if listed", + async (id, entry) => { + const outDir = join(cacheDir, id.slice("bench/".length)); + mkdirSync(outDir, { recursive: true }); + const result = await compile(join(suite, entry), { outPath: join(outDir, "program"), outDir }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + const { stdout } = await execFileAsync("nm", [result.binaryPath], { + encoding: "utf8", + maxBuffer: 1 << 28, + }); + const linked = dynamicTierSymbols(definedFunctionNames(stdout)); + if (DYNAMIC_WORKLOADS.has(id)) + expect( + linked.length, + `${id} links no dynamic-tier symbol: drop it from DYNAMIC_WORKLOADS`, + ).toBeGreaterThan(0); + else expect(linked, `fully typed ${id} links dynamic-tier symbols`).toEqual([]); + }, +); diff --git a/tests/harness/ci-lanes.test.ts b/tests/harness/ci-lanes.test.ts index c0e74a934..faca8a7d0 100644 --- a/tests/harness/ci-lanes.test.ts +++ b/tests/harness/ci-lanes.test.ts @@ -1,7 +1,22 @@ -import { readFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; import { load } from "js-yaml"; import { expect, test } from "vitest"; import { select } from "../../scripts/ci-lanes.mjs"; +import { darwinCorpusFiles, darwinFiles } from "../../scripts/ci-test-plan.mjs"; + +type Job = { + if?: string; + "runs-on"?: string; + strategy?: { matrix?: { include?: { runner?: string }[] } }; + steps: { id?: string; run?: string }[]; +}; +const workflow = load( + readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), +) as { on: { workflow_dispatch: { inputs: Record } }; jobs: Record }; +const lanesScript = workflow.jobs["lanes"]!.steps.find((step) => step.id === "lanes")!.run!; type Selection = { on: Record; reasons: string[] }; const lanesOf = (files: string[], lines: { file: string; text: string }[] = []) => @@ -43,6 +58,16 @@ test.each([ ["tests/harness/ffi-frameworks.test.ts"], { linux: true, macos: true, effects: false, bootstrap: false }, ], + [ + "the kqueue event loop", + ["packages/runtime/src/scr_loop_kqueue.c"], + { linux: true, macos: true, effects: false, bootstrap: false }, + ], + [ + "the runtime's platform header", + ["packages/runtime/src/scr_platform.h"], + { linux: true, macos: true, effects: false, bootstrap: false }, + ], [ "the Effect harness", ["tests/harness/effect.test.ts"], @@ -91,15 +116,61 @@ test("a changed line that mentions a darwin-specific API runs the macOS lane", ( expect(lanesOf([file], [{ file, text: " struct kevent change;" }]).macos).toBe(true); }); +test("every file of the darwin suite runs the macOS lane", () => { + for (const file of [...darwinCorpusFiles, ...darwinFiles]) + expect(lanesOf([file]).macos, file).toBe(true); +}); + test("CI runs the base commit's lane selection against the pull request's diff", () => { - const workflow = load( - readFileSync(new URL("../../.github/workflows/ci.yml", import.meta.url), "utf8"), - ) as { jobs: Record }; - const script = workflow.jobs["lanes"]!.steps.find((step) => step.id === "lanes")!.run!; - expect(script).toContain('git show "$BASE:scripts/ci-lanes.mjs"'); - expect(script).toContain( + expect(lanesScript).toContain('git show "$BASE:scripts/ci-lanes.mjs"'); + expect(lanesScript).toContain( 'node "$RUNNER_TEMP/ci-lanes.mjs" --base "$BASE" --head "$HEAD" --github-output', ); - // Pushes and the nightly sweep, and a base without the script, run every lane. - expect(script.match(/every$/gm)).toHaveLength(2); + // The nightly sweep and manual runs, and a base without the script, run every lane. + expect(lanesScript.match(/every$/gm)).toHaveLength(2); +}); + +function runLanes(event: string) { + const directory = mkdtempSync(join(tmpdir(), "ci-lanes-")); + try { + const output = join(directory, "output"); + writeFileSync(output, ""); + const result = spawnSync("bash", ["-e", "-o", "pipefail", "-c", lanesScript], { + encoding: "utf8", + env: { ...process.env, EVENT: event, GITHUB_OUTPUT: output }, + }); + expect(result.status, result.stderr).toBe(0); + return readFileSync(output, "utf8"); + } finally { + rmSync(directory, { recursive: true, force: true }); + } +} + +test("pushes to main run every lane except macos; the nightly sweep and manual runs run every lane", () => { + expect(runLanes("push")).toBe("linux=true\nmacos=false\neffects=true\nbootstrap=true\n"); + for (const event of ["schedule", "workflow_dispatch"]) + expect(runLanes(event), event).toBe("linux=true\nmacos=true\neffects=true\nbootstrap=true\n"); + expect(workflow.on.workflow_dispatch.inputs["macos_suite"]).toEqual({ + description: "The test suite to run on macOS", + type: "choice", + options: ["full", "darwin"], + default: "full", + }); +}); + +test("every job on a macOS runner is in the macos lane", () => { + const macosJobs = Object.entries(workflow.jobs).filter(([, job]) => + [job["runs-on"], ...(job.strategy?.matrix?.include ?? []).map((entry) => entry.runner)].some( + (runner) => runner?.startsWith("macos"), + ), + ); + expect(macosJobs.map(([name]) => name).sort()).toEqual([ + "llvm_artifacts_macos_arm64", + "llvm_artifacts_macos_x64", + "mobile_runtime_packs_ios", + "native_macos", + "tests_macos", + ]); + for (const [name, job] of macosJobs) + expect(job.if, name).toContain("needs.lanes.outputs.macos == 'true'"); }); diff --git a/tests/harness/ci-test-plan.test.ts b/tests/harness/ci-test-plan.test.ts index 3b8c6d4e1..34d61346f 100644 --- a/tests/harness/ci-test-plan.test.ts +++ b/tests/harness/ci-test-plan.test.ts @@ -10,6 +10,7 @@ import { fileExclusions, caseShardedSideFiles, isolatedFiles, + leakSuppressions, testCosts, } from "../../scripts/ci-test-plan.mjs"; import { runCiTestPlan } from "../../scripts/ci-test.mjs"; @@ -66,6 +67,20 @@ test.for([1, 3, 5, 10, 20])( }, ); +test("LeakSanitizer runs over the sanitized corpus on Linux only", () => { + const plan = (flavor: "plain" | "san", platform: string) => + ciTestPlan({ shard: "1/20", flavor, workers: 4, platform }); + const linuxSan = plan("san", "linux").corpus.env; + expect(linuxSan.ASAN_OPTIONS).toBe("detect_leaks=1"); + expect(linuxSan.LSAN_OPTIONS).toBe(`suppressions=${leakSuppressions}:print_suppressions=0`); + expect(existsSync(leakSuppressions)).toBe(true); + for (const env of [plan("plain", "linux").corpus.env, plan("san", "darwin").corpus.env]) { + expect(env.ASAN_OPTIONS).toBeUndefined(); + expect(env.LSAN_OPTIONS).toBeUndefined(); + } + for (const task of plan("san", "linux").side) expect(task.env.ASAN_OPTIONS).toBeUndefined(); +}); + test("the side queue balances measured files across the twenty Linux shards", () => { for (const flavor of ["plain", "san"] as const) { const costs = testCosts[flavor] as Record; @@ -186,12 +201,12 @@ test.for([1, 2, 3])("the darwin suite retains every darwin file with %i shards", /** Expands the ${{ }} expressions of a workflow value for one event: string * literals, ==, !=, !, &&, || and fromJSON, with matrix.shard left as * "{shard}". A value that is a single expression returns its result. */ -function expand(value: string, context: { event: string; macosFull: boolean | null }): unknown { +function expand(value: string, context: { event: string; macosSuite: string | null }): unknown { const evaluate = (expression: string): unknown => new Function( `return (${expression .replaceAll("github.event_name", JSON.stringify(context.event)) - .replaceAll("inputs.macos_full", JSON.stringify(context.macosFull)) + .replaceAll("inputs.macos_suite", JSON.stringify(context.macosSuite)) .replaceAll("fromJSON", "JSON.parse")});`, )(); const single = /^\$\{\{(.*)\}\}$/s.exec(value); @@ -225,18 +240,17 @@ test("pull requests run the full suite on Linux and the darwin suite on macOS", ).toBe("native-linux-x64-gnu"); expect(macos["runs-on"]).toBe("macos-15"); expect(macos.needs).toContain("native_macos"); - // Pull requests, pushes to main and plain manual runs run the darwin suite in - // three slices; the nightly sweep and manual runs with macos_full run the + // Pull requests and manual runs with macos_suite set to darwin run the darwin + // suite in three slices; the nightly sweep and other manual runs run the // complete suite in ten. - for (const [event, macosFull, full] of [ + for (const [event, macosSuite, full] of [ ["pull_request", null, false], - ["push", null, false], - ["workflow_dispatch", false, false], - ["workflow_dispatch", true, true], + ["workflow_dispatch", "darwin", false], + ["workflow_dispatch", "full", true], ["schedule", null, true], ] as const) { - const context = { event, macosFull }; - const label = `${event} ${macosFull}`; + const context = { event, macosSuite }; + const label = `${event} ${macosSuite}`; expect(expand(String(macos.strategy.matrix.shard), context), label).toEqual( full ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] : [1, 2, 3], ); diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index 30899d72b..85e4ba4aa 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -1886,6 +1886,7 @@ "5464-locale-compare-fractions.ts": 2833, "5465-dyn-index-read-arrays.ts": 3603, "5466-dyn-index-read-special.ts": 2823, + "5469-typed-array-loop-initializer-rebinding.ts": 1453, "5470-strip-vt-osc.cjs": 2497, "5471-strip-vt-csi.cjs": 2651, "5472-strip-vt-random.cjs": 2375, @@ -2129,6 +2130,9 @@ "5873-readline-promises-namespace.cjs": 2023, "5874-worker-stdio-streams.ts": 2839, "5875-worker-stdout-pipe.cjs": 2740, + "5876-readline-dispose-instanceof.ts": 2465, + "5877-readline-question-query.cjs": 3233, + "5878-readline-dynamic-streams.mjs": 2925, "5879-worker-stdin.ts": 3158, "5880-emitter-leak-warning-delivery.ts": 3418, "5881-inspect-weak-collections.ts": 2615, @@ -2186,9 +2190,6 @@ "5940-callback-helper-specialization.ts": 3109, "5941-callback-specialization-recursion.ts": 3112, "5942-callback-specialization-throws.ts": 3221, - "5942-local-class-observed-methods.ts": 5063, - "5943-fields-read-before-assignment.ts": 6856, - "5944-js-fields-read-before-assignment.js": 4247, "5945-local-class-observed-methods.ts": 5063, "5946-nested-record-field-optional-writes.ts": 6045, "5950-tuple-predicate-methods.ts": 5990, @@ -2230,6 +2231,8 @@ "6001-json-stringify-js-class-instances.js": 4668, "6002-inspect-class-added-properties.ts": 6044, "6003-inspect-js-class-properties.js": 2752, + "6004-class-instance-integrity.ts": 10548, + "6005-class-instance-integrity-sloppy.cjs": 7466, "601-closures-loops.ts": 1788, "6010-match-metadata.ts": 3990, "6011-match-metadata-js.js": 3660, @@ -4980,6 +4983,7 @@ "5464-locale-compare-fractions.ts": 3312, "5465-dyn-index-read-arrays.ts": 3798, "5466-dyn-index-read-special.ts": 4384, + "5469-typed-array-loop-initializer-rebinding.ts": 2058, "5470-strip-vt-osc.cjs": 2708, "5471-strip-vt-csi.cjs": 3038, "5472-strip-vt-random.cjs": 2850, @@ -5223,6 +5227,9 @@ "5873-readline-promises-namespace.cjs": 3811, "5874-worker-stdio-streams.ts": 27801, "5875-worker-stdout-pipe.cjs": 31391, + "5876-readline-dispose-instanceof.ts": 29361, + "5877-readline-question-query.cjs": 170940, + "5878-readline-dynamic-streams.mjs": 25503, "5879-worker-stdin.ts": 33232, "5880-emitter-leak-warning-delivery.ts": 4319, "5881-inspect-weak-collections.ts": 2915, @@ -5280,9 +5287,6 @@ "5940-callback-helper-specialization.ts": 4548, "5941-callback-specialization-recursion.ts": 4448, "5942-callback-specialization-throws.ts": 3443, - "5942-local-class-observed-methods.ts": 6212, - "5943-fields-read-before-assignment.ts": 72293, - "5944-js-fields-read-before-assignment.js": 40310, "5945-local-class-observed-methods.ts": 6212, "5946-nested-record-field-optional-writes.ts": 68117, "5950-tuple-predicate-methods.ts": 7790, @@ -5324,6 +5328,8 @@ "6001-json-stringify-js-class-instances.js": 7469, "6002-inspect-class-added-properties.ts": 9670, "6003-inspect-js-class-properties.js": 4403, + "6004-class-instance-integrity.ts": 16877, + "6005-class-instance-integrity-sloppy.cjs": 11946, "601-closures-loops.ts": 2297, "6010-match-metadata.ts": 5450, "6011-match-metadata-js.js": 4890, diff --git a/tests/harness/corpus-costs/5730-thread-slots-widened-values.ts.json b/tests/harness/corpus-costs/5730-thread-slots-widened-values.ts.json new file mode 100644 index 000000000..444a9b634 --- /dev/null +++ b/tests/harness/corpus-costs/5730-thread-slots-widened-values.ts.json @@ -0,0 +1 @@ +{ "plain": 2849, "san": 4164 } diff --git a/tests/harness/corpus-costs/5775-md5-lengths.ts.json b/tests/harness/corpus-costs/5775-md5-lengths.ts.json new file mode 100644 index 000000000..70f936cec --- /dev/null +++ b/tests/harness/corpus-costs/5775-md5-lengths.ts.json @@ -0,0 +1 @@ +{"plain":2850,"san":3660} diff --git a/tests/harness/corpus-costs/5779-sha512-lengths.ts.json b/tests/harness/corpus-costs/5779-sha512-lengths.ts.json new file mode 100644 index 000000000..9e04a316c --- /dev/null +++ b/tests/harness/corpus-costs/5779-sha512-lengths.ts.json @@ -0,0 +1 @@ +{"plain":2610,"san":3540} diff --git a/tests/harness/corpus-costs/5816-fixed-into-unknown-rest.ts.json b/tests/harness/corpus-costs/5816-fixed-into-unknown-rest.ts.json new file mode 100644 index 000000000..2b149eb6a --- /dev/null +++ b/tests/harness/corpus-costs/5816-fixed-into-unknown-rest.ts.json @@ -0,0 +1 @@ +{ "plain": 3028, "san": 5470 } diff --git a/tests/harness/corpus-costs/5817-constructor-function-instanceof.cjs.json b/tests/harness/corpus-costs/5817-constructor-function-instanceof.cjs.json new file mode 100644 index 000000000..15eedbeaa --- /dev/null +++ b/tests/harness/corpus-costs/5817-constructor-function-instanceof.cjs.json @@ -0,0 +1 @@ +{ "plain": 2655, "san": 7944 } diff --git a/tests/harness/corpus-costs/5818-constructor-function-emitter-defaults.cjs.json b/tests/harness/corpus-costs/5818-constructor-function-emitter-defaults.cjs.json new file mode 100644 index 000000000..0f4eb2e02 --- /dev/null +++ b/tests/harness/corpus-costs/5818-constructor-function-emitter-defaults.cjs.json @@ -0,0 +1 @@ +{ "plain": 2746, "san": 51663 } diff --git a/tests/harness/corpus-costs/5819-node-stream-dyn-direct-pipe/main.ts.json b/tests/harness/corpus-costs/5819-node-stream-dyn-direct-pipe/main.ts.json new file mode 100644 index 000000000..12637a176 --- /dev/null +++ b/tests/harness/corpus-costs/5819-node-stream-dyn-direct-pipe/main.ts.json @@ -0,0 +1 @@ +{ "plain": 4912, "san": 12058 } diff --git a/tests/harness/corpus-costs/5820-node-stream-dyn-constructor-overrides/main.ts.json b/tests/harness/corpus-costs/5820-node-stream-dyn-constructor-overrides/main.ts.json new file mode 100644 index 000000000..f8ccf0699 --- /dev/null +++ b/tests/harness/corpus-costs/5820-node-stream-dyn-constructor-overrides/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3490, "san": 8050 } diff --git a/tests/harness/corpus-costs/5916-dyn-error-stack-property.ts.json b/tests/harness/corpus-costs/5916-dyn-error-stack-property.ts.json new file mode 100644 index 000000000..3ed0acb36 --- /dev/null +++ b/tests/harness/corpus-costs/5916-dyn-error-stack-property.ts.json @@ -0,0 +1 @@ +{ "plain": 3232, "san": 4298 } diff --git a/tests/harness/corpus-costs/5983-runtime-constructor.ts.json b/tests/harness/corpus-costs/5983-runtime-constructor.ts.json new file mode 100644 index 000000000..5506cd9a8 --- /dev/null +++ b/tests/harness/corpus-costs/5983-runtime-constructor.ts.json @@ -0,0 +1 @@ +{ "plain": 3500, "san": 6900 } diff --git a/tests/harness/corpus-costs/6055-class-generator-iterables.ts.json b/tests/harness/corpus-costs/6055-class-generator-iterables.ts.json new file mode 100644 index 000000000..68b6f9db4 --- /dev/null +++ b/tests/harness/corpus-costs/6055-class-generator-iterables.ts.json @@ -0,0 +1 @@ +{ "plain": 4921, "san": 6451 } diff --git a/tests/harness/corpus-costs/6056-class-symbol-method-calls.ts.json b/tests/harness/corpus-costs/6056-class-symbol-method-calls.ts.json new file mode 100644 index 000000000..4207a0ed0 --- /dev/null +++ b/tests/harness/corpus-costs/6056-class-symbol-method-calls.ts.json @@ -0,0 +1 @@ +{ "plain": 3468, "san": 5120 } diff --git a/tests/harness/corpus-costs/6058-class-view-asserted-bindings.ts.json b/tests/harness/corpus-costs/6058-class-view-asserted-bindings.ts.json new file mode 100644 index 000000000..80e35e544 --- /dev/null +++ b/tests/harness/corpus-costs/6058-class-view-asserted-bindings.ts.json @@ -0,0 +1 @@ +{ "plain": 6139, "san": 10783 } diff --git a/tests/harness/corpus-costs/6061-type-only-class-fields/main.ts.json b/tests/harness/corpus-costs/6061-type-only-class-fields/main.ts.json new file mode 100644 index 000000000..546e4a9e8 --- /dev/null +++ b/tests/harness/corpus-costs/6061-type-only-class-fields/main.ts.json @@ -0,0 +1 @@ +{ "plain": 6644, "san": 11367 } diff --git a/tests/harness/corpus-costs/6063-union-class-field-checks.ts.json b/tests/harness/corpus-costs/6063-union-class-field-checks.ts.json new file mode 100644 index 000000000..bba381595 --- /dev/null +++ b/tests/harness/corpus-costs/6063-union-class-field-checks.ts.json @@ -0,0 +1 @@ +{ "plain": 4152, "san": 6474 } diff --git a/tests/harness/corpus-costs/6230-date-parse-legacy.ts.json b/tests/harness/corpus-costs/6230-date-parse-legacy.ts.json new file mode 100644 index 000000000..38848e321 --- /dev/null +++ b/tests/harness/corpus-costs/6230-date-parse-legacy.ts.json @@ -0,0 +1 @@ +{ "plain": 2490, "san": 3133 } diff --git a/tests/harness/corpus-costs/6231-date-local-fields.ts.json b/tests/harness/corpus-costs/6231-date-local-fields.ts.json new file mode 100644 index 000000000..900da7a06 --- /dev/null +++ b/tests/harness/corpus-costs/6231-date-local-fields.ts.json @@ -0,0 +1 @@ +{ "plain": 2472, "san": 3365 } diff --git a/tests/harness/corpus-costs/6232-date-fields-js.cjs.json b/tests/harness/corpus-costs/6232-date-fields-js.cjs.json new file mode 100644 index 000000000..dce549ea0 --- /dev/null +++ b/tests/harness/corpus-costs/6232-date-fields-js.cjs.json @@ -0,0 +1 @@ +{ "plain": 2875, "san": 3637 } diff --git a/tests/harness/corpus-costs/6233-object-identity-dynamic.cjs.json b/tests/harness/corpus-costs/6233-object-identity-dynamic.cjs.json new file mode 100644 index 000000000..18fc9038e --- /dev/null +++ b/tests/harness/corpus-costs/6233-object-identity-dynamic.cjs.json @@ -0,0 +1 @@ +{ "plain": 4636, "san": 4061 } diff --git a/tests/harness/corpus-costs/6234-object-prototype-values.cjs.json b/tests/harness/corpus-costs/6234-object-prototype-values.cjs.json new file mode 100644 index 000000000..2681d28e6 --- /dev/null +++ b/tests/harness/corpus-costs/6234-object-prototype-values.cjs.json @@ -0,0 +1 @@ +{ "plain": 3149, "san": 3979 } diff --git a/tests/harness/corpus-costs/6235-object-identity-typed.ts.json b/tests/harness/corpus-costs/6235-object-identity-typed.ts.json new file mode 100644 index 000000000..2929ddfc7 --- /dev/null +++ b/tests/harness/corpus-costs/6235-object-identity-typed.ts.json @@ -0,0 +1 @@ +{ "plain": 2802, "san": 3441 } diff --git a/tests/harness/corpus-costs/6236-builtin-function-values.cjs.json b/tests/harness/corpus-costs/6236-builtin-function-values.cjs.json new file mode 100644 index 000000000..c01634b4a --- /dev/null +++ b/tests/harness/corpus-costs/6236-builtin-function-values.cjs.json @@ -0,0 +1 @@ +{ "plain": 3572, "san": 4025 } diff --git a/tests/harness/corpus-costs/6341-throw-array-identity.ts.json b/tests/harness/corpus-costs/6341-throw-array-identity.ts.json new file mode 100644 index 000000000..2c49e2f02 --- /dev/null +++ b/tests/harness/corpus-costs/6341-throw-array-identity.ts.json @@ -0,0 +1 @@ +{ "plain": 3800, "san": 4900 } diff --git a/tests/harness/corpus-costs/6342-throw-builtin-identity.ts.json b/tests/harness/corpus-costs/6342-throw-builtin-identity.ts.json new file mode 100644 index 000000000..e2254bc18 --- /dev/null +++ b/tests/harness/corpus-costs/6342-throw-builtin-identity.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3900 } diff --git a/tests/harness/corpus-costs/6345-inspect-dyn-holes.ts.json b/tests/harness/corpus-costs/6345-inspect-dyn-holes.ts.json new file mode 100644 index 000000000..d80769f29 --- /dev/null +++ b/tests/harness/corpus-costs/6345-inspect-dyn-holes.ts.json @@ -0,0 +1 @@ +{ "plain": 2100, "san": 2300 } diff --git a/tests/harness/corpus-costs/6346-inspect-dyn-dataview.ts.json b/tests/harness/corpus-costs/6346-inspect-dyn-dataview.ts.json new file mode 100644 index 000000000..2b1382076 --- /dev/null +++ b/tests/harness/corpus-costs/6346-inspect-dyn-dataview.ts.json @@ -0,0 +1 @@ +{ "plain": 1700, "san": 2200 } diff --git a/tests/harness/corpus-costs/6347-dataview-methods-js.cjs.json b/tests/harness/corpus-costs/6347-dataview-methods-js.cjs.json new file mode 100644 index 000000000..e053739a0 --- /dev/null +++ b/tests/harness/corpus-costs/6347-dataview-methods-js.cjs.json @@ -0,0 +1 @@ +{ "plain": 1500, "san": 2000 } diff --git a/tests/harness/corpus-costs/6348-errno-code-own-field.ts.json b/tests/harness/corpus-costs/6348-errno-code-own-field.ts.json new file mode 100644 index 000000000..28b033d9b --- /dev/null +++ b/tests/harness/corpus-costs/6348-errno-code-own-field.ts.json @@ -0,0 +1 @@ +{ "plain": 1200, "san": 1500 } diff --git a/tests/harness/corpus-costs/6356-format-integer-dyn.ts.json b/tests/harness/corpus-costs/6356-format-integer-dyn.ts.json new file mode 100644 index 000000000..8bcdf4a18 --- /dev/null +++ b/tests/harness/corpus-costs/6356-format-integer-dyn.ts.json @@ -0,0 +1 @@ +{ "plain": 2000, "san": 2000 } diff --git a/tests/harness/corpus-costs/6380-variadic-wrapper-arguments-apply.ts.json b/tests/harness/corpus-costs/6380-variadic-wrapper-arguments-apply.ts.json new file mode 100644 index 000000000..553943f5a --- /dev/null +++ b/tests/harness/corpus-costs/6380-variadic-wrapper-arguments-apply.ts.json @@ -0,0 +1 @@ +{ "plain": 3870, "san": 3660 } diff --git a/tests/harness/corpus-costs/6381-variadic-wrapper-async.ts.json b/tests/harness/corpus-costs/6381-variadic-wrapper-async.ts.json new file mode 100644 index 000000000..5cad95fef --- /dev/null +++ b/tests/harness/corpus-costs/6381-variadic-wrapper-async.ts.json @@ -0,0 +1 @@ +{ "plain": 3550, "san": 4820 } diff --git a/tests/harness/corpus-costs/6382-variadic-wrapper-methods-this.ts.json b/tests/harness/corpus-costs/6382-variadic-wrapper-methods-this.ts.json new file mode 100644 index 000000000..644fc5fea --- /dev/null +++ b/tests/harness/corpus-costs/6382-variadic-wrapper-methods-this.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 6260 } diff --git a/tests/harness/corpus-costs/6383-variadic-wrapper-rest-tuple.ts.json b/tests/harness/corpus-costs/6383-variadic-wrapper-rest-tuple.ts.json new file mode 100644 index 000000000..f83a73df7 --- /dev/null +++ b/tests/harness/corpus-costs/6383-variadic-wrapper-rest-tuple.ts.json @@ -0,0 +1 @@ +{ "plain": 3140, "san": 4540 } diff --git a/tests/harness/corpus-costs/6384-variadic-wrapper-nested.ts.json b/tests/harness/corpus-costs/6384-variadic-wrapper-nested.ts.json new file mode 100644 index 000000000..21dee9274 --- /dev/null +++ b/tests/harness/corpus-costs/6384-variadic-wrapper-nested.ts.json @@ -0,0 +1 @@ +{ "plain": 3220, "san": 2960 } diff --git a/tests/harness/corpus-costs/6385-variadic-wrapper-shared-local-names.ts.json b/tests/harness/corpus-costs/6385-variadic-wrapper-shared-local-names.ts.json new file mode 100644 index 000000000..187f1764f --- /dev/null +++ b/tests/harness/corpus-costs/6385-variadic-wrapper-shared-local-names.ts.json @@ -0,0 +1 @@ +{ "plain": 2200, "san": 3300 } diff --git a/tests/harness/corpus-costs/6390-catch-binding-predicate.ts.json b/tests/harness/corpus-costs/6390-catch-binding-predicate.ts.json new file mode 100644 index 000000000..5b661172c --- /dev/null +++ b/tests/harness/corpus-costs/6390-catch-binding-predicate.ts.json @@ -0,0 +1 @@ +{ "plain": 3020, "san": 3560 } diff --git a/tests/harness/corpus-costs/6391-nullish-function-default.ts.json b/tests/harness/corpus-costs/6391-nullish-function-default.ts.json new file mode 100644 index 000000000..a3ae15ea4 --- /dev/null +++ b/tests/harness/corpus-costs/6391-nullish-function-default.ts.json @@ -0,0 +1 @@ +{ "plain": 4100, "san": 3150 } diff --git a/tests/harness/corpus-costs/6392-optional-chain-protocol-calls.ts.json b/tests/harness/corpus-costs/6392-optional-chain-protocol-calls.ts.json new file mode 100644 index 000000000..3fa72b729 --- /dev/null +++ b/tests/harness/corpus-costs/6392-optional-chain-protocol-calls.ts.json @@ -0,0 +1 @@ +{ "plain": 4400, "san": 4200 } diff --git a/tests/harness/corpus-costs/6393-function-types-any-params.ts.json b/tests/harness/corpus-costs/6393-function-types-any-params.ts.json new file mode 100644 index 000000000..73f790694 --- /dev/null +++ b/tests/harness/corpus-costs/6393-function-types-any-params.ts.json @@ -0,0 +1 @@ +{ "plain": 2790, "san": 3150 } diff --git a/tests/harness/corpus-costs/6394-in-key-error-subclasses.ts.json b/tests/harness/corpus-costs/6394-in-key-error-subclasses.ts.json new file mode 100644 index 000000000..6fe8b6765 --- /dev/null +++ b/tests/harness/corpus-costs/6394-in-key-error-subclasses.ts.json @@ -0,0 +1 @@ +{ "plain": 2900, "san": 3120 } diff --git a/tests/harness/corpus-costs/6395-generic-object-entries.ts.json b/tests/harness/corpus-costs/6395-generic-object-entries.ts.json new file mode 100644 index 000000000..d38b11826 --- /dev/null +++ b/tests/harness/corpus-costs/6395-generic-object-entries.ts.json @@ -0,0 +1 @@ +{ "plain": 2990, "san": 3030 } diff --git a/tests/harness/corpus-costs/6396-generic-any-instantiation.ts.json b/tests/harness/corpus-costs/6396-generic-any-instantiation.ts.json new file mode 100644 index 000000000..d38f92d43 --- /dev/null +++ b/tests/harness/corpus-costs/6396-generic-any-instantiation.ts.json @@ -0,0 +1 @@ +{ "plain": 2590, "san": 2850 } diff --git a/tests/harness/corpus-costs/6397-event-loop-metrics.ts.json b/tests/harness/corpus-costs/6397-event-loop-metrics.ts.json new file mode 100644 index 000000000..26c5a51a4 --- /dev/null +++ b/tests/harness/corpus-costs/6397-event-loop-metrics.ts.json @@ -0,0 +1 @@ +{ "plain": 3300, "san": 4140 } diff --git a/tests/harness/corpus-costs/6398-conditional-fixed-arity.ts.json b/tests/harness/corpus-costs/6398-conditional-fixed-arity.ts.json new file mode 100644 index 000000000..c8912b309 --- /dev/null +++ b/tests/harness/corpus-costs/6398-conditional-fixed-arity.ts.json @@ -0,0 +1 @@ +{ "plain": 3630, "san": 3870 } diff --git a/tests/harness/corpus-costs/6400-dyn-error-stack-property-js.js.json b/tests/harness/corpus-costs/6400-dyn-error-stack-property-js.js.json new file mode 100644 index 000000000..c51a3475f --- /dev/null +++ b/tests/harness/corpus-costs/6400-dyn-error-stack-property-js.js.json @@ -0,0 +1 @@ +{ "plain": 3172, "san": 4044 } diff --git a/tests/harness/corpus-costs/6400-override-any-base.ts.json b/tests/harness/corpus-costs/6400-override-any-base.ts.json new file mode 100644 index 000000000..5ff4195cc --- /dev/null +++ b/tests/harness/corpus-costs/6400-override-any-base.ts.json @@ -0,0 +1 @@ +{"plain":4713} diff --git a/tests/harness/corpus-costs/6401-dyn-error-own-property-names.ts.json b/tests/harness/corpus-costs/6401-dyn-error-own-property-names.ts.json new file mode 100644 index 000000000..f85c4901a --- /dev/null +++ b/tests/harness/corpus-costs/6401-dyn-error-own-property-names.ts.json @@ -0,0 +1 @@ +{ "plain": 3175, "san": 4362 } diff --git a/tests/harness/corpus-costs/6401-override-param-variance.ts.json b/tests/harness/corpus-costs/6401-override-param-variance.ts.json new file mode 100644 index 000000000..0f324b571 --- /dev/null +++ b/tests/harness/corpus-costs/6401-override-param-variance.ts.json @@ -0,0 +1 @@ +{"plain":3445} diff --git a/tests/harness/corpus-costs/6402-dyn-error-own-property-names-js.js.json b/tests/harness/corpus-costs/6402-dyn-error-own-property-names-js.js.json new file mode 100644 index 000000000..55012b442 --- /dev/null +++ b/tests/harness/corpus-costs/6402-dyn-error-own-property-names-js.js.json @@ -0,0 +1 @@ +{ "plain": 3056, "san": 4620 } diff --git a/tests/harness/corpus-costs/6402-override-js-base/main.ts.json b/tests/harness/corpus-costs/6402-override-js-base/main.ts.json new file mode 100644 index 000000000..dafa7b34e --- /dev/null +++ b/tests/harness/corpus-costs/6402-override-js-base/main.ts.json @@ -0,0 +1 @@ +{"plain":5309} diff --git a/tests/harness/corpus-costs/6403-error-code-write.ts.json b/tests/harness/corpus-costs/6403-error-code-write.ts.json new file mode 100644 index 000000000..e4d08c9bb --- /dev/null +++ b/tests/harness/corpus-costs/6403-error-code-write.ts.json @@ -0,0 +1 @@ +{"plain":3632} diff --git a/tests/harness/corpus-costs/6403-error-own-name-order.ts.json b/tests/harness/corpus-costs/6403-error-own-name-order.ts.json new file mode 100644 index 000000000..882c13186 --- /dev/null +++ b/tests/harness/corpus-costs/6403-error-own-name-order.ts.json @@ -0,0 +1 @@ +{ "plain": 3289, "san": 4302 } diff --git a/tests/harness/corpus-costs/6404-error-in-and-assign.ts.json b/tests/harness/corpus-costs/6404-error-in-and-assign.ts.json new file mode 100644 index 000000000..12a27db7d --- /dev/null +++ b/tests/harness/corpus-costs/6404-error-in-and-assign.ts.json @@ -0,0 +1 @@ +{"plain":4787} diff --git a/tests/harness/corpus-costs/6404-error-own-name-order-js.js.json b/tests/harness/corpus-costs/6404-error-own-name-order-js.js.json new file mode 100644 index 000000000..05ac342cb --- /dev/null +++ b/tests/harness/corpus-costs/6404-error-own-name-order-js.js.json @@ -0,0 +1 @@ +{ "plain": 3030, "san": 3963 } diff --git a/tests/harness/corpus-costs/6405-wrapped-default-parameters.ts.json b/tests/harness/corpus-costs/6405-wrapped-default-parameters.ts.json new file mode 100644 index 000000000..2fe355bec --- /dev/null +++ b/tests/harness/corpus-costs/6405-wrapped-default-parameters.ts.json @@ -0,0 +1 @@ +{"plain":4127} diff --git a/tests/harness/corpus-costs/6406-npm-instance-receiver/main.ts.json b/tests/harness/corpus-costs/6406-npm-instance-receiver/main.ts.json new file mode 100644 index 000000000..0e304e2ed --- /dev/null +++ b/tests/harness/corpus-costs/6406-npm-instance-receiver/main.ts.json @@ -0,0 +1 @@ +{"plain":4693} diff --git a/tests/harness/corpus-costs/6407-dyn-bytes-in.ts.json b/tests/harness/corpus-costs/6407-dyn-bytes-in.ts.json new file mode 100644 index 000000000..4b769b3a1 --- /dev/null +++ b/tests/harness/corpus-costs/6407-dyn-bytes-in.ts.json @@ -0,0 +1 @@ +{ "plain": 3590, "san": 4401 } diff --git a/tests/harness/corpus-costs/6408-dyn-bytes-in-js.js.json b/tests/harness/corpus-costs/6408-dyn-bytes-in-js.js.json new file mode 100644 index 000000000..12ffbeef1 --- /dev/null +++ b/tests/harness/corpus-costs/6408-dyn-bytes-in-js.js.json @@ -0,0 +1 @@ +{ "plain": 3076, "san": 4009 } diff --git a/tests/harness/corpus-costs/6409-catch-binding-shapes.ts.json b/tests/harness/corpus-costs/6409-catch-binding-shapes.ts.json new file mode 100644 index 000000000..072cdc179 --- /dev/null +++ b/tests/harness/corpus-costs/6409-catch-binding-shapes.ts.json @@ -0,0 +1 @@ +{"plain":3726} diff --git a/tests/harness/corpus-costs/6410-wrapper-untyped-results.ts.json b/tests/harness/corpus-costs/6410-wrapper-untyped-results.ts.json new file mode 100644 index 000000000..970974165 --- /dev/null +++ b/tests/harness/corpus-costs/6410-wrapper-untyped-results.ts.json @@ -0,0 +1 @@ +{"plain":3837} diff --git a/tests/harness/corpus-costs/6411-prototype-reset-constructors/main.ts.json b/tests/harness/corpus-costs/6411-prototype-reset-constructors/main.ts.json new file mode 100644 index 000000000..b2731b1c1 --- /dev/null +++ b/tests/harness/corpus-costs/6411-prototype-reset-constructors/main.ts.json @@ -0,0 +1 @@ +{"plain":4711} diff --git a/tests/harness/corpus-costs/6412-js-error-tostring-override/main.ts.json b/tests/harness/corpus-costs/6412-js-error-tostring-override/main.ts.json new file mode 100644 index 000000000..64c9f2e6d --- /dev/null +++ b/tests/harness/corpus-costs/6412-js-error-tostring-override/main.ts.json @@ -0,0 +1 @@ +{"plain":3803} diff --git a/tests/harness/corpus-costs/6413-override-js-async-base/main.ts.json b/tests/harness/corpus-costs/6413-override-js-async-base/main.ts.json new file mode 100644 index 000000000..eeb8f92e9 --- /dev/null +++ b/tests/harness/corpus-costs/6413-override-js-async-base/main.ts.json @@ -0,0 +1 @@ +{"plain":3820} diff --git a/tests/harness/corpus-costs/6414-catch-values-roundtrip.ts.json b/tests/harness/corpus-costs/6414-catch-values-roundtrip.ts.json new file mode 100644 index 000000000..502f0220d --- /dev/null +++ b/tests/harness/corpus-costs/6414-catch-values-roundtrip.ts.json @@ -0,0 +1 @@ +{"plain":4942} diff --git a/tests/harness/corpus-costs/6415-error-code-dynamic-writes.ts.json b/tests/harness/corpus-costs/6415-error-code-dynamic-writes.ts.json new file mode 100644 index 000000000..fb3867563 --- /dev/null +++ b/tests/harness/corpus-costs/6415-error-code-dynamic-writes.ts.json @@ -0,0 +1 @@ +{"plain":3425} diff --git a/tests/harness/corpus-costs/6440-http-socket-path.ts.json b/tests/harness/corpus-costs/6440-http-socket-path.ts.json new file mode 100644 index 000000000..7096393ca --- /dev/null +++ b/tests/harness/corpus-costs/6440-http-socket-path.ts.json @@ -0,0 +1 @@ +{ "plain": 3157, "san": 11133 } diff --git a/tests/harness/corpus-costs/6441-http-request-options-record.ts.json b/tests/harness/corpus-costs/6441-http-request-options-record.ts.json new file mode 100644 index 000000000..004d0559a --- /dev/null +++ b/tests/harness/corpus-costs/6441-http-request-options-record.ts.json @@ -0,0 +1 @@ +{ "plain": 3186, "san": 59437 } diff --git a/tests/harness/corpus-costs/6442-https-request-options-record.ts.json b/tests/harness/corpus-costs/6442-https-request-options-record.ts.json new file mode 100644 index 000000000..eaa5f8364 --- /dev/null +++ b/tests/harness/corpus-costs/6442-https-request-options-record.ts.json @@ -0,0 +1 @@ +{ "plain": 3069, "san": 6484 } diff --git a/tests/harness/corpus-costs/6443-net-lookup-http-close-text.ts.json b/tests/harness/corpus-costs/6443-net-lookup-http-close-text.ts.json new file mode 100644 index 000000000..0fafd0f0b --- /dev/null +++ b/tests/harness/corpus-costs/6443-net-lookup-http-close-text.ts.json @@ -0,0 +1 @@ +{ "plain": 2847, "san": 10348 } diff --git a/tests/harness/corpus-costs/6445-rest-function-fixed-slot.ts.json b/tests/harness/corpus-costs/6445-rest-function-fixed-slot.ts.json new file mode 100644 index 000000000..858c662a8 --- /dev/null +++ b/tests/harness/corpus-costs/6445-rest-function-fixed-slot.ts.json @@ -0,0 +1 @@ +{ "plain": 2526, "san": 3450 } diff --git a/tests/harness/corpus-costs/6446-rest-function-nested-declaration.ts.json b/tests/harness/corpus-costs/6446-rest-function-nested-declaration.ts.json new file mode 100644 index 000000000..39f698b22 --- /dev/null +++ b/tests/harness/corpus-costs/6446-rest-function-nested-declaration.ts.json @@ -0,0 +1 @@ +{ "plain": 2222, "san": 3035 } diff --git a/tests/harness/corpus-costs/6453-constructor-function-emitter.cjs.json b/tests/harness/corpus-costs/6453-constructor-function-emitter.cjs.json new file mode 100644 index 000000000..a09a333d5 --- /dev/null +++ b/tests/harness/corpus-costs/6453-constructor-function-emitter.cjs.json @@ -0,0 +1 @@ +{ "plain": 4300, "san": 51474 } diff --git a/tests/harness/corpus-costs/6454-constructor-function-stream-bases.cjs.json b/tests/harness/corpus-costs/6454-constructor-function-stream-bases.cjs.json new file mode 100644 index 000000000..25987b67f --- /dev/null +++ b/tests/harness/corpus-costs/6454-constructor-function-stream-bases.cjs.json @@ -0,0 +1 @@ +{ "plain": 3371, "san": 3844 } diff --git a/tests/harness/corpus-costs/6465-optional-members-over-index-signature.ts.json b/tests/harness/corpus-costs/6465-optional-members-over-index-signature.ts.json new file mode 100644 index 000000000..93c788dd2 --- /dev/null +++ b/tests/harness/corpus-costs/6465-optional-members-over-index-signature.ts.json @@ -0,0 +1 @@ +{ "plain": 3225, "san": 26787 } diff --git a/tests/harness/corpus-costs/6466-never-assertion-argument.ts.json b/tests/harness/corpus-costs/6466-never-assertion-argument.ts.json new file mode 100644 index 000000000..4b7e88af5 --- /dev/null +++ b/tests/harness/corpus-costs/6466-never-assertion-argument.ts.json @@ -0,0 +1 @@ +{ "plain": 2895, "san": 23565 } diff --git a/tests/harness/corpus-costs/6467-fromentries-record-assertion.ts.json b/tests/harness/corpus-costs/6467-fromentries-record-assertion.ts.json new file mode 100644 index 000000000..390533b9e --- /dev/null +++ b/tests/harness/corpus-costs/6467-fromentries-record-assertion.ts.json @@ -0,0 +1 @@ +{ "plain": 3093, "san": 22400 } diff --git a/tests/harness/corpus-costs/6468-generic-unknown-record-entries.ts.json b/tests/harness/corpus-costs/6468-generic-unknown-record-entries.ts.json new file mode 100644 index 000000000..8489daced --- /dev/null +++ b/tests/harness/corpus-costs/6468-generic-unknown-record-entries.ts.json @@ -0,0 +1 @@ +{ "plain": 4560, "san": 4770 } diff --git a/tests/harness/corpus-costs/6468-node-stdio-writable-slot.ts.json b/tests/harness/corpus-costs/6468-node-stdio-writable-slot.ts.json new file mode 100644 index 000000000..3fafdfc4b --- /dev/null +++ b/tests/harness/corpus-costs/6468-node-stdio-writable-slot.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3500 } diff --git a/tests/harness/corpus-costs/6469-node-stdio-writable-js-pipe/main.ts.json b/tests/harness/corpus-costs/6469-node-stdio-writable-js-pipe/main.ts.json new file mode 100644 index 000000000..3fafdfc4b --- /dev/null +++ b/tests/harness/corpus-costs/6469-node-stdio-writable-js-pipe/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3000, "san": 3500 } diff --git a/tests/harness/corpus-costs/6470-any-destructuring.ts.json b/tests/harness/corpus-costs/6470-any-destructuring.ts.json new file mode 100644 index 000000000..101a112d4 --- /dev/null +++ b/tests/harness/corpus-costs/6470-any-destructuring.ts.json @@ -0,0 +1 @@ +{ "plain": 2678, "san": 3175 } diff --git a/tests/harness/corpus-costs/6471-any-method-chains.ts.json b/tests/harness/corpus-costs/6471-any-method-chains.ts.json new file mode 100644 index 000000000..13db76f84 --- /dev/null +++ b/tests/harness/corpus-costs/6471-any-method-chains.ts.json @@ -0,0 +1 @@ +{ "plain": 1526, "san": 1821 } diff --git a/tests/harness/corpus-costs/6472-namespace-destructuring/main.ts.json b/tests/harness/corpus-costs/6472-namespace-destructuring/main.ts.json new file mode 100644 index 000000000..a296c0da0 --- /dev/null +++ b/tests/harness/corpus-costs/6472-namespace-destructuring/main.ts.json @@ -0,0 +1 @@ +{ "plain": 1728, "san": 1731 } diff --git a/tests/harness/corpus-costs/6473-any-parameter-values.ts.json b/tests/harness/corpus-costs/6473-any-parameter-values.ts.json new file mode 100644 index 000000000..cb6a6fb42 --- /dev/null +++ b/tests/harness/corpus-costs/6473-any-parameter-values.ts.json @@ -0,0 +1 @@ +{ "plain": 1588, "san": 1779 } diff --git a/tests/harness/corpus-costs/6474-async-any-callbacks/main.ts.json b/tests/harness/corpus-costs/6474-async-any-callbacks/main.ts.json new file mode 100644 index 000000000..7c0aa7365 --- /dev/null +++ b/tests/harness/corpus-costs/6474-async-any-callbacks/main.ts.json @@ -0,0 +1 @@ +{ "plain": 1691, "san": 1787 } diff --git a/tests/harness/corpus-costs/6475-js-promise-resolve-adoption/main.ts.json b/tests/harness/corpus-costs/6475-js-promise-resolve-adoption/main.ts.json new file mode 100644 index 000000000..e1cfd6357 --- /dev/null +++ b/tests/harness/corpus-costs/6475-js-promise-resolve-adoption/main.ts.json @@ -0,0 +1 @@ +{ "plain": 1592, "san": 1653 } diff --git a/tests/harness/corpus-costs/6490-js-named-promise-executor/main.ts.json b/tests/harness/corpus-costs/6490-js-named-promise-executor/main.ts.json new file mode 100644 index 000000000..f5d08389a --- /dev/null +++ b/tests/harness/corpus-costs/6490-js-named-promise-executor/main.ts.json @@ -0,0 +1 @@ +{ "plain": 1658, "san": 1832 } diff --git a/tests/harness/corpus-costs/6491-catch-any-bindings.ts.json b/tests/harness/corpus-costs/6491-catch-any-bindings.ts.json new file mode 100644 index 000000000..644275755 --- /dev/null +++ b/tests/harness/corpus-costs/6491-catch-any-bindings.ts.json @@ -0,0 +1 @@ +{ "plain": 1509, "san": 1704 } diff --git a/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json b/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json new file mode 100644 index 000000000..e873baf78 --- /dev/null +++ b/tests/harness/corpus-costs/6564-constructor-function-stream-late-hooks.cjs.json @@ -0,0 +1 @@ +{ "plain": 2750, "san": 3200 } diff --git a/tests/harness/corpus-costs/761-any-objects.ts.json b/tests/harness/corpus-costs/761-any-objects.ts.json new file mode 100644 index 000000000..50113af31 --- /dev/null +++ b/tests/harness/corpus-costs/761-any-objects.ts.json @@ -0,0 +1 @@ +{ "plain": 1524, "san": 1822 } diff --git a/tests/harness/corpus-costs/cjs-class-hierarchy-npm/main.ts.json b/tests/harness/corpus-costs/cjs-class-hierarchy-npm/main.ts.json new file mode 100644 index 000000000..6f2c07c70 --- /dev/null +++ b/tests/harness/corpus-costs/cjs-class-hierarchy-npm/main.ts.json @@ -0,0 +1 @@ +{ "plain": 5788, "san": 8587 } diff --git a/tests/harness/corpus-costs/cjs-open-options-npm/main.ts.json b/tests/harness/corpus-costs/cjs-open-options-npm/main.ts.json new file mode 100644 index 000000000..bc8f1df37 --- /dev/null +++ b/tests/harness/corpus-costs/cjs-open-options-npm/main.ts.json @@ -0,0 +1 @@ +{ "plain": 3636, "san": 4211 } diff --git a/tests/harness/corpus-costs/cjs-strict-arguments.cjs.json b/tests/harness/corpus-costs/cjs-strict-arguments.cjs.json new file mode 100644 index 000000000..82b947fc7 --- /dev/null +++ b/tests/harness/corpus-costs/cjs-strict-arguments.cjs.json @@ -0,0 +1 @@ +{ "plain": 2381, "san": 3148 } diff --git a/tests/harness/corpus-costs/cjs-symbol-key-collisions/main.js.json b/tests/harness/corpus-costs/cjs-symbol-key-collisions/main.js.json new file mode 100644 index 000000000..682d630d5 --- /dev/null +++ b/tests/harness/corpus-costs/cjs-symbol-key-collisions/main.js.json @@ -0,0 +1 @@ +{ "plain": 2258, "san": 3272 } diff --git a/tests/harness/corpus-costs/cjs-symbol-key-table/main.js.json b/tests/harness/corpus-costs/cjs-symbol-key-table/main.js.json new file mode 100644 index 000000000..5bdd1fc52 --- /dev/null +++ b/tests/harness/corpus-costs/cjs-symbol-key-table/main.js.json @@ -0,0 +1 @@ +{ "plain": 3327, "san": 4728 } diff --git a/tests/harness/corpus-costs/cjs-unmapped-arguments.cjs.json b/tests/harness/corpus-costs/cjs-unmapped-arguments.cjs.json new file mode 100644 index 000000000..7a5c1ebd8 --- /dev/null +++ b/tests/harness/corpus-costs/cjs-unmapped-arguments.cjs.json @@ -0,0 +1 @@ +{ "plain": 2377, "san": 3264 } diff --git a/tests/harness/corpus-costs/code-generation-disallowed.cjs.json b/tests/harness/corpus-costs/code-generation-disallowed.cjs.json new file mode 100644 index 000000000..25789b757 --- /dev/null +++ b/tests/harness/corpus-costs/code-generation-disallowed.cjs.json @@ -0,0 +1 @@ +{ "plain": 1728, "san": 3278 } diff --git a/tests/harness/corpus-costs/code-generation-disallowed.ts.json b/tests/harness/corpus-costs/code-generation-disallowed.ts.json new file mode 100644 index 000000000..9736f10c7 --- /dev/null +++ b/tests/harness/corpus-costs/code-generation-disallowed.ts.json @@ -0,0 +1 @@ +{ "plain": 1842, "san": 3388 } diff --git a/tests/harness/corpus-costs/comma-null-union-return.cjs.json b/tests/harness/corpus-costs/comma-null-union-return.cjs.json new file mode 100644 index 000000000..843c2ab0b --- /dev/null +++ b/tests/harness/corpus-costs/comma-null-union-return.cjs.json @@ -0,0 +1 @@ +{ "plain": 2400, "san": 3000 } diff --git a/tests/harness/corpus-costs/dns-promises-lookup.ts.json b/tests/harness/corpus-costs/dns-promises-lookup.ts.json new file mode 100644 index 000000000..65afea6db --- /dev/null +++ b/tests/harness/corpus-costs/dns-promises-lookup.ts.json @@ -0,0 +1 @@ +{ "plain": 2143, "san": 8272 } diff --git a/tests/harness/corpus-costs/dns-promises-untyped.cjs.json b/tests/harness/corpus-costs/dns-promises-untyped.cjs.json new file mode 100644 index 000000000..b8cfb5ac8 --- /dev/null +++ b/tests/harness/corpus-costs/dns-promises-untyped.cjs.json @@ -0,0 +1 @@ +{ "plain": 2052, "san": 6436 } diff --git a/tests/harness/corpus-costs/function-local-detached-methods.js.json b/tests/harness/corpus-costs/function-local-detached-methods.js.json new file mode 100644 index 000000000..f839807c5 --- /dev/null +++ b/tests/harness/corpus-costs/function-local-detached-methods.js.json @@ -0,0 +1 @@ +{ "plain": 3316, "san": 4688 } diff --git a/tests/harness/corpus-costs/function-local-detached-methods.ts.json b/tests/harness/corpus-costs/function-local-detached-methods.ts.json new file mode 100644 index 000000000..19907d4e4 --- /dev/null +++ b/tests/harness/corpus-costs/function-local-detached-methods.ts.json @@ -0,0 +1 @@ +{ "plain": 2314, "san": 3117 } diff --git a/tests/harness/corpus-costs/function-local-private-evaluations.js.json b/tests/harness/corpus-costs/function-local-private-evaluations.js.json new file mode 100644 index 000000000..c75974ffd --- /dev/null +++ b/tests/harness/corpus-costs/function-local-private-evaluations.js.json @@ -0,0 +1 @@ +{ "plain": 6264, "san": 10866 } diff --git a/tests/harness/corpus-costs/function-local-private-fields.js.json b/tests/harness/corpus-costs/function-local-private-fields.js.json new file mode 100644 index 000000000..e01fe4c5f --- /dev/null +++ b/tests/harness/corpus-costs/function-local-private-fields.js.json @@ -0,0 +1 @@ +{ "plain": 2964, "san": 5827 } diff --git a/tests/harness/corpus-costs/graceful-fs-alias/main.cjs.json b/tests/harness/corpus-costs/graceful-fs-alias/main.cjs.json new file mode 100644 index 000000000..d3f53272b --- /dev/null +++ b/tests/harness/corpus-costs/graceful-fs-alias/main.cjs.json @@ -0,0 +1 @@ +{ "plain": 2539, "san": 4532 } diff --git a/tests/harness/corpus-costs/local-class-detached-method.ts.json b/tests/harness/corpus-costs/local-class-detached-method.ts.json new file mode 100644 index 000000000..0de92c93b --- /dev/null +++ b/tests/harness/corpus-costs/local-class-detached-method.ts.json @@ -0,0 +1 @@ +{ "plain": 2500, "san": 3900 } diff --git a/tests/harness/corpus-costs/object-literal-accessors-dyn.ts.json b/tests/harness/corpus-costs/object-literal-accessors-dyn.ts.json new file mode 100644 index 000000000..9643a761b --- /dev/null +++ b/tests/harness/corpus-costs/object-literal-accessors-dyn.ts.json @@ -0,0 +1 @@ +{ "plain": 2903, "san": 3777 } diff --git a/tests/harness/corpus-costs/private-in-checked-primitives.js.json b/tests/harness/corpus-costs/private-in-checked-primitives.js.json new file mode 100644 index 000000000..ce2fa848e --- /dev/null +++ b/tests/harness/corpus-costs/private-in-checked-primitives.js.json @@ -0,0 +1 @@ +{ "plain": 2316, "san": 2225 } diff --git a/tests/harness/corpus-costs/required-module-truthiness.cjs.json b/tests/harness/corpus-costs/required-module-truthiness.cjs.json new file mode 100644 index 000000000..efd5011d1 --- /dev/null +++ b/tests/harness/corpus-costs/required-module-truthiness.cjs.json @@ -0,0 +1 @@ +{ "plain": 2400, "san": 3700 } diff --git a/tests/harness/corpus-costs/stream-web-readable.ts.json b/tests/harness/corpus-costs/stream-web-readable.ts.json new file mode 100644 index 000000000..575244767 --- /dev/null +++ b/tests/harness/corpus-costs/stream-web-readable.ts.json @@ -0,0 +1 @@ +{ "plain": 2572, "san": 34567 } diff --git a/tests/harness/corpus-costs/stream-web-require.cjs.json b/tests/harness/corpus-costs/stream-web-require.cjs.json new file mode 100644 index 000000000..f746fcc69 --- /dev/null +++ b/tests/harness/corpus-costs/stream-web-require.cjs.json @@ -0,0 +1 @@ +{ "plain": 2290, "san": 3799 } diff --git a/tests/harness/corpus-costs/strict-arguments-parameters.cjs.json b/tests/harness/corpus-costs/strict-arguments-parameters.cjs.json new file mode 100644 index 000000000..adc7f5470 --- /dev/null +++ b/tests/harness/corpus-costs/strict-arguments-parameters.cjs.json @@ -0,0 +1 @@ +{ "plain": 2600, "san": 3300 } diff --git a/tests/harness/corpus-costs/temporal-absent-probe.js.json b/tests/harness/corpus-costs/temporal-absent-probe.js.json new file mode 100644 index 000000000..82c169a3b --- /dev/null +++ b/tests/harness/corpus-costs/temporal-absent-probe.js.json @@ -0,0 +1 @@ +{ "plain": 2167, "san": 4537 } diff --git a/tests/harness/corpus-costs/temporal-absent.ts.json b/tests/harness/corpus-costs/temporal-absent.ts.json new file mode 100644 index 000000000..dd6d3e202 --- /dev/null +++ b/tests/harness/corpus-costs/temporal-absent.ts.json @@ -0,0 +1 @@ +{ "plain": 4023, "san": 4152 } diff --git a/tests/harness/corpus-costs/v8-heap-statistics.ts.json b/tests/harness/corpus-costs/v8-heap-statistics.ts.json new file mode 100644 index 000000000..7c4b1a685 --- /dev/null +++ b/tests/harness/corpus-costs/v8-heap-statistics.ts.json @@ -0,0 +1 @@ +{ "plain": 4416, "san": 4204 } diff --git a/tests/harness/corpus-costs/v8-require-optional.cjs.json b/tests/harness/corpus-costs/v8-require-optional.cjs.json new file mode 100644 index 000000000..0c14a711e --- /dev/null +++ b/tests/harness/corpus-costs/v8-require-optional.cjs.json @@ -0,0 +1 @@ +{ "plain": 3623, "san": 3831 } diff --git a/tests/harness/corpus-costs/webassembly-api.mjs.json b/tests/harness/corpus-costs/webassembly-api.mjs.json new file mode 100644 index 000000000..dd2a34243 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-api.mjs.json @@ -0,0 +1 @@ +{ "plain": 6776, "san": 5737 } diff --git a/tests/harness/corpus-costs/webassembly-dylink/main.mjs.json b/tests/harness/corpus-costs/webassembly-dylink/main.mjs.json new file mode 100644 index 000000000..0d6b0acf9 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-dylink/main.mjs.json @@ -0,0 +1 @@ +{ "plain": 6843, "san": 50107 } diff --git a/tests/harness/corpus-costs/webassembly-externref-lifetimes.mjs.json b/tests/harness/corpus-costs/webassembly-externref-lifetimes.mjs.json new file mode 100644 index 000000000..d88c1b461 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-externref-lifetimes.mjs.json @@ -0,0 +1 @@ +{ "plain": 3200, "san": 5700 } diff --git a/tests/harness/corpus-costs/webassembly-jsapi.mjs.json b/tests/harness/corpus-costs/webassembly-jsapi.mjs.json new file mode 100644 index 000000000..0cf9acaeb --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-jsapi.mjs.json @@ -0,0 +1 @@ +{ "plain": 7155, "san": 9278 } diff --git a/tests/harness/corpus-costs/webassembly-lifetimes.mjs.json b/tests/harness/corpus-costs/webassembly-lifetimes.mjs.json new file mode 100644 index 000000000..0c9bada83 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-lifetimes.mjs.json @@ -0,0 +1 @@ +{ "plain": 2996, "san": 7565 } diff --git a/tests/harness/corpus-costs/webassembly-spec-control/main.mjs.json b/tests/harness/corpus-costs/webassembly-spec-control/main.mjs.json new file mode 100644 index 000000000..223969e02 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-spec-control/main.mjs.json @@ -0,0 +1 @@ +{ "plain": 6916, "san": 8022 } diff --git a/tests/harness/corpus-costs/webassembly-spec-float/main.mjs.json b/tests/harness/corpus-costs/webassembly-spec-float/main.mjs.json new file mode 100644 index 000000000..3a4a79b75 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-spec-float/main.mjs.json @@ -0,0 +1 @@ +{ "plain": 5393, "san": 6409 } diff --git a/tests/harness/corpus-costs/webassembly-spec-imports/main.mjs.json b/tests/harness/corpus-costs/webassembly-spec-imports/main.mjs.json new file mode 100644 index 000000000..ab57737bf --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-spec-imports/main.mjs.json @@ -0,0 +1 @@ +{ "plain": 4431, "san": 4894 } diff --git a/tests/harness/corpus-costs/webassembly-spec-int/main.mjs.json b/tests/harness/corpus-costs/webassembly-spec-int/main.mjs.json new file mode 100644 index 000000000..c3541f5ca --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-spec-int/main.mjs.json @@ -0,0 +1 @@ +{ "plain": 4576, "san": 5275 } diff --git a/tests/harness/corpus-costs/webassembly-spec-memory/main.mjs.json b/tests/harness/corpus-costs/webassembly-spec-memory/main.mjs.json new file mode 100644 index 000000000..074a854fe --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-spec-memory/main.mjs.json @@ -0,0 +1 @@ +{ "plain": 6100, "san": 7066 } diff --git a/tests/harness/corpus-costs/webassembly-spec-table/main.mjs.json b/tests/harness/corpus-costs/webassembly-spec-table/main.mjs.json new file mode 100644 index 000000000..da0d7c4e2 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-spec-table/main.mjs.json @@ -0,0 +1 @@ +{ "plain": 6712, "san": 7095 } diff --git a/tests/harness/corpus-costs/webassembly-typed.ts.json b/tests/harness/corpus-costs/webassembly-typed.ts.json new file mode 100644 index 000000000..e698fa802 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-typed.ts.json @@ -0,0 +1 @@ +{ "plain": 3602, "san": 3848 } diff --git a/tests/harness/corpus-costs/webassembly-worker.ts.json b/tests/harness/corpus-costs/webassembly-worker.ts.json new file mode 100644 index 000000000..b91ffeb21 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-worker.ts.json @@ -0,0 +1 @@ +{ "plain": 4051, "san": 4892 } diff --git a/tests/harness/corpus-costs/webassembly-wpt-jsapi.mjs.json b/tests/harness/corpus-costs/webassembly-wpt-jsapi.mjs.json new file mode 100644 index 000000000..dcd63ffe0 --- /dev/null +++ b/tests/harness/corpus-costs/webassembly-wpt-jsapi.mjs.json @@ -0,0 +1 @@ +{ "plain": 14016, "san": 15948 } diff --git a/tests/harness/coverage.test.ts b/tests/harness/coverage.test.ts index b1e25dead..cc91c0922 100644 --- a/tests/harness/coverage.test.ts +++ b/tests/harness/coverage.test.ts @@ -44,8 +44,9 @@ test("unreached blockers report in their own group", async () => { }); test("WebAssembly modules the build can see report with their sources", async () => { - // A resolved path and an inline literal (glue.js), a packaged .wasm - // (wasm-fixture-pkg), and package.json's scriptc.wasm list. + // A resolved path, a resolved .so shared library and an inline literal + // (glue.js), a packaged .wasm (wasm-fixture-pkg), and package.json's + // scriptc.wasm list. A resolved .so that is not a module is not reported. await expect(report(fixture("wasm/main.ts"))).toMatchFileSnapshot( "__snapshots__/coverage-wasm.txt", ); @@ -61,6 +62,7 @@ test("WebAssembly modules the build can see report with their sources", async () ).toEqual([ ["inline", "valid", [], ["inline"]], ["tests/coverage-fixtures/wasm/add.wasm", "valid", [], ["path", "config"]], + ["tests/coverage-fixtures/wasm/ext.so", "valid", [], ["path"]], [ "tests/coverage-fixtures/wasm/node_modules/wasm-fixture-pkg/lib/core.wasm", "invalid", @@ -70,7 +72,7 @@ test("WebAssembly modules the build can see report with their sources", async () ["tests/coverage-fixtures/wasm/build/generated.wasm", "unsupported", ["simd"], ["config"]], ]); const envelope = coverageEnvelope(coverage, { compilerVersion: "test" }); - expect(envelope.wasmModules?.map((m) => m.sha256.length)).toEqual([64, 64, 64, 64]); + expect(envelope.wasmModules?.map((m) => m.sha256.length)).toEqual([64, 64, 64, 64, 64]); }); test("divergences report as warnings, never as blockers", async () => { @@ -82,9 +84,6 @@ test("divergences report as warnings, never as blockers", async () => { "SC6002", "SC6003", "SC6004", - "SC6005", - "SC6005", - "SC6005", ]); expect(coveragePasses(coverage, "blockers")).toBe(true); expect(coveragePasses(coverage, "divergences")).toBe(false); @@ -256,11 +255,11 @@ test("CLI: build --print=diagnostics reports failures and divergence warnings as }; expect(envelope.success).toBe(true); expect(envelope.artifact).toBe(join(outDir, "out.ir.json")); - expect(envelope.diagnostics.map((d) => d.severity)).toEqual(Array(8).fill("warning")); + expect(envelope.diagnostics.map((d) => d.severity)).toEqual(Array(5).fill("warning")); const human = build(fixture("divergences.ts")); expect(human.status).toBe(0); expect(human.stderr).toContain("warning SC6001"); - expect(human.stderr).toContain("8 warnings: the program compiled"); + expect(human.stderr).toContain("5 warnings: the program compiled"); } finally { rmSync(outDir, { recursive: true, force: true }); } diff --git a/tests/harness/diagnostics.test.ts b/tests/harness/diagnostics.test.ts index 57ac8c6c0..77fd5937d 100644 --- a/tests/harness/diagnostics.test.ts +++ b/tests/harness/diagnostics.test.ts @@ -33,7 +33,17 @@ describe(`diagnostics corpus (${files.length} programs)`, () => { // any backend runs (that is the suite's contract), so the default // lane is irrelevant by construction — and riding it proves the // fallback machinery never turns a diagnostic into a build. - const result = await compile(file, { outPath: join(outDir, "never"), outDir }); + // The checker-panic programs exercise tsgo's own failures (a panic, and a + // +Infinity it cannot send), so they compile with tsgo. + const checker = process.env["SCRIPTC_CHECKER"]; + if (name.startsWith("checker-panic-")) process.env["SCRIPTC_CHECKER"] = "tsgo"; + let result: Awaited>; + try { + result = await compile(file, { outPath: join(outDir, "never"), outDir }); + } finally { + if (checker === undefined) delete process.env["SCRIPTC_CHECKER"]; + else process.env["SCRIPTC_CHECKER"] = checker; + } if (result.ok) { expect.unreachable(`${name} compiled successfully but must produce diagnostics`); } diff --git a/tests/harness/differential.test.ts b/tests/harness/differential.test.ts index d8139299d..b70e59c86 100644 --- a/tests/harness/differential.test.ts +++ b/tests/harness/differential.test.ts @@ -155,15 +155,27 @@ function wantsNoDeprecation(file: string): boolean { /** The Node oracle's argv for a corpus entry (shims + the per-program * transform flag). */ +/** `// @disallow-code-generation` in the directive head: the Node oracle + * runs with --disallow-code-generation-from-strings — the mode compiled + * programs match for eval and the Function constructor, which throw its + * EvalError (code-generation.ts). Programs that never generate code + * behave identically either way. */ +function wantsDisallowCodeGeneration(file: string): boolean { + return directiveHead(file).some((l) => /^\/\/ @disallow-code-generation\s*$/.test(l)); +} + function nodeOracleArgs(file: string): string[] { const transform = wantsTransformTypes(file) ? ["--experimental-transform-types", "--disable-warning=ExperimentalWarning"] : []; const nodep = wantsNoDeprecation(file) ? ["--no-deprecation"] : []; + const codegen = wantsDisallowCodeGeneration(file) + ? ["--disallow-code-generation-from-strings"] + : []; // --import makes Node load even a CJS entry through its ESM loader, // changing an entry throw's uncaughtException origin to unhandledRejection. const shims = directiveHead(file).includes("// @no-node-shims") ? [] : ["--import", comptimeShim]; - return [...transform, ...nodep, ...shims, nodeOracleFile(file)]; + return [...transform, ...nodep, ...codegen, ...shims, nodeOracleFile(file)]; } /** Runs a binary, tolerating an expected nonzero exit (execFile rejects on diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index 26dfd3a5c..c35022f51 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -359,6 +359,37 @@ const arr = tuple.map(() => new Date(0));`, expect(r).toEqual({ stdout: reference.stdout, stderr: reference.stderr, exitCode: 0 }); }); + test.each([ + ["an argument", "make(Upper, { objectMode: true });"], + ["a spread argument", "spread(Upper, [{ objectMode: true }]);"], + ])( + "%s through an untyped factory to an inherited stream constructor is a named deferred fence (JS lane)", + async (_, call) => { + // The constructor a ctor-less stream subclass inherits takes no + // arguments here; typed construction refuses them at compile time, + // and construction from untyped code refuses them at run time + // instead of constructing without them. + const r = await compileAndRun( + `inherited-stream-ctor-${call.startsWith("make") ? "args" : "spread"}`, + `'use strict'; +const { Transform } = require('node:stream'); +class Upper extends Transform {} +function make(F, options) { return new F(options); } +function bare(F) { return new F(); } +function spread(F, args) { return new F(...args); } +console.log('bare', bare(Upper) instanceof Upper, spread(Upper, []) instanceof Upper); +${call} +`, + "cjs", + ); + expect(r.exitCode).toBe(1); + expect(r.stdout).toBe("bare true true\n"); + expect(r.stderr).toMatch( + /^Uncaught Error: new Upper with arguments through an inherited stream constructor .* is not supported yet \[SC1090 at .*inherited-stream-ctor-\w+\.cjs:3\]\n$/, + ); + }, + ); + test("extending a property-assigned class ABOVE its assignment is a named deferred fence (JS lane)", async () => { // The property-assigned class-expression family (corpus 2032/2033 // pins what lowers): `exports.O = class extends exports.I {}` with @@ -1009,7 +1040,7 @@ describe("refused statements in speculatively lowered JavaScript functions", () `(function () { var root = this; console.log("before"); - console.log(root === eval("this")); + process.on("__proto__", () => root); console.log("after"); }).call(this); `, @@ -1020,7 +1051,7 @@ describe("refused statements in speculatively lowered JavaScript functions", () "speculative-apply", `(function () { console.log("before"); - console.log(typeof eval("1")); + process.on("__proto__", () => {}); }).apply(null, []); `, "before\n", @@ -1031,7 +1062,7 @@ describe("refused statements in speculatively lowered JavaScript functions", () `class Base {} class Derived extends (function () { console.log("before"); - console.log(typeof eval("1")); + process.on("__proto__", () => {}); return Base; })() {} console.log(typeof Derived); @@ -1045,7 +1076,7 @@ console.log(typeof Derived); expect(r.stdout).toBe(stdout); expect(r.stderr).toMatch( new RegExp( - `^Uncaught Error: values of type '\\(x: string\\) => any' cannot be compiled: .* \\[SC2009 at .*${name}\\.cjs:${line}\\]\\n$`, + `^Uncaught Error: 'process\\.on\\("__proto__", \\.\\.\\.\\)' is part of the standard library types but has no scriptc lowering yet \\[SC2020 at .*${name}\\.cjs:${line}\\]\\n$`, ), ); }); @@ -1150,6 +1181,244 @@ export function plain(x) { return x + 1; } /^Uncaught Error: calling a module export that reads 'this' through its module binding \('who': .*\) is not supported yet \[SC1090 at .*namespace-member-this\.mjs:3\]\n$/, ); }); + + test("a namespace import's export that passes this on with apply or call compiles", async () => { + // Effect 4's provideService: `dual(...).apply(this, arguments)` hands the + // receiver to a function that `dual` returns, which reads it only to + // pass it on to an arrow, as here. + const r = await compileAndRun( + "namespace-member-forwards-this", + `import * as ns from './lib.mjs'; +export const added = ns.provide(1, 2, 3); +console.log(added, ns.sum(3, 4)); +`, + "mjs", + { + "lib.mjs": `const dual = function (arity, body) { + switch (arity) { + case 2: + return function (a, b) { return arguments.length >= 2 ? body(a, b) : (self) => body(self, a); }; + default: + return function () { return body.apply(this, arguments); }; + } +}; +export const provide = function () { return dual(3, (a, b, c) => a + b + c).apply(this, arguments); }; +const twice = function (a, b) { return (a + b) * 2; }; +export const sum = function (a, b) { return twice.call(this, a, b); }; +`, + }, + ); + expect(r).toEqual({ stdout: "6 14\n", stderr: "", exitCode: 0 }); + }); + + test("a require binding's export that passes this on with apply compiles", async () => { + const r = await compileAndRun( + "require-member-forwards-this", + `'use strict'; +const m = require('./lib.cjs'); +console.log(m.scale(5)); +`, + "cjs", + { + "lib.cjs": `'use strict'; +const triple = function (x) { return x * 3; }; +exports.scale = function () { return triple.apply(this, arguments); }; +`, + }, + ); + expect(r).toEqual({ stdout: "15\n", stderr: "", exitCode: 0 }); + }); + + test.each([ + [ + "a namespace import's export that passes this on to a function that reads it", + "namespace-member-forwards-to-reader", + "mjs", + `import * as ns from './lib.mjs'; +console.log(ns.plain(1)); +console.log(ns.viaCall('?')); +`, + { + "lib.mjs": `export const tag = 'ns'; +function read(suffix) { return this.tag + suffix; } +export function viaCall(suffix) { return read.call(this, suffix); } +export function plain(x) { return x + 1; } +`, + }, + "2\n", + "viaCall", + 3, + ], + [ + "a namespace import's export that passes this on through a factory to a function that reads it", + "namespace-member-forwards-through-factory", + "mjs", + `import * as ns from './lib.mjs'; +console.log(ns.plain(1)); +console.log(ns.viaDual('?')); +`, + { + "lib.mjs": `export const tag = 'ns'; +const dual = function (arity, body) { + switch (arity) { + case 2: + return function (a, b) { return arguments.length >= 2 ? body(a, b) : (self) => body(self, a); }; + default: + return function () { return body.apply(this, arguments); }; + } +}; +export const viaDual = function () { return dual(9, function (s) { return this.tag + s; }).apply(this, arguments); }; +export function plain(x) { return x + 1; } +`, + }, + "2\n", + "viaDual", + 3, + ], + [ + "a namespace import's export that passes this on through a factory that reassigns its parameter", + "namespace-member-forwards-reassigned", + "mjs", + `import * as ns from './lib.mjs'; +console.log(ns.plain(1)); +console.log(ns.viaWrap('?')); +`, + { + "lib.mjs": `export const tag = 'ns'; +const wrap = function (body) { + body = function (s) { return this.tag + s; }; + return function () { return body.apply(this, arguments); }; +}; +export const viaWrap = function () { return wrap((s) => s).apply(this, arguments); }; +export function plain(x) { return x + 1; } +`, + }, + "2\n", + "viaWrap", + 3, + ], + [ + "a namespace import's export that passes this on through a factory that replaces its parameter as a destructuring", + "namespace-member-forwards-destructured", + "mjs", + `import * as ns from './lib.mjs'; +console.log(ns.plain(1)); +console.log(ns.viaWrap('?')); +`, + { + "lib.mjs": `export const tag = 'ns'; +const wrap = function (body) { + [body] = [function (s) { return this.tag + s; }]; + return function () { return body.apply(this, arguments); }; +}; +export const viaWrap = function () { return wrap((s) => s).apply(this, arguments); }; +export function plain(x) { return x + 1; } +`, + }, + "2\n", + "viaWrap", + 3, + ], + [ + "a namespace import's export that passes this on through a factory that replaces its parameter as a object destructuring", + "namespace-member-forwards-object-destructured", + "mjs", + `import * as ns from './lib.mjs'; +console.log(ns.plain(1)); +console.log(ns.viaWrap('?')); +`, + { + "lib.mjs": `export const tag = 'ns'; +const wrap = function (body) { + ({ body } = { body: function (s) { return this.tag + s; } }); + return function () { return body.apply(this, arguments); }; +}; +export const viaWrap = function () { return wrap((s) => s).apply(this, arguments); }; +export function plain(x) { return x + 1; } +`, + }, + "2\n", + "viaWrap", + 3, + ], + [ + "a namespace import's export that passes this on through a factory that replaces its parameter as a for-of target", + "namespace-member-forwards-for-of", + "mjs", + `import * as ns from './lib.mjs'; +console.log(ns.plain(1)); +console.log(ns.viaWrap('?')); +`, + { + "lib.mjs": `export const tag = 'ns'; +const wrap = function (body) { + for (body of [function (s) { return this.tag + s; }]); + return function () { return body.apply(this, arguments); }; +}; +export const viaWrap = function () { return wrap((s) => s).apply(this, arguments); }; +export function plain(x) { return x + 1; } +`, + }, + "2\n", + "viaWrap", + 3, + ], + [ + "a require binding's export that passes this on to a function that reads it", + "require-member-forwards-to-reader", + "cjs", + `'use strict'; +const m = require('./lib.cjs'); +console.log(m.plain(21)); +console.log(m.viaApply('!')); +`, + { + "lib.cjs": `'use strict'; +exports.name = 'lib'; +const read = function (suffix) { return this.name + suffix; }; +exports.viaApply = function () { return read.apply(this, arguments); }; +exports.plain = function (x) { return x * 2; }; +`, + }, + "42\n", + "viaApply", + 4, + ], + ] as const)( + "%s is a named deferred fence", + async (_, name, ext, source, files, stdout, member, line) => { + // Node passes the module object on to `read`, which reads it; the + // direct call has none to pass. + const r = await compileAndRun(name, source, ext, files); + expect(r.exitCode).toBe(1); + expect(r.stdout).toBe(stdout); + expect(r.stderr).toMatch( + new RegExp( + `^Uncaught Error: calling a module export that reads 'this' through its module binding \\('${member}': .*\\) is not supported yet \\[SC1090 at .*${name}\\.${ext}:${line}\\]\\n$`, + ), + ); + }, + ); + + test("an export that also reads this beside passing it on is a named deferred fence", async () => { + const r = await compileAndRun( + "namespace-member-forwards-and-reads-this", + `import * as ns from './lib.mjs'; +console.log(ns.tag('x')); +`, + "mjs", + { + "lib.mjs": `const join = function (a, b) { return a + ':' + b; }; +export function tag(x) { return join.call(this, x, typeof this); } +`, + }, + ); + expect(r.exitCode).toBe(1); + expect(r.stdout).toBe(""); + expect(r.stderr).toMatch( + /^Uncaught Error: calling a module export that reads 'this' through its module binding \('tag': .*\) is not supported yet \[SC1090 at .*namespace-member-forwards-and-reads-this\.mjs:2\]\n$/, + ); + }); }); // CommonJS forms around the module's exports that keep a refusal name the @@ -1174,7 +1443,7 @@ console.log(keys(exports)); [ "exports-rebinding", `console.log("before"); -exports = module.exports = function hash(x) { +exports = function hash(x) { return x; }; exports.extra = 1; @@ -1182,7 +1451,7 @@ exports.extra = 1; {}, "before\n", 2, - "rebinding 'exports' \\(exports = module\\.exports = value\\)", + "rebinding 'exports' without module\\.exports \\(exports = value\\)", ], [ "require-computed", @@ -1232,6 +1501,70 @@ console.log(count(require("./lib.cjs"))); }); }); +describe("builtin constructor values (JS lane)", () => { + test("members without a lowering stay refused instead of reading undefined", async () => { + // Builtin constructors read as interned function values that model only + // name, length and constructor. Every other static member or prototype + // read (direct, bracketed, `in`, through an alias or globalThis), and + // reflection on or redefinition of their members, must stay a located + // SC2020/SC1090 refusal, never an undefined or otherwise wrong value. + // Test262 lost 769 refusals to undefined reads before this was pinned. + const reads = [ + ["Map.prototype", "Map.prototype"], + ["Date.prototype", "Date.prototype"], + ["RegExp.prototype", "RegExp.prototype"], + ["Set.prototype", "Set.prototype"], + ["Promise.prototype", "Promise.prototype"], + ["WeakMap.prototype", "WeakMap.prototype"], + ["WeakSet.prototype", "WeakSet.prototype"], + ["Date.prototype.getYear", "Date.prototype.getYear"], + ["Map.prototype.constructor", "Map.prototype.constructor"], + ["Object.isFrozen", "Object.isFrozen"], + [ + "getOwnPropertyDescriptor(Map.prototype)", + 'Object.getOwnPropertyDescriptor(Map.prototype, "size")', + ], + ["isExtensible(Date.prototype)", "Object.isExtensible(Date.prototype)"], + ["Map['prototype']", 'Map["prototype"]'], + ["'prototype' in Map", '"prototype" in Map'], + ["globalThis.Map.prototype", "globalThis.Map.prototype"], + ["Map alias prototype", "MapAlias.prototype"], + ["Date alias prototype", "DateAlias.prototype"], + ["Object alias isFrozen", "ObjectAlias.isFrozen"], + ["Object.hasOwn(Object)", 'Object.hasOwn(Object, "hasOwn")'], + ["getOwnPropertyNames(Date)", "Object.getOwnPropertyNames(Date)"], + ["defineProperty(Promise)", 'Object.defineProperty(Promise, "resolve", { value: 1 })'], + ["hasOwnProperty.call(Map)", 'Object.prototype.hasOwnProperty.call(Map, "groupBy")'], + ] as const; + const source = `"use strict"; +const MapAlias = Map; +const DateAlias = Date; +const { Object: ObjectAlias } = globalThis; +function report(label, e) { + const m = /\\[(SC\\d{4}) at [^\\]]*:(\\d+)\\]$/.exec(e.message); + console.log(label, m ? "refused " + m[1] + " line " + m[2] : "threw " + e.message); +} +${reads.map(([label, read]) => `try { const value = ${read}; console.log(${JSON.stringify(label)}, "read", typeof value); } catch (e) { report(${JSON.stringify(label)}, e); }`).join("\n")} +console.log(Date.length, RegExp.length, Promise.length, Object.length, Map.length, Set.length); +console.log(Date.name, Map.name, Map.constructor === Function, Object.constructor === Function); +`; + const r = await compileAndRun("builtin-constructor-members", source, "cjs"); + expect(r.stderr).toBe(""); + expect(r.exitCode).toBe(0); + const lines = r.stdout.trimEnd().split("\n"); + expect(lines.slice(0, reads.length)).toEqual( + reads.map(([label], i) => + expect.stringMatching( + new RegExp( + `^${label.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")} refused SC(2020|1090) line ${i + 9}$`, + ), + ), + ), + ); + expect(lines.slice(reads.length)).toEqual(["7 2 1 1 0 0", "Date Map true true"]); + }); +}); + // WebCrypto's native slice is HMAC importKey('raw'), sign and verify, and // the SHA-1/SHA-2 digests. Algorithms Node recognizes outside that slice // reject with a coded SC2020 Error instead of a Node-shaped success, and a diff --git a/tests/harness/esm-path-globals.test.ts b/tests/harness/esm-path-globals.test.ts new file mode 100644 index 000000000..546c924a9 --- /dev/null +++ b/tests/harness/esm-path-globals.test.ts @@ -0,0 +1,38 @@ +/* __dirname / __filename in TypeScript files that Node's strip-types rule + * loads as ES modules: scriptc compiles them to the module's source path, + * the CommonJS-bundle reading. Node itself has no such globals in an ES + * module, so the oracle runs a twin of each source with the globals spelled + * import.meta.dirname / import.meta.filename, at the same paths. */ +import { execFile } from "node:child_process"; +import { cpSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const fixture = join(import.meta.dirname, "../fixtures/esm-path-globals"); + +test("__dirname and __filename in TypeScript ES modules read the module's source path", async () => { + const dir = mkdtempSync(join(tmpdir(), "scriptc-esm-path-globals-")); + cpSync(fixture, join(dir, "src"), { recursive: true }); + const entry = join(dir, "src", "main.ts"); + const result = await compile(entry, { + outPath: join(dir, "main"), + outDir: dir, + sanitize: process.env["SCRIPTC_SAN"] === "1", + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const native = await execFileAsync(result.binaryPath); + for (const file of [entry, join(dir, "src", "lib", "helper.ts")]) { + const text = readFileSync(file, "utf8") + .replaceAll("__dirname", "import.meta.dirname") + .replaceAll("__filename", "import.meta.filename"); + writeFileSync(file, text); + } + const node = await execFileAsync(process.execPath, [entry]); + expect(native.stdout).toBe(node.stdout); + expect(native.stdout).toContain("string string\nsrc main.ts\ntrue true\ntrue helper.ts\n"); +}); diff --git a/tests/harness/inheritance.test.ts b/tests/harness/inheritance.test.ts index c0ce3d7d4..27a176764 100644 --- a/tests/harness/inheritance.test.ts +++ b/tests/harness/inheritance.test.ts @@ -204,14 +204,11 @@ r.on("data", (b: Buffer) => console.log(b.toString())); expect(llvm).toContain("call void @scr_stream_init_readable(ptr "); }); - test("a runtime-fenced nested stream class emits the refusal without class helpers", async () => { - // The phase-1 reachable bug: a class declared inside a block (a - // runtime fence in JS) whose instances are captured emitted capture- - // box RC adapters for the uncollected class — a C compile error. - // run()'s unregistered-class sweep now rewrites such type slots to - // the inert f64 placeholder before emission (no instance can exist; - // every use traps), so the capture box is a PLAIN-kind box and the - // class never reaches the emitter at all. + test("a block-scoped stream class lowers natively with its class helpers", async () => { + // A class declared inside a block is collected like a class expression. + // Its captured instances once needed the unregistered-class sweep (the + // class was fenced); a builtin stream base no longer fences, so the + // class reaches the emitter with its object type and stream init. const llvm = await emittedLlvm( "stream-nested-fence", `const { Readable } = require("stream"); @@ -226,11 +223,10 @@ r.on("data", (b: Buffer) => console.log(b.toString())); `, "js", ); - expect(llvm).not.toContain("sc_retain_R_v"); expect(llvm).not.toContain("uncollected class"); - expect(llvm).not.toContain("%sc_o_R"); - expect(llvm).toMatch(/%sc_l_stream_0 = alloca ptr/); - expect(llvm).toContain("call void @scr_throw_error_msg_code"); + expect(llvm).toContain("%sc_o_R"); + expect(llvm).toContain("call void @scr_stream_init_readable(ptr "); + expect(llvm).not.toContain("call void @scr_throw_error_msg_code"); }); test("a getter-only override of a pair fills the set slot with a thrower", async () => { diff --git a/tests/harness/library-asyncfree.test.ts b/tests/harness/library-asyncfree.test.ts index 0adbef7bc..7c4d029cc 100644 --- a/tests/harness/library-asyncfree.test.ts +++ b/tests/harness/library-asyncfree.test.ts @@ -73,6 +73,21 @@ describe("async_free detection over the IR", () => { ); }); + test("the event-loop metrics refuse", async () => { + expect( + await surfaceOf( + "eld", + `import { monitorEventLoopDelay } from "node:perf_hooks";\nconst h = monitorEventLoopDelay();\nconsole.log(h.enable());\n`, + ), + ).toContain("monitorEventLoopDelay"); + expect( + await surfaceOf( + "elu", + `import { performance } from "node:perf_hooks";\nconsole.log(performance.eventLoopUtilization().idle >= 0);\n`, + ), + ).toContain("eventLoopUtilization"); + }); + test("child_process refuses even in its synchronous spelling", async () => { expect( await surfaceOf( diff --git a/tests/harness/library-profile.test.ts b/tests/harness/library-profile.test.ts index eb1bcc4cd..2b28a6cd0 100644 --- a/tests/harness/library-profile.test.ts +++ b/tests/harness/library-profile.test.ts @@ -569,10 +569,13 @@ describe("library profile fences", () => { expect(processIds).toContain("node-builtin.process.isTTY"); expect(r.profile.fences[1]!.surfaces.every((s) => s.detector !== undefined)).toBe(true); // The user-timing timeline reads the same clock (mark() and measure() - // default to performance.now()), so it joins the perf_hooks family. + // default to performance.now()), and so do the event-loop metrics, so + // they join the perf_hooks family. expect(r.profile.fences[2]!.surfaces.map((s) => s.id)).toEqual([ + "node-builtin.perf_hooks.monitorEventLoopDelay", "node-builtin.perf_hooks.performance.clearMarks", "node-builtin.perf_hooks.performance.clearMeasures", + "node-builtin.perf_hooks.performance.eventLoopUtilization", "node-builtin.perf_hooks.performance.getEntries", "node-builtin.perf_hooks.performance.mark", "node-builtin.perf_hooks.performance.measure", diff --git a/tests/harness/linux-differential.test.ts b/tests/harness/linux-differential.test.ts index 6b0c7f1bb..9730623cd 100644 --- a/tests/harness/linux-differential.test.ts +++ b/tests/harness/linux-differential.test.ts @@ -199,6 +199,9 @@ async function runLinuxNode(file: string): Promise { ...(directiveHead(file).some((l) => /^\/\/ @no-deprecation\s*$/.test(l)) ? ["--no-deprecation"] : []), + ...(directiveHead(file).some((l) => /^\/\/ @disallow-code-generation\s*$/.test(l)) + ? ["--disallow-code-generation-from-strings"] + : []), ...(directiveHead(file).includes("// @no-node-shims") ? [] : ["--import", inContainer(join(repoRoot, "tests/harness/comptime-shim.mjs")), "--import"]), diff --git a/tests/harness/llvm-differential.test.ts b/tests/harness/llvm-differential.test.ts index 6df4d6f6b..792fdb1d2 100644 --- a/tests/harness/llvm-differential.test.ts +++ b/tests/harness/llvm-differential.test.ts @@ -152,10 +152,14 @@ function nodeOracleArgs(file: string): string[] { ? ["--experimental-transform-types", "--disable-warning=ExperimentalWarning"] : []; const nodep = wantsNoDeprecation(file) ? ["--no-deprecation"] : []; + // differential.test.ts's @disallow-code-generation directive. + const codegen = directiveHead(file).some((l) => /^\/\/ @disallow-code-generation\s*$/.test(l)) + ? ["--disallow-code-generation-from-strings"] + : []; // --import makes Node load even a CJS entry through its ESM loader, // changing an entry throw's uncaughtException origin to unhandledRejection. const shims = directiveHead(file).includes("// @no-node-shims") ? [] : ["--import", comptimeShim]; - return [...transform, ...nodep, ...shims, nodeOracleFile(file)]; + return [...transform, ...nodep, ...codegen, ...shims, nodeOracleFile(file)]; } function programInputs(file: string): string[] { diff --git a/tests/harness/lsan.supp b/tests/harness/lsan.supp new file mode 100644 index 000000000..41125589f --- /dev/null +++ b/tests/harness/lsan.supp @@ -0,0 +1,4 @@ +# LeakSanitizer suppressions for the sanitized corpus on Linux CI +# (scripts/ci-test-plan.mjs). One `leak:` line per intentional +# leak, under a comment that gives the reason. Empty: no corpus program +# leaks. diff --git a/tests/harness/native-cli-packaging.test.ts b/tests/harness/native-cli-packaging.test.ts index a62a3c0c7..e69f9d9fe 100644 --- a/tests/harness/native-cli-packaging.test.ts +++ b/tests/harness/native-cli-packaging.test.ts @@ -42,6 +42,7 @@ function fixture(windows = true) { compiler_version: identity.version, target, ts7: asset(`../lib/typescript/lib/tsc${windows ? ".exe" : ""}`), + tsc_ts: asset(`../lib/tsc-ts/lib/tsc-ts${windows ? ".exe" : ""}`), llvm_package: asset("../lib/llvm"), runtime_pack: asset("../lib/runtime"), runtime_sources: asset("../lib/runtime-sources"), @@ -54,7 +55,12 @@ function fixture(windows = true) { write(join(distribution, "bin", executable), Buffer.alloc(2048, 1)); chmodSync(join(distribution, "bin", executable), 0o755); write(manifestPath, JSON.stringify(manifest)); - for (const path of [manifest.ts7, manifest.comptime, manifest.wasi_node_runner]) { + for (const path of [ + manifest.ts7, + manifest.tsc_ts, + manifest.comptime, + manifest.wasi_node_runner, + ]) { write(join(distribution, "bin", path.replaceAll("\\", "/")), "fixture"); } write( @@ -112,6 +118,7 @@ test.each([true, false])( readFileSync(join(unpacked, "bin", f.executable + ".json"), "utf8"), ); expect(manifest.ts7).toBe(f.manifest.ts7); + expect(manifest.tsc_ts).toBe(f.manifest.tsc_ts); const runtime = manifest.runtime_packs[0]; expect( JSON.parse( diff --git a/tests/harness/npm-cases.ts b/tests/harness/npm-cases.ts index 4dda0b9ad..01718abf9 100644 --- a/tests/harness/npm-cases.ts +++ b/tests/harness/npm-cases.ts @@ -19,12 +19,14 @@ export function npmCases(fixturesRoot: string): NpmCase[] { return [ ...globSync(join(fixturesRoot, "npm/cases/*/main.ts")) .sort() - // 2465-2469 and 2556-2557 are the --npm-static bundler-emitted-CJS + // 2465-2469, 2556-2567, 2571 and 2580-2584 are the --npm-static bundler-emitted-CJS // cases (npm-static.test.ts drives them with the opt-in): their // consumers exercise the static rewrite's surface (lexer-visible // names the shipped .d.ts never declares, the __toESM interop // family), so they stay out of the flagless island lane by design. - .filter((entry) => !/\/(246[5-9]|255[67])-[^/]+\/main\.ts$/.test(entry)) + .filter( + (entry) => !/\/(246[5-9]|255[6-9]|256[0-7]|2571|258[0-4])-[^/]+\/main\.ts$/.test(entry), + ) .map((entry) => ({ name: entry.split("/").at(-2)!, entry, diff --git a/tests/harness/npm-static.test.ts b/tests/harness/npm-static.test.ts index 356d2fa6d..c16567f47 100644 --- a/tests/harness/npm-static.test.ts +++ b/tests/harness/npm-static.test.ts @@ -76,8 +76,9 @@ async function buildStatic(entry: string, npmStatic: string[] | "auto"): Promise ...globSync( join(fixturesRoot, "npm/node_modules/cryptozoo/**/*.{js,mjs,cjs,json,d.ts}"), ).sort(), - // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2557) - ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,json}")).sort(), + // the bundler-emitted-CJS mini packages (cases 2465-2469, 2556-2567, 2571, 2580-2584) + ...globSync(join(fixturesRoot, "npm/node_modules/gt*/**/*.{js,cjs,json}")).sort(), + ...globSync(join(fixturesRoot, "npm/store/gt*/**/*.{js,cjs,json}")).sort(), ]; for (const f of inputs) hash.update(f).update(readFileSync(f)); const key = hash @@ -828,30 +829,41 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { // Types-first resolution answers @types/twinned, but the refusal names the // package that has the code and the flag that compiles it. - test("an @types-typed package is refused under its runtime name with the opt-in remedy", () => { - const { coverage } = analyze(join(pilotRoot, "twinned-cli.ts")); + // The default selection compiles a package typed by a DefinitelyTyped + // twin that describes its installed release, and the untyped + // dependencies of compiled packages. + test("an @types-typed package and its untyped dependency compile by default", async () => { + const entry = join(pilotRoot, "twinned-cli.ts"); + const { coverage } = analyze(entry); expect(coverage.npmStatic).toEqual([ - { - package: "twinned", - status: "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)", - }, + { package: "twinned", status: "static" }, + { package: "ms", status: "static" }, ]); - const diagnostic = coverage.diagnostics.find((d) => d.code === "SC2013"); - expect(diagnostic?.message).toContain("'twinned'"); - expect(diagnostic?.hint).toContain("--npm-static=twinned"); + expect(coverage.preflightFailed).toBe(false); + const binary = await buildStatic(entry, "auto"); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + expect(nativeRes.stdout.toString("utf8")).toBe(nodeRes.stdout.toString("utf8")); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, 180_000); + + // The twin only checks the program; the JavaScript compiles with its + // inferred surface, so a twin naming an export the JavaScript lacks + // (Node's link error) fails the build at the import's use instead of + // compiling against the declaration. + test("an @types twin that disagrees with the shipped JavaScript does not build", () => { + const { coverage } = analyze(join(pilotRoot, "twinstale-cli.ts")); + expect(coverage.npmStatic).toEqual([{ package: "twinstale", status: "static" }]); + expect(coverage.diagnostics).toContainEqual( + expect.objectContaining({ message: expect.stringContaining("'stale'") as string }), + ); }, 120_000); - test("a named package's untyped dependency points at the transitive opt-in", () => { + test("a named package's untyped dependency compiles with it", () => { const { coverage } = analyze(join(pilotRoot, "twinned-cli.ts"), { npmStatic: ["twinned"] }); - expect(coverage.npmStatic).toContainEqual({ - package: "ms", - status: "refused", - detail: "it ships no own .d.ts declaration surface", - hint: "to compile it anyway, name it with --npm-static=ms, or use --npm-static=twinned... to include all of twinned's dependencies", - }); + expect(coverage.npmStatic).toContainEqual({ package: "ms", status: "static" }); }, 120_000); test("--npm-static=pkg... compiles the package's dependency closure", async () => { @@ -1389,6 +1401,224 @@ describe(`npm-static pilots${sanitize ? " (sanitized)" : ""}`, () => { ]); }, 120_000); + /* ── 2558-2561: esbuild's bundled CommonJS chunks and per-file CommonJS + * cycles ─ every __commonJS chunk compiles as its own CommonJS module + * (evaluated once, at its first require); a require cycle between + * esbuild outputs whose live getter tables install before their first + * require initializes exactly like Node, and a cycle that reads a + * binding in its temporal dead zone stays refused. */ + async function expectNodeParity(entry: string, packages: string[]): Promise { + const { coverage } = analyze(entry, { npmStatic: packages }); + expect(coverage.npmStatic).toEqual(packages.map((pkg) => ({ package: pkg, status: "static" }))); + expect(coverage.preflightFailed).toBe(false); + expect(coverage.diagnostics).toHaveLength(0); + const binary = await buildStatic(entry, packages); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + // Node's stderr carries its type-stripping warning; the programs write + // only to stdout. + expect(nativeRes.stdout.toString("utf8")).toBe(nodeRes.stdout.toString("utf8")); + expect(comparableStderr(nativeRes.stderr).toString("utf8")).toBe(""); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + expect(nativeRes.exitCode).toBe(0); + } + + // 2558: esbuild 0.25 (plain and whitespace/syntax minified) and 0.17 + // CommonJS bundles of one source, plus a .cjs bundle in a type-module + // package with the node-mode interop. + test("bundled CommonJS chunks 2558-bundled-chunks compile statically and byte-match Node", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2558-bundled-chunks/main.ts"), [ + "gtchunk", + "gtchunkws", + "gtchunk17", + "gtcjsdist", + "gtcore", + ]); + }, 180_000); + + // A whitespace/syntax-minified bundle whose helpers keep their names is + // judged by the recognizers, not refused up front as minified. + test("--npm-static=auto opts a minified bundle with named helpers in", () => { + const entry = join(fixturesRoot, "npm/cases/2558-bundled-chunks/main.ts"); + const { coverage } = analyze(entry, { npmStatic: "auto" }); + expect(coverage.npmStatic).toContainEqual({ package: "gtchunkws", status: "static" }); + }, 120_000); + + test.each(["2559-cjs-cycle", "2560-cjs-cycle-partner-first"])( + "the esbuild CommonJS cycle %s compiles statically and byte-matches Node", + async (name) => { + await expectNodeParity(join(fixturesRoot, `npm/cases/${name}/main.ts`), ["gtcycle"]); + }, + 180_000, + ); + + // 2561: Node throws a ReferenceError there; the cycle keeps SC1016. + test("a CommonJS cycle that reads a binding before its declaration stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2561-cjs-cycle-early-read/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcyclebad"] }); + expect(coverage.npmStatic).toEqual([ + { + package: "gtcyclebad", + status: "refused", + detail: expect.stringMatching( + /circular imports .*can use its binding 'import_index' of a module of the cycle/, + ) as string, + }, + ]); + }, 120_000); + + // 2563: lazily initialized ES modules (__esm) in esbuild 0.25 plain and + // whitespace/syntax-minified bundles. + test("bundled ES module initializers 2563-bundled-esm-inits compile statically and byte-match Node", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2563-bundled-esm-inits/main.ts"), [ + "gtesm", + "gtesmws", + "gtcore", + ]); + }, 180_000); + + // 2580-2581: a barrel whose re-exports are memoized lazy loaders of + // lazily initialized ES modules (a bundle plugin's shape): ES-module + // importers copy every export when they link (Node's CommonJS import), + // require() reads each export's getter at the read. + test.each([ + ["2580-lazy-barrel", ["gtlazy", "gtlazyws"]], + ["2581-lazy-barrel-require", ["gtlazyuser", "gtlazy"]], + ])( + "the lazy barrel %s compiles statically and byte-matches Node", + async (name, packages) => { + await expectNodeParity(join(fixturesRoot, `npm/cases/${name}/main.ts`), packages); + }, + 180_000, + ); + + // 2582: two releases of one package in a pnpm store, the newer through + // an npm alias directory; the default selection compiles both copies. + test("an npm alias of a selected package compiles from its JavaScript", async () => { + const entry = join(fixturesRoot, "npm/cases/2582-npm-alias-two-releases/main.ts"); + const { coverage } = analyze(entry); + expect(coverage.npmStatic).toEqual( + expect.arrayContaining([ + { package: "gtroute", status: "static" }, + { package: "gtpattern", status: "static" }, + ]), + ); + const binary = await buildStatic(entry, "auto"); + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry]), + runBinary(binary, []), + ]); + expect(nativeRes.stdout.toString("utf8")).toBe(nodeRes.stdout.toString("utf8")); + expect(nativeRes.exitCode).toBe(nodeRes.exitCode); + }, 180_000); + + // 2583: a module whose export object is one of its functions (semver + // 5/6's `exports = module.exports = Version`), under both values of the + // environment variable the module reads. + test("function export objects 2583-cjs-function-exports compile statically and byte-match Node", async () => { + const entry = join(fixturesRoot, "npm/cases/2583-cjs-function-exports/main.ts"); + await expectNodeParity(entry, ["gtfnexports", "gtfnmembers"]); + const binary = await buildStatic(entry, ["gtfnexports", "gtfnmembers"]); + const env = { ...process.env, GT_LABEL: "x" }; + const [nodeRes, nativeRes] = await Promise.all([ + runBinary("node", [entry], env), + runBinary(binary, [], env), + ]); + expect(nativeRes.stdout.toString("utf8")).toBe(nodeRes.stdout.toString("utf8")); + expect(nativeRes.stdout.toString("utf8")).toContain("[x]"); + }, 180_000); + + // 2584: an export object replaced inside an environment-dependent branch + // (readable-stream 2.x) is named precisely. + test("an environment-dependent export object is refused by name", () => { + const entry = join(fixturesRoot, "npm/cases/2584-cjs-conditional-exports/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcondexports"] }); + expect(JSON.stringify(coverage)).toContain( + "replacing module.exports outside a top-level statement (a conditional or environment-dependent export object)", + ); + }, 120_000); + + // 2567: an export that reads `this`, called through its namespace, sees + // Node's module object; the lowered namespace would differ, so the + // package stays refused. + test("a namespace call to an export that reads `this` stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2567-esm-namespace-this/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtesmthis", "gtcore"] }); + expect(coverage.npmStatic).toContainEqual({ + package: "gtesmthis", + status: "refused", + detail: expect.stringMatching( + /calls only of exported functions that do not read 'this'/, + ) as string, + }); + }, 120_000); + + // 2562: a file with unverifiable interop that no compiled code can load + // (the program imports a leaf data module and only a type from the + // main entry) leaves its package static. + test("a package is judged by the files its imported subpaths can load", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2562-subpath-leaf/main.ts"), [ + "gtsubpath", + ]); + }, 180_000); + + // 2566: the same early read through esbuild's `(0, binding.fn)()` call + // spelling also keeps SC1016. + test("a CommonJS cycle that calls an export during initialization stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2566-cjs-cycle-indirect-call/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcyclecall"] }); + expect(coverage.npmStatic).toEqual([ + { + package: "gtcyclecall", + status: "refused", + detail: expect.stringMatching( + /circular imports .*can use its binding 'import_index' of a module of the cycle/, + ) as string, + }, + ]); + }, 120_000); + + // 2571: the partner stores the export (in a const, or in an object + // literal) and calls it while the cycle initializes; both keep SC1016. + test("a CommonJS cycle that stores an export and calls it stays refused", () => { + const entry = join(fixturesRoot, "npm/cases/2571-cjs-cycle-stored-function/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtcyclealias", "gtcyclebox"] }); + for (const pkg of ["gtcyclealias", "gtcyclebox"]) + expect(coverage.npmStatic).toContainEqual({ + package: pkg, + status: "refused", + detail: expect.stringMatching( + /circular imports .*can use its binding 'import_index' of a module of the cycle/, + ) as string, + }); + }, 120_000); + + // 2564: a chunk reads a top-level function whose name a nested scope of + // the chunk also declares; the function still travels with the chunk. + test("a chunk carries a top-level function its nested scopes shadow", async () => { + await expectNodeParity(join(fixturesRoot, "npm/cases/2564-chunk-helper-shadowing/main.ts"), [ + "gtchunkshadow", + ]); + }, 180_000); + + // 2565: Node shares one function between the chunks that export it; a + // per-chunk copy would not, so the package stays refused. + test("chunks that export a shared top-level function stay refused", () => { + const entry = join(fixturesRoot, "npm/cases/2565-chunk-shared-function/main.ts"); + const { coverage } = analyze(entry, { npmStatic: ["gtchunkshared"] }); + expect(coverage.npmStatic).toEqual([ + { + package: "gtchunkshared", + status: "refused", + detail: expect.stringMatching( + /uses the bundle's top-level function 'tally' as a value, which its chunks cannot share/, + ) as string, + }, + ]); + }, 120_000); + test("published type-only declarations coexist with a static runtime bundle", async () => { const entry = join(fixturesRoot, "npm/cases/2469-bundle-offender/main.ts"); const { coverage } = analyze(entry, { npmStatic: ["gtghost"] }); diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index 54207f246..f5f3fdeeb 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -40,7 +40,6 @@ const KNOWN: Record = { "bytes-exit": { build: "SC2004" }, "cjs-esm-named": { build: "SC2013" }, "cjs-lexer-aligned": { build: "SC2013" }, - "cjs-lexer-invisible": { build: "SC2013" }, "commander-calc": { run: "SC1090", argvs: [["--help"]] }, "commander-calc-async": { run: "SC1090", argvs: [["--help"]] }, "create-require": { run: "SC1090" }, @@ -53,16 +52,15 @@ const KNOWN: Record = { "esm-missing-export": { build: "SC1090" }, "fetch-destructure": { run: "SC1031" }, "fs-shims": { run: "SC1101" }, - "hoisted-workspace": { build: "SC2013" }, "import-refusal": { build: "SC2020" }, "island-registry": { build: "SC2011" }, "island-timers": { divergence: "Timeout handles are numbers (typeof) and lose their ref flag once fired or cleared", }, - "island-web-plumbing": { build: "SC2013" }, + "island-web-plumbing": { run: "SC2020" }, "lazy-traps": { build: "SC2013" }, - "misc-shims": { build: "SC2013" }, + "misc-shims": { run: "SC2020" }, "namespace": { build: "SC1013" }, "node24-async-events": { run: "SC2020" }, "node24-commonjs-metadata": { run: "SC2020" }, @@ -89,24 +87,24 @@ const KNOWN: Record = { "node24-url-factories": { run: "SC2020" }, "node24-url-mutation": { run: "SC1090" }, "node24-util-adapters": { run: "SC2020" }, - "node24-util-types": { run: "SC2009" }, - "node24-web-stream-encoding": { build: "SC2013" }, - "node24-web-stream-errors": { build: "SC2013" }, - "node24-web-stream-lifecycle": { build: "SC2013" }, - "node24-web-stream-pipe-abort": { build: "SC2013" }, - "node24-web-stream-pipe-errors": { build: "SC2013" }, - "node24-web-stream-pipes": { build: "SC2013" }, - "node24-web-stream-queues": { build: "SC2013" }, - "node24-web-stream-readable-lifecycle": { build: "SC2013" }, - "node24-web-stream-readers": { build: "SC2013" }, - "node24-web-stream-tee": { build: "SC2013" }, - "node24-web-stream-transforms": { build: "SC2013" }, + "node24-util-types": { run: "SC1090" }, + "node24-web-stream-encoding": { run: "SC1090" }, + "node24-web-stream-errors": { run: "SC1090" }, + "node24-web-stream-lifecycle": { run: "SC1090" }, + "node24-web-stream-pipe-abort": { run: "SC2020" }, + "node24-web-stream-pipe-errors": { run: "SC1090" }, + "node24-web-stream-pipes": { run: "SC1090" }, + "node24-web-stream-queues": { run: "SC1090" }, + "node24-web-stream-readable-lifecycle": { run: "SC2020" }, + "node24-web-stream-readers": { run: "SC1090" }, + "node24-web-stream-tee": { run: "SC2020" }, + "node24-web-stream-transforms": { run: "SC1090" }, "package-imports": { build: "SC2013" }, "promise-bridge": { build: "SC1090" }, "self-name": { build: "SC2013" }, - "stream-shims": { build: "SC1090" }, + "stream-shims": { run: "SC1090" }, "util-shims": { run: "SC2020" }, - "web-streams": { build: "SC2013" }, + "web-streams": { run: "SC1090" }, "workspace-copied": { build: "SC2013" }, "workspace-unclean": { build: "SC2013" }, }; diff --git a/tests/harness/project-config.test.ts b/tests/harness/project-config.test.ts index 802fbd3e1..2b705edae 100644 --- a/tests/harness/project-config.test.ts +++ b/tests/harness/project-config.test.ts @@ -52,6 +52,32 @@ test("node-types: filesystem capacity supports typed number and BigInt results u expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +for (const [name, title] of [ + [ + "event-loop-metrics", + "eventLoopUtilization and monitorEventLoopDelay follow @types/node's types", + ], + ["timeout-type", "NodeJS.Timeout fields, ref/unref/hasRef and clearInterval under @types/node"], +] as const) { + test(`node-types: ${title}`, async () => { + const outDir = outDirFor(name); + const entry = join(nodeTypesDir, `${name}.ts`); + const result = await compile(entry, { + outPath: join(outDir, process.platform === "win32" ? `${name}.exe` : name), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); + }); +} + test("node-types: http header arrays and server.address() follow @types/node's types", async () => { const outDir = outDirFor("http-headers-address"); const entry = join(nodeTypesDir, "http-headers-address.ts"); @@ -179,6 +205,24 @@ test("node-types: events.once over typed EventEmitter listeners under @types/nod expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: events.once with a signal over streams, raced, under @types/node", async () => { + const outDir = outDirFor("events-once-signal-race"); + const entry = join(nodeTypesDir, "events-once-signal-race.ts"); + const result = await compile(entry, { + outPath: join(outDir, "events-once-signal-race"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: socket.address and resetAndDestroy under @types/node", async () => { const outDir = outDirFor("net-reset-address"); const entry = join(nodeTypesDir, "net-reset-address.ts"); @@ -200,6 +244,27 @@ test("node-types: socket.address and resetAndDestroy under @types/node", async ( expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: TLSSocket.isSessionReused and keyed ??= timings under @types/node", async () => { + const outDir = outDirFor("tls-session-timings"); + const entry = join(nodeTypesDir, "tls-session-timings.ts"); + const result = await compile(entry, { + outPath: join( + outDir, + process.platform === "win32" ? "tls-session-timings.exe" : "tls-session-timings", + ), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: net.SocketAddress and net.BlockList under @types/node", async () => { const outDir = outDirFor("net-blocklist"); const entry = join(nodeTypesDir, "net-blocklist.ts"); @@ -404,6 +469,135 @@ test("node-types: promisified fs callback functions lower as their fs/promises c expect(comparableStderr(native.stderr)).toBe(node.stderr); }); +test("node-types: util.promisify over typed callback-last functions settles like Node", async () => { + const outDir = outDirFor("promisify-callback-functions"); + const entry = join(nodeTypesDir, "promisify-callback-functions.ts"); + const result = await compile(entry, { + outPath: join(outDir, "promisify-callback-functions"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + +test("node-types: lookup-option unions and narrowed header-record entries under @types/node", async () => { + const outDir = outDirFor("lookup-and-header-entries"); + const entry = join(nodeTypesDir, "lookup-and-header-entries.ts"); + const result = await compile(entry, { + outPath: join(outDir, "lookup-and-header-entries"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const native = await execFileAsync(result.binaryPath); + const node = await execFileAsync(process.execPath, [ + "--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", + entry, + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + +test("node-types: http.request over a Unix socket with RequestOptions read at run time", async () => { + const outDir = outDirFor("http-socket-path"); + const entry = join(nodeTypesDir, "http-socket-path.ts"); + const result = await compile(entry, { + outPath: join(outDir, "http-socket-path"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const native = await execFileAsync(result.binaryPath); + const node = await execFileAsync(process.execPath, [ + "--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", + entry, + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + +test("node-types: RequestInit-derived options carry BodyInit and HeadersInit into fetch", async () => { + const outDir = outDirFor("fetch-init-members"); + const entry = join(nodeTypesDir, "fetch-init-members.ts"); + const result = await compile(entry, { + outPath: join(outDir, "fetch-init-members"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const native = await execFileAsync(result.binaryPath); + const node = await execFileAsync(process.execPath, [ + "--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", + entry, + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + +test("node-types: stream subclasses extend the @types/node stream classes", async () => { + const outDir = outDirFor("stream-subclass-hooks"); + const entry = join(nodeTypesDir, "stream-subclass-hooks.ts"); + const result = await compile(entry, { + outPath: join(outDir, "stream-subclass-hooks"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + +test("node-types: NodeJS stream interfaces and stream.Readable/Writable take every stream producer", async () => { + const outDir = outDirFor("node-stream-interfaces"); + const entry = join(nodeTypesDir, "node-stream-interfaces.ts"); + const result = await compile(entry, { + outPath: join(outDir, "node-stream-interfaces"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + +test("node-types: streams construct and child listeners take @types/node's parameter types", async () => { + const outDir = outDirFor("stream-construction"); + const entry = join(nodeTypesDir, "stream-construction.ts"); + const result = await compile(entry, { + outPath: join(outDir, "stream-construction"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); + test("node-types: promisified setTimeout and setImmediate lower as timers/promises", async () => { const outDir = outDirFor("promisify-timers"); const entry = join(nodeTypesDir, "promisify-timers.ts"); @@ -476,26 +670,22 @@ test("node-types: promisified dns lookups settle with the lookup objects", async expect(comparableStderr(native.stderr)).toBe(node.stderr); }); -test("node-types: unsupported child stdin forms remain named fences", async () => { - const dir = outDirFor("node-child-stdin-fences"); - const entry = join(nodeTypesDir, "child-stdin-fences.ts"); +test("node-types: child stdin forms beyond write(string) compile through the general stream representation", async () => { + const dir = outDirFor("node-child-stdin-streams"); + const entry = join(nodeTypesDir, "child-stdin-streams.ts"); const result = await compile(entry, { - outPath: join(dir, "child-stdin-fences"), + outPath: join(dir, "child-stdin-streams"), outDir: dir, sanitize, }); - expect(result.ok).toBe(false); - if (result.ok) return; - expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toEqual([ - "SC2020", - "SC2020", - "SC2020", - ]); - expect(result.diagnostics.map((diagnostic) => diagnostic.message)).toEqual([ - expect.stringContaining("child stdin write with 2 arguments"), - expect.stringContaining("Writable.end"), - expect.stringContaining("Writable.destroy"), + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); }); test("node-types: callback exec uses the typed ExecException overloads", async () => { @@ -1003,3 +1193,21 @@ test("node-types: terminal properties and filesystem error metadata match Node", expect(native.stdout).toBe(node.stdout); expect(comparableStderr(native.stderr)).toBe(node.stderr); }); + +test("node-types: process.versions entries @types/node declares as strings read undefined when absent", async () => { + const outDir = outDirFor("process-versions-absent"); + const entry = join(nodeTypesDir, "process-versions-absent.ts"); + const result = await compile(entry, { + outPath: join(outDir, "process-versions-absent"), + outDir, + sanitize, + }); + expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true); + if (!result.ok) return; + const [native, node] = await Promise.all([ + execFileAsync(result.binaryPath), + execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry]), + ]); + expect(native.stdout).toBe(node.stdout); + expect(comparableStderr(native.stderr)).toBe(node.stderr); +}); diff --git a/tests/harness/runtime-tree-shaking.test.ts b/tests/harness/runtime-tree-shaking.test.ts index 7d85a48c3..b33a0d2f9 100644 --- a/tests/harness/runtime-tree-shaking.test.ts +++ b/tests/harness/runtime-tree-shaking.test.ts @@ -172,6 +172,33 @@ nativeToolchainTest( }, ); +nativeToolchainTest( + "the event loop links the child-process unit only for programs that spawn", + async () => { + const timer = await build( + "async-timer", + `await new Promise((resolve) => setTimeout(resolve, 1)); +console.log("timer"); +`, + ); + await expectNodeParity(timer.sourcePath, timer.binaryPath); + const timerSymbols = await symbols(timer.binaryPath); + for (const family of ["scr_children_", "scr_child_", "scr_ipc_"]) { + expect(timerSymbols, `the timer program retains ${family}`).not.toContain(family); + } + + const spawned = await build( + "async-spawn", + `import { spawn } from "node:child_process"; +const child = spawn("/bin/echo", ["child"], { stdio: "ignore" }); +child.on("close", (code) => console.log("close", code)); +`, + ); + await expectNodeParity(spawned.sourcePath, spawned.binaryPath); + expect(await symbols(spawned.binaryPath)).toContain("scr_children_poll"); + }, +); + nativeToolchainTest("fetch response JSON retains the URL and parser runtime", async () => { const server = createServer((_request, response) => { response.setHeader("content-type", "application/json"); diff --git a/tests/harness/self-hosting-native-toolchain.test.ts b/tests/harness/self-hosting-native-toolchain.test.ts index fa71b5a08..83cc0d2fd 100644 --- a/tests/harness/self-hosting-native-toolchain.test.ts +++ b/tests/harness/self-hosting-native-toolchain.test.ts @@ -30,12 +30,15 @@ const features: NativeLinkFeatures = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, @@ -43,6 +46,7 @@ const features: NativeLinkFeatures = { tlsCa: false, cipher: false, webcrypto: false, + perfLoop: false, }; test("native toolchain configuration, staging and helper validation are static", async () => { @@ -121,6 +125,7 @@ for (const backend of ["llvm"] as const) { compiler_version: manifest.version, target: target.name, ts7: "ts7", + tsc_ts: "tsc-ts", llvm_package: "llvm", runtime_pack: "pack", linker: "ld", diff --git a/tests/harness/self-hosting-runtime-features.test.ts b/tests/harness/self-hosting-runtime-features.test.ts index d2c2b589b..b2f7e3062 100644 --- a/tests/harness/self-hosting-runtime-features.test.ts +++ b/tests/harness/self-hosting-runtime-features.test.ts @@ -50,6 +50,8 @@ const featureCalls: readonly [string, IrLibFn][] = [ ["bigint", "bigint.parse"], ["searchParams", "sp.new"], ["qs", "qs.parse"], + ["dynBuiltinClass", "dyn.builtinClass"], + ["dynBuiltinStream", "dyn.builtinStreamClass"], ["parseArgs", "util.parseArgs"], ["parseArgs", "util.isDeepStrictEqual"], ["parseArgs", "util.styleText"], diff --git a/tests/harness/self-hosting-runtime-pack.test.ts b/tests/harness/self-hosting-runtime-pack.test.ts index c95d6d9a0..2b9e51969 100644 --- a/tests/harness/self-hosting-runtime-pack.test.ts +++ b/tests/harness/self-hosting-runtime-pack.test.ts @@ -31,12 +31,15 @@ const base: NativeLinkFeatures = { bigint: false, searchParams: false, qs: false, + dynBuiltinClass: false, + dynBuiltinStream: false, parseArgs: false, stream: false, net: false, http: false, http2: false, dgram: false, + dnsPromises: false, watch: false, foreignFfi: false, nodeTest: false, @@ -44,6 +47,7 @@ const base: NativeLinkFeatures = { tlsCa: false, cipher: false, webcrypto: false, + perfLoop: false, }; test("runtime manifest validation and selection are entirely static", async () => { diff --git a/tests/harness/self-hosting-ts7-process.test.ts b/tests/harness/self-hosting-ts7-process.test.ts index b8d53dff9..d49a5c092 100644 --- a/tests/harness/self-hosting-ts7-process.test.ts +++ b/tests/harness/self-hosting-ts7-process.test.ts @@ -4,7 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { analyze, compile } from "@scriptc/compiler"; import { expect, test } from "vitest"; -import { ts7Executable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js"; +import { tsgoExecutable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js"; import { runClient } from "./ts7-client-harness.js"; const root = join(import.meta.dirname, "../.."); @@ -96,8 +96,9 @@ for (const backend of ["llvm"] as const) { ); const report = join(dir, "native.json"); // Only ordinary stdio is inherited. The native executable starts the - // actual tsgo process; the test runner never creates its RPC pipes. - const run = spawnSync(built.binaryPath, [ts7Executable(), dir, report], { + // actual tsgo process; the test runner never creates its RPC pipes. As in + // ts7-client-harness.ts, the protocol cases run against tsgo. + const run = spawnSync(built.binaryPath, [tsgoExecutable(), dir, report], { encoding: "utf8", timeout: 45_000, }); diff --git a/tests/harness/ts7-client-harness.ts b/tests/harness/ts7-client-harness.ts index e0d0eccd6..b65dc706f 100644 --- a/tests/harness/ts7-client-harness.ts +++ b/tests/harness/ts7-client-harness.ts @@ -2,7 +2,7 @@ import { spawn, type ChildProcess } from "node:child_process"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { expect } from "vitest"; -import { ts7Executable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js"; +import { tsgoExecutable } from "../../packages/compiler/src/frontend/ts7/rpc-api.js"; import { TS7_FILE_SYSTEM_CALLBACKS } from "../../packages/compiler/src/frontend/ts7/rpc-filesystem.js"; const root = fileURLToPath(new URL("../..", import.meta.url)); @@ -40,8 +40,10 @@ export async function runClient( directory: string, report: string, ): Promise { + // The protocol cases echo bytes that are not UTF-8 text, which tsc-ts 0.0.3 + // returns decoded, so the transport is checked against tsgo. const server = spawn( - ts7Executable(), + tsgoExecutable(), ["--api", "--timing", "--cwd", directory, `--callbacks=${TS7_FILE_SYSTEM_CALLBACKS}`], { stdio: ["pipe", "pipe", "pipe"], diff --git a/tests/harness/webassembly-memory-growth.test.ts b/tests/harness/webassembly-memory-growth.test.ts new file mode 100644 index 000000000..02fafe5ca --- /dev/null +++ b/tests/harness/webassembly-memory-growth.test.ts @@ -0,0 +1,96 @@ +/* WebAssembly memory growth while JavaScript holds views of the buffer — + * scriptc-only. + * + * Node detaches the previous ArrayBuffer when a memory grows (its views + * read as empty). scriptc does not detach it yet: the previous buffer and + * its views keep their length and keep aliasing the memory's storage, + * which therefore never moves, also in the explicitly bounds-checked + * configuration (sanitized builds, Windows) that has no guard pages. These + * pins check that writes through an old view and through the guest reach + * the same storage after growth, from JavaScript and from the guest. + * Under SCRIPTC_SAN=1 an access through a view of moved storage is an + * AddressSanitizer report. + */ +import { execFile } from "node:child_process"; +import { createHash } from "node:crypto"; +import { mkdirSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { describe, expect, test } from "vitest"; +import { compile } from "@scriptc/compiler"; + +const execFileAsync = promisify(execFile); +const repoRoot = join(import.meta.dirname, "../.."); +const cacheDir = join(repoRoot, "node_modules/.cache/scriptc-tests"); +const sanitize = process.env["SCRIPTC_SAN"] === "1"; + +// (module +// (memory (export "memory") 1 64) +// (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) +// (func (export "poke") (param i32 i32) (i32.store8 (local.get 0) (local.get 1))) +// (func (export "peek") (param i32) (result i32) (i32.load8_u (local.get 0)))) +const growthModule = + "AGFzbQEAAAABCwJgAX8Bf2ACf38AAwQDAAEABQQBAQFABx8EBm1lbW9yeQIABGdyb3cAAARwb2tlAAEEcGVlawACChoDBgAgAEAACwkAIAAgAToAAAsHACAALQAACw=="; + +async function run(name: string, source: string): Promise { + const key = createHash("sha256") + .update(source) + .update(sanitize ? "san" : "plain") + .digest("hex") + .slice(0, 16); + const outDir = join(cacheDir, `wasm-growth-${key}`); + mkdirSync(outDir, { recursive: true }); + const file = join(outDir, `${name}.mjs`); + writeFileSync(file, source); + const result = await compile(file, { + outPath: join(outDir, name), + outDir, + sanitize, + backend: "llvm", + }); + if (!result.ok) { + throw new Error( + "program failed to compile:\n" + + result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n"), + ); + } + const { stdout } = await execFileAsync(result.binaryPath, [], { encoding: "utf8" }); + return stdout; +} + +describe(`WebAssembly memory growth with live views${sanitize ? " (sanitized)" : ""}`, () => { + test("Memory.prototype.grow keeps earlier views on the memory's storage", async () => { + const out = await run( + "js-growth", + `const bytes = Buffer.from("${growthModule}", "base64"); +const { memory } = new WebAssembly.Instance(new WebAssembly.Module(bytes)).exports; +const view = new Uint8Array(memory.buffer); +view[0] = 7; +for (let i = 0; i < 40; i++) memory.grow(1); +view[1] = 9; +const now = new Uint8Array(memory.buffer); +now[2] = 11; +console.log(view.length, now.length, now[0], now[1], view[2]); +`, + ); + expect(out).toBe("65536 2686976 7 9 11\n"); + }, 180_000); + + test("memory.grow in the guest keeps earlier views on the memory's storage", async () => { + const out = await run( + "guest-growth", + `const bytes = Buffer.from("${growthModule}", "base64"); +const { exports } = new WebAssembly.Instance(new WebAssembly.Module(bytes)); +const view = new Uint8Array(exports.memory.buffer); +view[0] = 7; +let pages = 0; +for (let i = 0; i < 40; i++) pages = exports.grow(1); +view[1] = 9; +exports.poke(2, 11); +const now = new Uint8Array(exports.memory.buffer); +console.log(pages, view.length, now.length, exports.peek(0), exports.peek(1), view[2], now[2]); +`, + ); + expect(out).toBe("40 65536 2686976 7 9 11 11\n"); + }, 180_000); +}); diff --git a/tests/harness/windows-differential.test.ts b/tests/harness/windows-differential.test.ts index dc199d38f..366820420 100644 --- a/tests/harness/windows-differential.test.ts +++ b/tests/harness/windows-differential.test.ts @@ -354,10 +354,13 @@ async function runWindowsNode(file: string): Promise { const nodep = directiveHead(file).some((l) => /^\/\/ @no-deprecation\s*$/.test(l)) ? "--no-deprecation " : ""; + const codegen = directiveHead(file).some((l) => /^\/\/ @disallow-code-generation\s*$/.test(l)) + ? "--disallow-code-generation-from-strings " + : ""; const shims = directiveHead(file).includes("// @no-node-shims") ? "" : "--import ./comptime-shim.mjs "; - return runOnBox(`node ${transform}${nodep}${shims}${entry}`); + return runOnBox(`node ${transform}${nodep}${codegen}${shims}${entry}`); } function programInputs(file: string): string[] { diff --git a/tests/harness/zero-cost-gate.test.ts b/tests/harness/zero-cost-gate.test.ts index 0b08da748..3da134010 100644 --- a/tests/harness/zero-cost-gate.test.ts +++ b/tests/harness/zero-cost-gate.test.ts @@ -5,6 +5,7 @@ import { compareArtifact, compareCounts, compareExecutables, + definedFunctionNames, dynamicTierSymbols, nmOutput, programVerdict, @@ -141,6 +142,34 @@ test("the zero-cost gate recognizes dynamic-tier runtime symbols", () => { ).toEqual(["scr_dyn_obj_read", "scr_js_load_ic"]); }); +test("the zero-cost gate reads function names from ELF and Mach-O nm output", () => { + const elf = [ + "0000000000001000 0000000000000010 T scr_dyn_call", + "0000000000001010 0000000000000008 t scr_dyn_obj_get", + "0000000000002000 0000000000000004 D scr_dyn_table", + " U memcpy", + ].join("\n"); + expect(definedFunctionNames(elf, "linux")).toEqual(["scr_dyn_call", "scr_dyn_obj_get"]); + // Mach-O names carry a leading underscore, which would hide every + // dynamic-tier symbol from the prefix match. + const macho = [ + "0000000100001000 T _scr_dyn_call", + "0000000100001010 t _scr_dyn_obj_get", + "0000000100002000 W __main", + "0000000100003000 s _scr_dyn_add.message", + " U _memcpy", + ].join("\n"); + expect(definedFunctionNames(macho, "darwin")).toEqual([ + "scr_dyn_call", + "scr_dyn_obj_get", + "_main", + ]); + expect(dynamicTierSymbols(definedFunctionNames(macho, "darwin"))).toEqual([ + "scr_dyn_call", + "scr_dyn_obj_get", + ]); +}); + 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 } }; diff --git a/tests/wasm-spec/corpus/api.wat b/tests/wasm-spec/corpus/api.wat new file mode 100644 index 000000000..353a8421c --- /dev/null +++ b/tests/wasm-spec/corpus/api.wat @@ -0,0 +1,52 @@ +(module + (import "env" "log" (func $log (param i32))) + (import "env" "fail" (func $fail (param i32) (result i32))) + (import "env" "base" (global $base i32)) + (import "env" "big" (global $big i64)) + (memory (export "memory") 1 4) + (table (export "table") 2 8 funcref) + (global $counter (export "counter") (mut i32) (i32.const 0)) + (global (export "pi") f64 (f64.const 3.25)) + (global (export "big2") i64 (global.get $big)) + (data (i32.const 16) "scriptc\00") + (elem (i32.const 0) $double $square) + (type $unop (func (param i32) (result i32))) + (type $binop (func (param i32 i32) (result i32))) + (func $double (type $unop) (i32.mul (local.get 0) (i32.const 2))) + (func $square (type $unop) (i32.mul (local.get 0) (local.get 0))) + (start $init) + (func $init (call $log (global.get $base))) + (func (export "add") (type $binop) (i32.add (local.get 0) (local.get 1))) + (func (export "apply") (param i32 i32) (result i32) + (call_indirect (type $unop) (local.get 1) (local.get 0))) + (func (export "bump") (result i32) + (global.set $counter (i32.add (global.get $counter) (i32.const 1))) + (global.get $counter)) + (func (export "mul64") (param i64 i64) (result i64) (i64.mul (local.get 0) (local.get 1))) + (func (export "divmod") (param i32 i32) (result i32 i32) + (i32.div_s (local.get 0) (local.get 1)) (i32.rem_s (local.get 0) (local.get 1))) + (func (export "narrow") (param f32) (result f32) (local.get 0)) + (func (export "strlen") (param $p i32) (result i32) (local $n i32) + (block $done (loop $next + (br_if $done (i32.eqz (i32.load8_u (i32.add (local.get $p) (local.get $n))))) + (local.set $n (i32.add (local.get $n) (i32.const 1))) + (br $next))) + (local.get $n)) + (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) + (func (export "pages") (result i32) (memory.size)) + (func (export "callFail") (param i32) (result i32) (i32.add (call $fail (local.get 0)) (i32.const 1))) + (func (export "trap") (param i32) (result i32) + (block $b6 (block $b5 (block $b4 (block $b3 (block $b2 (block $b1 (block $b0 + (br_table $b0 $b1 $b2 $b3 $b4 $b5 $b6 (local.get 0))) + unreachable) + (return (i32.div_u (i32.const 1) (i32.const 0)))) + (return (i32.load (i32.const 0x7fffffff)))) + (return (call_indirect (type $binop) (i32.const 1) (i32.const 2) (i32.const 0)))) + (return (call_indirect (type $unop) (i32.const 1) (i32.const 7)))) + (return (i32.trunc_f64_s (f64.const nan)))) + (i32.div_s (i32.const 0x80000000) (i32.const -1))) + (func $rec (export "recurse") (param i32) (result i32) + (i32.add (call $rec (i32.add (local.get 0) (i32.const 1))) (i32.const 1))) + (func (export "fill") (param i32 i32 i32) (memory.fill (local.get 0) (local.get 1) (local.get 2))) + (func (export "copy") (param i32 i32 i32) (memory.copy (local.get 0) (local.get 1) (local.get 2))) +) diff --git a/tests/wasm-spec/corpus/dylink-ext.wat b/tests/wasm-spec/corpus/dylink-ext.wat new file mode 100644 index 000000000..87023c646 --- /dev/null +++ b/tests/wasm-spec/corpus/dylink-ext.wat @@ -0,0 +1,42 @@ +;; tests/corpus/webassembly-dylink: an extension shaped like quickjs-wasi's +;; WebAssembly shared libraries (wasi-sdk -shared): it shares the main +;; module's memory and table, places its data and functions at the +;; __memory_base and __table_base the loader assigns, reaches the main +;; module through GOT.mem and GOT.func globals and env imports, and leaves +;; one symbol unresolved and one resolved by its own export. The program's +;; base64 copy appends the dylink.0 section: memory size 48 (alignment 2^2), +;; table size 2, needed "libc.so". +(module + (type $i_i (func (param i32) (result i32))) + (import "env" "memory" (memory 1)) + (import "env" "__indirect_function_table" (table 0 funcref)) + (import "env" "__stack_pointer" (global $sp (mut i32))) + (import "env" "__memory_base" (global $mb i32)) + (import "env" "__table_base" (global $tb i32)) + (import "GOT.mem" "counter" (global $got_counter (mut i32))) + (import "GOT.func" "helper" (global $got_helper (mut i32))) + (import "env" "js_register" (func $js_register (param i32 i32) (result i32))) + (import "env" "missing_symbol" (func $missing (param i32) (result i32))) + (import "env" "self_symbol" (func $self (param i32) (result i32))) + (data (global.get $mb) "name=demo\0aversion=1.2\00") + (elem (global.get $tb) $triple $via_helper) + (func $triple (type $i_i) (i32.mul (local.get 0) (i32.const 3))) + (func $via_helper (type $i_i) + (call_indirect (type $i_i) (local.get 0) (global.get $got_helper))) + (func (export "__wasm_apply_data_relocs") + (global.set $got_counter (i32.add (global.get $mb) (i32.const 32)))) + (func (export "__wasm_call_ctors") + (i32.store (global.get $got_counter) (i32.const 5))) + (func (export "qjs_ext_demo_init") (param i32 i32) (result i32) + (drop (call $js_register (local.get 0) (global.get $tb))) + (drop (call $js_register (local.get 0) (i32.add (global.get $tb) (i32.const 1)))) + (i32.sub (local.get 1) (i32.const 200))) + (func (export "qjs_ext_demo_versions") (result i32) (global.get $mb)) + (func (export "self_symbol") (param i32) (result i32) (i32.add (local.get 0) (i32.const 1))) + (func (export "use_self") (param i32) (result i32) (call $self (local.get 0))) + (func (export "use_missing") (param i32) (result i32) (call $missing (local.get 0))) + (func (export "stack_pointer") (result i32) (global.get $sp)) + (func (export "bump_counter") (result i32) + (i32.store (global.get $got_counter) (i32.add (i32.load (global.get $got_counter)) (i32.const 1))) + (i32.load (global.get $got_counter))) +) diff --git a/tests/wasm-spec/corpus/dylink-main.wat b/tests/wasm-spec/corpus/dylink-main.wat new file mode 100644 index 000000000..0cba26bb3 --- /dev/null +++ b/tests/wasm-spec/corpus/dylink-main.wat @@ -0,0 +1,33 @@ +;; tests/corpus/webassembly-dylink: the main module of a dynamically linked +;; program, shaped like quickjs-wasi's quickjs.wasm: it exports its memory, +;; its function table, __stack_pointer, malloc and the entry points an +;; extension's init function calls. +(module + (type $i_i (func (param i32) (result i32))) + (memory (export "memory") 2) + (table (export "__indirect_function_table") 4 funcref) + (global $sp (export "__stack_pointer") (mut i32) (i32.const 65536)) + (global $heap (mut i32) (i32.const 1024)) + (global $registered (mut i32) (i32.const 0)) + (func (export "malloc") (param i32) (result i32) + (local i32) + (local.set 1 (global.get $heap)) + (global.set $heap + (i32.and (i32.add (i32.add (global.get $heap) (local.get 0)) (i32.const 15)) (i32.const -16))) + (local.get 1)) + (func (export "qjs_get_context_ptr") (result i32) (i32.const 100)) + (func (export "qjs_get_runtime_ptr") (result i32) (i32.const 200)) + ;; js_register(ctx, tableIndex): remembers a function an extension adds. + (func (export "js_register") (param i32 i32) (result i32) + (i32.store + (i32.add (i32.const 512) (i32.mul (global.get $registered) (i32.const 4))) + (local.get 1)) + (global.set $registered (i32.add (global.get $registered) (i32.const 1))) + (global.get $registered)) + ;; js_call(n, x): calls the n-th registered function through the table. + (func (export "js_call") (param i32 i32) (result i32) + (call_indirect (type $i_i) + (local.get 1) + (i32.load (i32.add (i32.const 512) (i32.mul (local.get 0) (i32.const 4)))))) + (func (export "helper") (param i32) (result i32) (i32.add (local.get 0) (i32.const 1000))) +) diff --git a/tests/wasm-spec/corpus/externref-lifetimes.wat b/tests/wasm-spec/corpus/externref-lifetimes.wat new file mode 100644 index 000000000..ce7d9769e --- /dev/null +++ b/tests/wasm-spec/corpus/externref-lifetimes.wat @@ -0,0 +1,24 @@ +(module + (import "env" "make" (func $make (result externref))) + (import "env" "clear" (func $clear)) + (global $g (export "slot") (mut externref) (ref.null extern)) + (table $t (export "cells") 1 externref) + ;; An import's result, returned through an export. + (func (export "pass") (result externref) + (call $make)) + ;; A global's reference, read into a local, then replaced by the guest. + (func (export "takeGlobal") (result externref) (local $x externref) + (local.set $x (global.get $g)) + (global.set $g (ref.null extern)) + (local.get $x)) + ;; A table's reference, read into a local, then replaced by the guest. + (func (export "takeTable") (result externref) (local $x externref) + (local.set $x (table.get $t (i32.const 0))) + (table.set $t (i32.const 0) (ref.null extern)) + (local.get $x)) + ;; A global's reference, read into a local, then replaced by JavaScript + ;; during an import call. + (func (export "takeAcrossCall") (result externref) (local $x externref) + (local.set $x (global.get $g)) + (call $clear) + (local.get $x))) diff --git a/tests/wasm-spec/corpus/jsapi-own.wat b/tests/wasm-spec/corpus/jsapi-own.wat new file mode 100644 index 000000000..be2795793 --- /dev/null +++ b/tests/wasm-spec/corpus/jsapi-own.wat @@ -0,0 +1,11 @@ +;; tests/corpus/webassembly-jsapi.mjs: a module that defines and exports a +;; memory, a table and globals, exported twice each, for identity and for +;; importing what one instance exports into another. +(module + (memory (export "mem") (export "mem2") 1 4) + (table (export "tab") 2 8 funcref) + (global (export "g") (export "g2") (mut i32) (i32.const 7)) + (global (export "fixed") f64 (f64.const 1.5)) + (func (export "id") (param i32) (result i32) (local.get 0)) + (elem (i32.const 1) 0) +) diff --git a/tests/wasm-spec/corpus/jsapi.wat b/tests/wasm-spec/corpus/jsapi.wat new file mode 100644 index 000000000..63e2d4241 --- /dev/null +++ b/tests/wasm-spec/corpus/jsapi.wat @@ -0,0 +1,37 @@ +;; tests/corpus/webassembly-jsapi.mjs: imports of every kind, filled by the +;; Memory, Table and Global constructors. The program's base64 copy also +;; carries custom sections ("dylink.0" [1 2 3], "meta" "hello", "dylink.0" +;; [4 5] and "" []), appended after wat2wasm. +(module + (import "env" "log" (func $log (param i32))) + (import "env" "memory" (memory 1 4)) + (import "env" "table" (table 2 8 funcref)) + (import "env" "counter" (global $counter (mut i32))) + (import "env" "scale" (global $scale f64)) + (import "env" "big" (global $big (mut i64))) + (import "env" "ref" (global $ref (mut externref))) + (type $i_i (func (param i32) (result i32))) + (func $double (type $i_i) (i32.mul (local.get 0) (i32.const 2))) + (func $square (type $i_i) (i32.mul (local.get 0) (local.get 0))) + (elem (i32.const 0) $double $square) + (func (export "bump") (result i32) + (global.set $counter (i32.add (global.get $counter) (i32.const 1))) + (global.get $counter)) + (func (export "scaled") (param f64) (result f64) (f64.mul (local.get 0) (global.get $scale))) + (func (export "bigAdd") (param i64) (global.set $big (i64.add (global.get $big) (local.get 0)))) + (func (export "getRef") (result externref) (global.get $ref)) + (func (export "setRef") (param externref) (global.set $ref (local.get 0))) + (func (export "apply") (param i32 i32) (result i32) + (call_indirect (type $i_i) (local.get 1) (local.get 0))) + (func (export "store") (param i32 i32) (i32.store8 (local.get 0) (local.get 1))) + (func (export "load") (param i32) (result i32) (i32.load8_u (local.get 0))) + (func (export "size") (result i32) (memory.size)) + (func (export "grow") (param i32) (result i32) (memory.grow (local.get 0))) + (func (export "tableSize") (result i32) (table.size 0)) + (func (export "say") (param i32) (call $log (local.get 0))) + (export "memory" (memory 0)) + (export "table" (table 0)) + (export "counter" (global $counter)) + (export "alsoCounter" (global $counter)) + (export "double" (func $double)) +) diff --git a/tests/wasm-spec/corpus/lifetimes.wat b/tests/wasm-spec/corpus/lifetimes.wat new file mode 100644 index 000000000..e8569dce9 --- /dev/null +++ b/tests/wasm-spec/corpus/lifetimes.wat @@ -0,0 +1,23 @@ +(module + (import "env" "make" (func $make (result externref))) + (table $slots 1 externref) + (global $slot (mut externref) (ref.null extern)) + (memory 1) + (func (export "put") (param externref) + (table.set $slots (i32.const 0) (local.get 0))) + (func (export "take") (result externref) (local externref) + (local.set 0 (table.get $slots (i32.const 0))) + (table.set $slots (i32.const 0) (ref.null extern)) + (local.get 0)) + (func (export "putg") (param externref) + (global.set $slot (local.get 0))) + (func (export "takeg") (result externref) + (global.get $slot) + (global.set $slot (ref.null extern))) + (func (export "fresh") (result externref) + (call $make)) + ;; i32.load with the memory index written out (memarg flags 0x42, memory + ;; 0, offset 8). + (func (export "load") (result i32) + (i32.load 0 offset=8 (i32.const 0))) + (data (i32.const 8) "abcd")) diff --git a/tests/wasm-spec/corpus/linked.wat b/tests/wasm-spec/corpus/linked.wat new file mode 100644 index 000000000..28834b006 --- /dev/null +++ b/tests/wasm-spec/corpus/linked.wat @@ -0,0 +1,12 @@ +(module + (import "api" "memory" (memory 1)) + (import "api" "table" (table 2 funcref)) + (import "api" "counter" (global $c (mut i32))) + (import "api" "add" (func $add (param i32 i32) (result i32))) + (func (export "read") (param i32) (result i32) (i32.load8_u (local.get 0))) + (func (export "useTable") (param i32 i32) (result i32) + (call_indirect (param i32) (result i32) (local.get 1) (local.get 0))) + (func (export "setCounter") (param i32) (global.set $c (local.get 0))) + (func (export "add3") (param i32 i32 i32) (result i32) + (call $add (call $add (local.get 0) (local.get 1)) (local.get 2))) +) diff --git a/tests/wasm-spec/corpus/refs.wat b/tests/wasm-spec/corpus/refs.wat new file mode 100644 index 000000000..52516fa30 --- /dev/null +++ b/tests/wasm-spec/corpus/refs.wat @@ -0,0 +1,10 @@ +(module + (table $t (export "refs") 1 10 externref) + (global (export "slot") (mut externref) (ref.null extern)) + (func (export "keep") (param externref) (result externref) (local.get 0)) + (func (export "store") (param i32 externref) (table.set $t (local.get 0) (local.get 1))) + (func (export "load") (param i32) (result externref) (table.get $t (local.get 0))) + (func (export "isNull") (param externref) (result i32) (ref.is_null (local.get 0))) + (func (export "grow") (param externref i32) (result i32) (table.grow $t (local.get 0) (local.get 1))) + (func (export "size") (result i32) (table.size $t)) +) diff --git a/tests/wasm-spec/gen.mjs b/tests/wasm-spec/gen.mjs new file mode 100644 index 000000000..2b4e5c9fb --- /dev/null +++ b/tests/wasm-spec/gen.mjs @@ -0,0 +1,273 @@ +/* Regenerates the executed WebAssembly spec-test corpus programs + * (tests/corpus/webassembly-spec-/data.mjs): the binary modules and + * the commands of selected .wast files of the WebAssembly 2.0 core suite, + * which runner.mjs instantiates and invokes under Node and as a compiled + * binary. The differential harness compares the two outputs, so V8 is the + * oracle; the modules are inline base64 literals, which the build + * discovers and translates. + * + * node tests/wasm-spec/gen.mjs --wast2json /bin/wast2json \ + * --spec /test/core + * + * Left out: modules given as text, modules the decoder does not accept + * within the translatable feature level and every command that targets + * them, and commands with v128 values. The runner's spectest module + * provides the harness's memory and table (new WebAssembly.Memory and + * Table), so modules that import them run. Also left out, because they test NaN payloads, which neither + * side preserves the way the other does: actions whose f32 or f64 + * arguments are NaNs other than the canonical quiet NaN (V8 and scriptc + * carry NaN payloads through JavaScript numbers differently), and + * float_exprs.wast's actions that pass a signaling NaN's bit pattern to + * arithmetic that must quiet it (LLVM may fold `x - 0.0` to `x`, leaving + * the payload; WebAssembly requires a quiet NaN). Files with more than + * MAX_ACTIONS actions keep an evenly spaced + * sample of them (every module and register command stays), which keeps + * the generated programs small. + * + * Commands are compact tuples (runner.mjs reads them): + * ["file", name] + * ["module", line, moduleIndex, name?] instantiate, make current + * ["instantiate", line, moduleIndex] assert_un{linkable,instantiable} + * ["register", as, name?] + * ["invoke" | "get", line, name | 0, field, args] args: "type:value" + * The suite's tests are Apache-2.0 licensed (WebAssembly Community + * Group). */ +import { execFileSync } from "node:child_process"; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { parseArgs } from "node:util"; +import { analyzeWasm } from "../../packages/compiler/dist/wasm/binary.js"; + +export const GROUPS = { + int: ["i32", "i64", "int_exprs", "int_literals", "conversions", "traps"], + float: [ + "f32", + "f64", + "f32_cmp", + "f64_cmp", + "f32_bitwise", + "f64_bitwise", + "float_exprs", + "float_literals", + "float_misc", + ], + control: [ + "block", + "br", + "br_if", + "br_table", + "call", + "call_indirect", + "fac", + "forward", + "func", + "func_ptrs", + "if", + "labels", + "loop", + "local_get", + "local_set", + "local_tee", + "nop", + "return", + "select", + "stack", + "switch", + "unreachable", + "unwind", + "left-to-right", + "global", + "start", + ], + memory: [ + "address", + "align", + "endianness", + "float_memory", + "load", + "store", + "memory", + "memory_grow", + "memory_redundancy", + "memory_size", + "memory_trap", + "bulk", + "memory_copy", + "memory_fill", + "memory_init", + "data", + ], + table: [ + "table_copy", + "table_fill", + "table_get", + "table_grow", + "table_init", + "table_set", + "table_size", + "elem", + "ref_func", + "ref_is_null", + "ref_null", + "exports", + "linking", + ], + imports: ["imports", "table", "names"], +}; + +const MAX_ACTIONS = 400; + +const { values } = parseArgs({ + options: { wast2json: { type: "string" }, spec: { type: "string" } }, +}); +const repo = join(dirname(new URL(import.meta.url).pathname), "../.."); + +// Writes control and format characters (names.wast has several) as escapes, so the +// generated file holds the same strings without invisible characters in its source. +function escapeInvisible(literal) { + return literal.replace(/[\u007f-\u009f\u2028\u2029\p{Cf}]/gu, (c) => `\\u{${c.codePointAt(0).toString(16)}}`); +} + +function value(v) { + if (v.type === "v128") return null; + return `${v.type}:${v.value}`; +} + +/** Whether a "type:value" argument is a NaN other than the canonical + * quiet one. */ +function oddNaN(arg) { + const [type, text] = arg.split(":"); + if (type === "f32") { + const bits = Number(text); + return (bits & 0x7f800000) === 0x7f800000 && (bits & 0x7fffff) !== 0 && (bits & 0x7fffffff) !== 0x7fc00000; + } + if (type === "f64") { + const bits = BigInt(text); + const exp = 0x7ff0000000000000n; + const mantissa = bits & 0xfffffffffffffn; + return (bits & exp) === exp && mantissa !== 0n && (bits & 0x7fffffffffffffffn) !== 0x7ff8000000000000n; + } + return false; +} + +/** Whether a "type:value" integer argument is the bit pattern of a + * signaling NaN. */ +function signalingPattern(arg) { + const [type, text] = arg.split(":"); + if (type === "i32") { + const bits = Number(text) >>> 0; + return (bits & 0x7fc00000) === 0x7f800000 && (bits & 0x3fffff) !== 0; + } + if (type === "i64") { + const bits = BigInt.asUintN(64, BigInt(text)); + return (bits & 0x7ff8000000000000n) === 0x7ff0000000000000n && (bits & 0x7ffffffffffffn) !== 0n; + } + return false; +} + +/** Keeps every structural command and an evenly spaced sample of at most + * MAX_ACTIONS actions. */ +function sample(commands) { + const actions = commands.filter((c) => c[0] === "invoke" || c[0] === "get").length; + if (actions <= MAX_ACTIONS) return commands; + const step = actions / MAX_ACTIONS; + let seen = 0; + let next = 0; + return commands.filter((c) => { + if (c[0] !== "invoke" && c[0] !== "get") return true; + const keep = seen >= next; + seen++; + if (keep) next += step; + return keep; + }); +} + +for (const [group, names] of Object.entries(GROUPS)) { + const modules = []; + const moduleIndex = new Map(); + const commands = []; + for (const name of names) { + const dir = mkdtempSync(join(tmpdir(), "wasm-spec-")); + try { + execFileSync(values.wast2json, [join(values.spec, `${name}.wast`), "-o", join(dir, "x.json")], { + stdio: "pipe", + }); + const json = JSON.parse(readFileSync(join(dir, "x.json"), "utf8")); + const fileCommands = [["file", `${name}.wast`]]; + // Module names that refer to a module left out: their commands go too. + const skipped = new Set(); + let current = null; + let currentSkipped = false; + for (const c of json.commands) { + const where = c.line; + if (c.type === "module" || c.type === "assert_uninstantiable" || c.type === "assert_unlinkable") { + if (c.module_type === "text" || !c.filename?.endsWith(".wasm")) { + if (c.type === "module") { + currentSkipped = true; + if (c.name) skipped.add(c.name); + } + continue; + } + const bytes = readFileSync(join(dir, c.filename)); + const summary = analyzeWasm(new Uint8Array(bytes)); + if (summary.status !== "valid") { + if (c.type === "module") { + currentSkipped = true; + if (c.name) skipped.add(c.name); + } + continue; + } + const b64 = bytes.toString("base64"); + let index = moduleIndex.get(b64); + if (index === undefined) { + index = modules.length; + modules.push(b64); + moduleIndex.set(b64, index); + } + if (c.type === "module") { + current = c.name ?? null; + currentSkipped = false; + fileCommands.push(["module", where, index, ...(c.name ? [c.name] : [])]); + } else { + fileCommands.push(["instantiate", where, index]); + } + continue; + } + if (c.type === "register") { + const target = c.name ?? current; + if ((c.name ? skipped.has(c.name) : currentSkipped)) continue; + fileCommands.push(["register", c.as, ...(target ? [target] : [])]); + continue; + } + if (!c.action) continue; + const a = c.action; + const target = a.module ?? null; + if (target ? skipped.has(target) : currentSkipped) continue; + const args = (a.args ?? []).map(value); + const expected = (c.expected ?? []).map(value); + if (args.includes(null) || expected.includes(null)) continue; + if (args.some(oddNaN)) continue; + if (name === "float_exprs" && args.some(signalingPattern)) continue; + fileCommands.push([a.type === "get" ? "get" : "invoke", where, target ?? 0, a.field, args]); + } + commands.push(...sample(fileCommands)); + } finally { + rmSync(dir, { recursive: true, force: true }); + } + } + const out = join(repo, `tests/corpus/webassembly-spec-${group}`); + mkdirSync(out, { recursive: true }); + const header = `/* Generated by tests/wasm-spec/gen.mjs from the WebAssembly 2.0 core test + * suite (WebAssembly/spec wg-2.0, Apache-2.0) with wabt 1.0.37 wast2json: + * ${names.map((n) => `${n}.wast`).join(", ")}. Do not edit. */\n`; + const body = + `export const modules = [\n${modules.map((m) => ` "${m}",`).join("\n")}\n];\n` + + `export const script = ${escapeInvisible(JSON.stringify(JSON.stringify(commands)))};\n`; + writeFileSync(join(out, "data.mjs"), header + body); + writeFileSync( + join(out, "main.mjs"), + `// Executes WebAssembly spec tests (${names.length} files); see tests/wasm-spec/runner.mjs.\nimport { run } from "../../wasm-spec/runner.mjs";\nimport { modules, script } from "./data.mjs";\n\nrun(modules, script);\n`, + ); + console.log(`${group}: ${modules.length} modules, ${commands.length} commands`); +} diff --git a/tests/wasm-spec/runner.mjs b/tests/wasm-spec/runner.mjs new file mode 100644 index 000000000..25e8ed393 --- /dev/null +++ b/tests/wasm-spec/runner.mjs @@ -0,0 +1,148 @@ +/* Executes WebAssembly spec-test commands (generated by gen.mjs) and prints + * one line per command. The webassembly-spec-* corpus programs call it; + * the differential harness runs them under Node and as compiled binaries + * and compares the output, so V8 decides every expected value, trap and + * error. Module bytes are inline base64 literals in each program's + * data.mjs, which the build translates. */ + +/** The spec harness's `spectest` module (the reference interpreter's JS + * harness), with its memory and table. */ +function spectestImports(log) { + return { + print() { + log("print"); + }, + print_i32(a) { + log(`print_i32 ${a}`); + }, + print_i64(a) { + log(`print_i64 ${a}`); + }, + print_f32(a) { + log(`print_f32 ${show(a)}`); + }, + print_f64(a) { + log(`print_f64 ${show(a)}`); + }, + print_i32_f32(a, b) { + log(`print_i32_f32 ${a} ${show(b)}`); + }, + print_f64_f64(a, b) { + log(`print_f64_f64 ${show(a)} ${show(b)}`); + }, + global_i32: 666, + global_i64: 666n, + global_f32: 666.6, + global_f64: 666.6, + table: new WebAssembly.Table({ initial: 10, maximum: 20, element: "anyfunc" }), + memory: new WebAssembly.Memory({ initial: 1, maximum: 2 }), + }; +} + +function show(v) { + if (typeof v === "bigint") return `${v}n`; + if (typeof v === "number") return Object.is(v, -0) ? "-0" : String(v); + if (v === null) return "null"; + if (v === undefined) return "undefined"; + if (typeof v === "function") return "function"; + if (Array.isArray(v)) return `[${v.map(show).join(",")}]`; + if (typeof v === "object" && typeof v.ref === "number") return `ref ${v.ref}`; + return typeof v; +} + +const refs = new Map(); + +function decode(arg) { + const colon = arg.indexOf(":"); + const type = arg.slice(0, colon); + const text = arg.slice(colon + 1); + if (type === "i32") return Number(text) | 0; + if (type === "i64") return BigInt.asIntN(64, BigInt(text)); + if (type === "f32") { + const view = new DataView(new ArrayBuffer(4)); + view.setUint32(0, Number(text)); + return view.getFloat32(0); + } + if (type === "f64") { + const view = new DataView(new ArrayBuffer(8)); + view.setBigUint64(0, BigInt(text)); + return view.getFloat64(0); + } + if (text === "null") return null; + const n = Number(text); + let ref = refs.get(n); + if (ref === undefined) { + ref = { ref: n }; + refs.set(n, ref); + } + return ref; +} + +function failure(e) { + return e instanceof Error ? `${e.name}: ${e.message}` : `threw ${show(e)}`; +} + +export function run(modules, script) { + const commands = JSON.parse(script); + const lines = []; + const log = (line) => { + lines.push(line); + }; + const imports = { spectest: spectestImports(log) }; + const named = new Map(); + let current = null; + const instantiate = (index) => + new WebAssembly.Instance(new WebAssembly.Module(Buffer.from(modules[index], "base64")), imports); + let file = ""; + for (const c of commands) { + const op = c[0]; + if (op === "file") { + file = c[1]; + log(`# ${file}`); + continue; + } + if (op === "register") { + const instance = c.length > 2 ? named.get(c[2]) : current; + if (instance) imports[c[1]] = instance.exports; + continue; + } + const at = `${file}:${c[1]}`; + if (op === "module") { + try { + current = instantiate(c[2]); + if (c.length > 3) named.set(c[3], current); + log(`${at} module`); + } catch (e) { + current = null; + log(`${at} module ${failure(e)}`); + } + continue; + } + if (op === "instantiate") { + try { + instantiate(c[2]); + log(`${at} instantiated`); + } catch (e) { + log(`${at} ${e instanceof Error ? e.name : "threw"}`); + } + continue; + } + const instance = c[2] !== 0 ? named.get(c[2]) : current; + if (!instance) { + log(`${at} no instance`); + continue; + } + try { + const target = instance.exports[c[3]]; + if (op === "get") { + log(`${at} ${show(target.value)}`); + continue; + } + const args = c[4].map(decode); + log(`${at} ${show(target(...args))}`); + } catch (e) { + log(`${at} ${failure(e)}`); + } + } + console.log(lines.join("\n")); +} From c85f2380fc65686f5af4f5ef3ebb2872ed06ace1 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 20 Oct 2026 18:04:46 -0500 Subject: [PATCH 8/8] Merge main --- benchmarks/runtime/README.md | 1 + benchmarks/runtime/workloads.json | 7 ++ benchmarks/runtime/workloads/unicode-lexer.ts | 116 ++++++++++++++++++ packages/compiler/src/backend/llvm/dyn.ts | 43 +++++++ .../compiler/src/backend/llvm/emit-module.ts | 11 +- .../src/backend/llvm/expr-primitives.test.ts | 41 +++++++ .../compiler/src/backend/llvm/string-reads.ts | 8 +- .../src/frontend/lowering/builtins/process.ts | 2 +- packages/runtime/src/scr_string.c | 7 +- packages/runtime/src/scr_string_methods.h | 3 +- tests/harness/dyncheck.test.ts | 40 ++++++ 11 files changed, 266 insertions(+), 13 deletions(-) create mode 100644 benchmarks/runtime/workloads/unicode-lexer.ts diff --git a/benchmarks/runtime/README.md b/benchmarks/runtime/README.md index f8552b003..7ab829bd5 100644 --- a/benchmarks/runtime/README.md +++ b/benchmarks/runtime/README.md @@ -30,6 +30,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea | `validate-errors` | cpu | throw/catch on a fraction of inputs, small call chains | | `csv-numbers` | cpu | `split`, `Number`, `parseInt`, `toFixed` | | `log-lines` | cpu | 200k immediate `console.log`/`process.stdout.write` lines into the runner's pipe | +| `unicode-lexer` | cpu | a hand-written lexer over a 2.7M-unit bundled module whose comments and strings are in several languages: `charCodeAt` scans, line starts | | `log-summary`, `inventory-report` | cpu | the multi-module applications from `benchmarks/builds`, on 300k-line inputs | | `cli-config` | startup | a `deploy-plan` CLI: read a ~110 KB JSON manifest, validate, interpolate env values, order 80 services into dependency waves, print a summary | | `http-api` | server | a `node:http` JSON API: routing, query parsing, a seeded catalog, per-cart state, typed JSON bodies, `ETag`/`If-None-Match`, auth and request-id headers | diff --git a/benchmarks/runtime/workloads.json b/benchmarks/runtime/workloads.json index 9a3f1bc2f..80beefab4 100644 --- a/benchmarks/runtime/workloads.json +++ b/benchmarks/runtime/workloads.json @@ -231,6 +231,13 @@ "4" ] }, + { + "name": "unicode-lexer", + "entry": "workloads/unicode-lexer.ts", + "args": [ + "1" + ] + }, { "name": "log-summary", "entry": "../builds/log-summary/main.ts", diff --git a/benchmarks/runtime/workloads/unicode-lexer.ts b/benchmarks/runtime/workloads/unicode-lexer.ts new file mode 100644 index 000000000..21638bb86 --- /dev/null +++ b/benchmarks/runtime/workloads/unicode-lexer.ts @@ -0,0 +1,116 @@ +// A lexer over a large bundled module whose comments and strings are in +// several languages: line starts, tokens by kind and comment text, read +// with charCodeAt as hand-written scanners do. Each pass relexes the whole +// text, as a bundler or an editor does after a change. +const comments = [ + "// Berechnet die Größe des Puffers für übergroße Einträge", + "// Calcule la taille maximale avant la prochaine itération", + "// バッファのサイズを計算します", + "/* 计算缓冲区大小,然后返回结果 */", + "// Returns the cached value (fast path) — see the note above", + "/** Résumé: the état of the café's ñandú registry */", +]; +const strings = ['"Grüße"', '"naïve façade"', '"東京"', '"ok ✓"', '"plain ascii"', "'mañana'"]; +const names = ["buffer", "größe", "index", "resultat", "count", "value", "entry", "table"]; + +let seed = 11; +function random(): number { + seed = (Math.imul(seed, 1103515245) + 12345) >>> 0; + return seed / 4294967296; +} +function pick(list: readonly string[]): string { + return list[Math.floor(random() * list.length)]!; +} + +function makeSource(lines: number): string { + const out: string[] = []; + for (let i = 0; i < lines; i++) { + const r = random(); + if (r < 0.2) out.push(pick(comments)); + else if (r < 0.6) out.push(` const ${pick(names)}${i} = ${pick(names)}(${Math.floor(random() * 1000)}, ${pick(strings)});`); + else if (r < 0.85) out.push(` if (${pick(names)} >= ${i % 97}) return ${pick(names)}.${pick(names)} + 1;`); + else out.push(`function ${pick(names)}${i}(a, b) { return a * b - ${i}; }`); + } + return out.join("\n"); +} + +interface Counts { + lines: number; + identifiers: number; + numbers: number; + strings: number; + comments: number; + commentChars: number; + punctuation: number; +} + +function isIdentifierStart(ch: number): boolean { + return (ch >= 97 && ch <= 122) || (ch >= 65 && ch <= 90) || ch === 95 || ch === 36 || ch > 127; +} + +function isIdentifierPart(ch: number): boolean { + return isIdentifierStart(ch) || (ch >= 48 && ch <= 57); +} + +function lex(text: string): Counts { + const counts: Counts = { lines: 1, identifiers: 0, numbers: 0, strings: 0, comments: 0, commentChars: 0, punctuation: 0 }; + let pos = 0; + const end = text.length; + while (pos < end) { + const ch = text.charCodeAt(pos); + if (ch === 10) { + counts.lines++; + pos++; + } else if (ch === 32 || ch === 9 || ch === 13) { + pos++; + } else if (ch === 47 && text.charCodeAt(pos + 1) === 47) { + const start = pos; + while (pos < end && text.charCodeAt(pos) !== 10) pos++; + counts.comments++; + counts.commentChars += pos - start; + } else if (ch === 47 && text.charCodeAt(pos + 1) === 42) { + const start = pos; + pos += 2; + while (pos < end && !(text.charCodeAt(pos) === 42 && text.charCodeAt(pos + 1) === 47)) { + if (text.charCodeAt(pos) === 10) counts.lines++; + pos++; + } + pos += 2; + counts.comments++; + counts.commentChars += pos - start; + } else if (ch === 34 || ch === 39) { + pos++; + while (pos < end && text.charCodeAt(pos) !== ch) pos++; + pos++; + counts.strings++; + } else if (ch >= 48 && ch <= 57) { + while (pos < end && text.charCodeAt(pos) >= 48 && text.charCodeAt(pos) <= 57) pos++; + counts.numbers++; + } else if (isIdentifierStart(ch)) { + while (pos < end && isIdentifierPart(text.charCodeAt(pos))) pos++; + counts.identifiers++; + } else { + counts.punctuation++; + pos++; + } + } + return counts; +} + +function lineStarts(text: string): number[] { + const starts = [0]; + for (let pos = 0; pos < text.length; pos++) if (text.charCodeAt(pos) === 10) starts.push(pos + 1); + return starts; +} + +const scale = Number(process.argv[2] ?? "1"); +const source = makeSource(60000); +const passes = Math.round(6 * scale); +let last = lex(source); +let total = last.identifiers + lineStarts(source).length; +for (let i = 1; i < passes; i++) { + last = lex(source); + total += last.identifiers + lineStarts(source).length; +} +console.log("units", source.length, "passes", passes, "total", total); +console.log(JSON.stringify(last)); diff --git a/packages/compiler/src/backend/llvm/dyn.ts b/packages/compiler/src/backend/llvm/dyn.ts index 731385825..9769bdb31 100644 --- a/packages/compiler/src/backend/llvm/dyn.ts +++ b/packages/compiler/src/backend/llvm/dyn.ts @@ -39,6 +39,7 @@ import { isDirentShape, isDynTypedRefType, isRefCounted, + isUnitType, typeKey, } from "../../ir/ir.js"; import { dynDesc, streamTypedRefEligible } from "../../ir/analysis.js"; @@ -697,6 +698,46 @@ export class LlDyn { /** Emit the standard in-helper pending check: on a pending exception * run `cleanup` and return `dummy`. */ + /** The failure block of a union dynCheck whose value matched no arm. A + * value of the one container kind exactly one arm can take (an object + * for the union's only record arm, an array for its only array arm, + * beside unit and scalar arms) runs that arm's builder, which throws the + * deepest failing path and the reason there (`expected string at + * $.order.owner.id, got undefined`) instead of the union's own outer + * path. Other values, and an arm builder that unexpectedly succeeds, + * fall through to the union's message. Only the failure path changes. */ + private unionArmFailure(B: BlockBuilder, def: IrUnionDef, kind: string): void { + const scalar = (arm: IrType): boolean => + isUnitType(arm) || + arm.kind === "f64" || + arm.kind === "bool" || + arm.kind === "string" || + arm.kind === "bigint"; + const containers = def.arms.filter((arm) => !scalar(arm)); + const arm = containers.length === 1 ? containers[0]! : null; + const container = + arm?.kind === "array" + ? DYN_KIND.ARR + : arm?.kind === "record" && !this.host.recordsById.get(arm.shapeId)?.tuple + ? DYN_KIND.OBJ + : null; + if (!arm || container === null) return; + const host = this.host; + const deep = B.newLabel("dcu.deep"); + const outer = B.newLabel("dcu.outer"); + const same = B.tmp(); + B.line(`${same} = icmp eq i32 ${kind}, ${container}`); + B.condBr(same, deep, outer); + B.startBlock(deep); + const built = B.tmp(); + B.line(`${built} = call ptr @${this.dynCheckHelper(arm)}(ptr %d, ptr %path)`); + this.pendingBail(B, "dcu.deep", () => {}, "ptr null"); + const rc = vAdapters(host, arm); + B.line(`call void ${rc.release}(ptr ${built})`); + B.br(outer); + B.startBlock(outer); + } + private pendingBail(B: BlockBuilder, hint: string, cleanup: () => void, dummy: string): void { const p = B.tmp(); for (const line of this.host.pendingTestLines(p)) B.line(line.trimStart()); @@ -2291,6 +2332,7 @@ export class LlDyn { B.line(`call void @scr_dyn_release_v(ptr ${materialized})`); B.terminate(`ret ptr ${checked}`); B.startBlock(lFail); + this.unionArmFailure(B, def, kind); B.line(`call void @scr_dyn_check_fail(ptr %path, ptr ${want}, ptr %d)`); B.terminate(`ret ptr null`); break; @@ -2359,6 +2401,7 @@ export class LlDyn { ); B.terminate(`ret ptr ${checked}`); B.startBlock(lFail); + this.unionArmFailure(B, def, kind); B.line(`call void @scr_dyn_check_fail(ptr %path, ptr ${want}, ptr %d)`); B.terminate(`ret ptr null`); break; diff --git a/packages/compiler/src/backend/llvm/emit-module.ts b/packages/compiler/src/backend/llvm/emit-module.ts index 4e4f11828..b373613e0 100644 --- a/packages/compiler/src/backend/llvm/emit-module.ts +++ b/packages/compiler/src/backend/llvm/emit-module.ts @@ -1333,14 +1333,17 @@ export function helperDefs(emitter: LlEmitter): string[] { // of the capacity word (SCR_STR_ASCII_BIT, the low half's sign bit on // little-endian targets) makes UTF-16 units bytes. Unproven strings and // anything but an in-range index take the runtime's UTF-16 mapping. - // A scan's receiver reads inline too where runtime state is ordinary - // global storage (ScrStrScan; SCR_TL makes it thread-local otherwise). - const scanInline = emitter.mod.workers !== true && emitter.mod.lib?.threadInstances !== true; + // A scan's receiver reads inline too (ScrStrScan): from ordinary global + // storage, or, in worker executables, from the thread's static TLS block, + // one thread-pointer-relative load (see executableTls). + const scanInline = + emitter.executableTls || + (emitter.mod.workers !== true && emitter.mod.lib?.threadInstances !== true); let scanDeclared = false; for (const helper of STRING_READ_HELPERS) { if (!emitter.decls.has(helper.decl)) continue; if (scanInline && !scanDeclared) { - defs.push(stringScanDecl(emitter.sizeType), ``); + defs.push(stringScanDecl(emitter.sizeType, emitter.executableTls), ``); scanDeclared = true; } defs.push(...helper.define(emitter.sizeType, FN_ATTRS, scanInline), ``); diff --git a/packages/compiler/src/backend/llvm/expr-primitives.test.ts b/packages/compiler/src/backend/llvm/expr-primitives.test.ts index 1790854ef..eb6a88d0a 100644 --- a/packages/compiler/src/backend/llvm/expr-primitives.test.ts +++ b/packages/compiler/src/backend/llvm/expr-primitives.test.ts @@ -306,3 +306,44 @@ test("sums that only feed a bitwise conversion of int32 operands add in 32 bits" } expect(body("number_add")).not.toMatch(/ = add i32 |fcmp ord/); }); + +test("string reads of a scan receiver load it inline where emitted code can address it", () => { + const receiver: IrExpr = { kind: "varRef", localId: "text", type: STRING, loc }; + const mod = fixture(); + mod.functions = [ + { name: "__main", params: [], returnType: VOID, locals: [], body: [], loc }, + { + name: "units", + params: [{ name: "text", localId: "text", type: STRING }], + returnType: F64, + locals: [{ id: "text", name: "text", type: STRING, mutable: false }], + loc, + body: [ + { + kind: "return", + loc, + value: { kind: "strIntrinsic", method: "length", receiver, args: [], type: F64, loc }, + }, + ], + }, + ]; + const helper = (llvm: string): string => + /^define internal double @sc_str_utf16_len\([^]*?^}/m.exec(llvm)![0]; + const plain = emitLlvmModule(mod); + expect(plain).toContain("@scr_str_scan = external global"); + expect(helper(plain)).toContain("%scan.same"); + // ELF worker executables keep the receiver in the thread's static TLS block. + const elf = emitLlvmModule( + { ...mod, workers: true }, + { targetTriple: "x86_64-unknown-linux-gnu" }, + ); + expect(elf).toContain("@scr_str_scan = external thread_local(localexec) global"); + expect(helper(elf)).toContain("%scan.same"); + // Darwin worker executables leave every non-ASCII read to the runtime. + const darwin = emitLlvmModule( + { ...mod, workers: true }, + { targetTriple: "arm64-apple-macosx14.0.0" }, + ); + expect(darwin).not.toContain("@scr_str_scan"); + expect(helper(darwin)).not.toContain("%scan.same"); +}); diff --git a/packages/compiler/src/backend/llvm/string-reads.ts b/packages/compiler/src/backend/llvm/string-reads.ts index ef0702361..d7bbed272 100644 --- a/packages/compiler/src/backend/llvm/string-reads.ts +++ b/packages/compiler/src/backend/llvm/string-reads.ts @@ -13,8 +13,8 @@ * With `scan`, a string that is not proven ASCII is first compared with * the runtime's published scan receiver (ScrStrScan, scr_runtime.h): the * string an indexed scan converted to UTF-16 units, whose length and units - * then read inline. Only targets whose runtime state is not thread-local - * publish it to emitted code. + * then read inline. Where the runtime keeps it per thread, the declaration + * is thread-local. */ import { exactIndexLines, sizeToDouble } from "./common.js"; @@ -53,8 +53,8 @@ export function stringScanType(sz: string): string { return `{ ptr, ptr, ${sz} }`; } -export function stringScanDecl(sz: string): string { - return `@scr_str_scan = external global ${stringScanType(sz)}`; +export function stringScanDecl(sz: string, threadLocal: boolean): string { + return `@scr_str_scan = external ${threadLocal ? "thread_local " : ""}global ${stringScanType(sz)}`; } /** The scan receiver test of `%s`: branches to `hit` with `%scan.n` (its diff --git a/packages/compiler/src/frontend/lowering/builtins/process.ts b/packages/compiler/src/frontend/lowering/builtins/process.ts index 934abbbeb..2ca1fb1ae 100644 --- a/packages/compiler/src/frontend/lowering/builtins/process.ts +++ b/packages/compiler/src/frontend/lowering/builtins/process.ts @@ -295,7 +295,7 @@ export function isOptionalProcessVersionsRead(lowerer: Lowerer, node: ts.Node): if (!programMentionsVersions(lowerer)) return false; const symbol = lowerer.typeOf(node.expression).getSymbol(); return ( - symbol?.name === "ProcessVersions" && + symbol !== undefined && ts.symbolName(symbol) === "ProcessVersions" && symbol.declarations.some( (d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()), ) && diff --git a/packages/runtime/src/scr_string.c b/packages/runtime/src/scr_string.c index 77c020ee0..2b4752b34 100644 --- a/packages/runtime/src/scr_string.c +++ b/packages/runtime/src/scr_string.c @@ -1846,9 +1846,10 @@ static uint16_t *scr_str_units(const ScrStr *s, size_t *n) { * UTF-16 units to bytes through the cursor and checkpoints. Once its * reads would have paid for converting it (one read per 16 bytes), a * receiver up to SCR_SIDX_UNITS_MAX units keeps its code units in its - * entry and reads become loads. The array is freed with the entry, and an - * append drops it (scr_sidx_concat_append). */ -#define SCR_SIDX_UNITS_MAX ((size_t)1 << 20) + * entry and reads become loads. The bound admits source texts of several + * megabytes, which scanners read in full. The array is freed with the + * entry, and an append drops it (scr_sidx_concat_append). */ +#define SCR_SIDX_UNITS_MAX ((size_t)1 << 24) static bool scr_sidx_units(const ScrStr *s, ScrSidx *e) { if (!e->units) { if (++e->reads < 32 || e->reads * 16 < s->len || s->len > SCR_SIDX_UNITS_MAX) return false; diff --git a/packages/runtime/src/scr_string_methods.h b/packages/runtime/src/scr_string_methods.h index 6ed2e09df..1f25b8766 100644 --- a/packages/runtime/src/scr_string_methods.h +++ b/packages/runtime/src/scr_string_methods.h @@ -27,7 +27,8 @@ double scr_str_utf16_len(ScrStr *s); * holds (scr_string.c), published so emitted length and charCodeAt reads of * that string are a compare and a load. Cleared whenever its entry drops * the units (release, append, eviction); all fields zero otherwise. Emitted - * code reads it only where SCR_TL is empty. */ + * code reads it where SCR_TL is empty, and in worker executables on ELF + * targets, whose thread-locals live in the executable's static TLS block. */ typedef struct ScrStrScan { const ScrStr *s; const uint16_t *units; diff --git a/tests/harness/dyncheck.test.ts b/tests/harness/dyncheck.test.ts index 2ab323f37..6a2d4dc61 100644 --- a/tests/harness/dyncheck.test.ts +++ b/tests/harness/dyncheck.test.ts @@ -305,6 +305,46 @@ console.log("unreachable", c.host); ); }); + test("optional record member: the failure names the deepest path inside it", async () => { + // `Partial` makes every member an `X | undefined` union. A present + // object that fails X used to report only the union's own path + // ("expected object | undefined at $.order, got object"); the record + // arm's builder now reports where inside it the value fails. + const r = await compileAndRun( + "optional-record-deep", + `interface Order { + owner: { id: string; plan: { tier: string } }; + total: number; +} +interface Envelope { order: Order; tags?: string[] } +const texts = [ + '{"order":{"owner":{"id":"u1"},"total":3}}', + '{"order":{"owner":{"id":7,"plan":{"tier":"pro"}},"total":3}}', + '{"order":{"owner":{"id":"u1","plan":{"tier":"pro"}},"total":3},"tags":["a",2]}', + '{"order":5}', +]; +for (const text of texts) { + try { + const e = JSON.parse(text) as Partial; + console.log("ok", e.order?.owner.id); + } catch (err) { + if (err instanceof TypeError) console.log(err.message); + } +} +`, + ); + expect(r.exitCode).toBe(0); + expect(r.stdout).toBe( + [ + "expected object at $.order.owner.plan, got undefined", + "expected string at $.order.owner.id, got number", + "expected string at $.tags[1], got number", + "expected object | undefined at $.order, got number", + "", + ].join("\n"), + ); + }); + test("JSON null matches nothing", async () => { const r = await compileAndRun( "null-value",