diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23936e46a..4fbd624fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,6 +136,8 @@ jobs: run: pnpm generate:ts7-ast --check - name: Verify the generated IR field readers run: pnpm generate:ir-fields --check + - name: Verify the generated IR codec + run: pnpm generate:ir-codec --check native_macos: needs: [validation_scope, lanes] diff --git a/package.json b/package.json index d049a4ecb..5540010f2 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "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", + "generate:ir-codec": "node scripts/generate-ir-codec.mjs", "generate:ir-fields": "node scripts/generate-ir-fields.mjs", "test:fetch-conformance": "vitest run tests/harness/fetch-conformance.test.ts", "test:test262": "tsx tests/test262/run.ts", diff --git a/packages/compiler/src/executable/prepare.ts b/packages/compiler/src/executable/prepare.ts index 2629a65d5..614e60155 100644 --- a/packages/compiler/src/executable/prepare.ts +++ b/packages/compiler/src/executable/prepare.ts @@ -20,6 +20,7 @@ import { } from "../backend/target-diagnostics.js"; import type { LowerResult, LowerStats } from "../frontend/lowering/lowerer.js"; import type { FrontendFactory } from "../frontend/pipeline.js"; +import type { UnitStore } from "../frontend/lowering/unit-reuse.js"; import type { CompilationTiming } from "../timing.js"; import { readProjectConfig } from "../project-config.js"; import { nodeIslandPolicy } from "../node-islands/policy.js"; @@ -42,7 +43,9 @@ export interface PreparedExecutableModule { warnings: ScrDiagnostic[]; } -/** Release the parser/checker before native object generation begins. */ +/** Release the parser/checker before native object generation begins. + * `unitStore` keeps the lowered units of this build for the next one + * (unit-reuse.ts). */ export function prepareExecutableModule( entryPath: string, opts: CompileRequestOptions, @@ -52,6 +55,7 @@ export function prepareExecutableModule( timing: CompilationTiming = () => {}, lowerIslands: ReadonlyMap = new Map(), retryFences = true, + unitStore: UnitStore | null = null, ): PreparedExecutableModule | CompileFailure { // The island decision reads the detector's scan unless islands are off. const islandScan = @@ -115,6 +119,7 @@ export function prepareExecutableModule( timing, grown, retryFences, + unitStore, ); } return fail(fe.preflight); @@ -127,6 +132,7 @@ export function prepareExecutableModule( targetPlatform: buildPlatform, ...(ffi !== null ? { ffiImports: ffi.functions } : {}), ...(opts.tier !== undefined ? { tierMode: opts.tier } : {}), + ...(unitStore !== null ? { unitStore } : {}), }, (module) => validateModule(module).length === 0, ); @@ -165,6 +171,7 @@ export function prepareExecutableModule( timing, grown, retryFences, + unitStore, ); } // A failed program whose compiled packages need the runtime or a @@ -267,6 +274,7 @@ export function prepareExecutableModule( timing, islands, false, + unitStore, ); if (islanded.ok) return islanded; firstError ??= islanded.diagnostics[0] ?? null; diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index b928055eb..e04e37953 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -30,7 +30,9 @@ import { irDataTable, journalDump, journalMode, + HeldTables, lowerWaveRecorded, + type WaveReuse, mapTable, setTable, waveStats, @@ -41,6 +43,7 @@ import { type UnitEvents, type WaveHost, } from "./unit-journal.js"; +import { UnitReuse, unitReuseVerify, verifyStoredUnit, type UnitStore } from "./unit-reuse.js"; import { moduleNamespaceDynValue, moduleNamespaceObject, @@ -961,6 +964,9 @@ export interface LowerOptions { nodeIslands?: NodeIslandSet | null; /** Parser/checker services owned by the load; required for npm graph analysis. */ frontendServices?: FrontendServices | undefined; + /** Where reachable emit stores its units' records and finds an earlier + * build's (unit-reuse.ts). */ + unitStore?: UnitStore | null; /** The project's tsconfig sets `checkJs` (see isCheckedJsSourceFile). */ projectCheckJs?: boolean; /** Coverage: additionally lower the unreached remainder (bodies nothing @@ -1360,7 +1366,7 @@ function lowerToIrWithIslands( }); for (const d of extraModuleCycleDiags) reachableEmit.pushDiag(d); for (const d of ffiValidation.diagnostics) reachableEmit.pushDiag(d); - const emitted = reachableEmit.emitReachable(options.libRoots); + const emitted = reachableEmit.emitReachable(options.libRoots, options.unitStore ?? null); let { reachable } = emitted; let result = emitted.result; let resultLowerer = reachableEmit; @@ -9940,7 +9946,10 @@ export class Lowerer { * demand-driven) and lifted lambdas lower inline with their enclosing * body; both fire edges through the same hooks and are not units * themselves. */ - emitReachable(extraRoots?: readonly string[]): { reachable: Set; result: LowerResult } { + emitReachable( + extraRoots?: readonly string[], + unitStore: UnitStore | null = null, + ): { reachable: Set; result: LowerResult } { // Frontend passes below rewrite function bodies through the IR mapper // (caught-value and class dispatch); key-order notes follow the record // literals they rebuild until the witness pass has read them. @@ -9998,12 +10007,16 @@ export class Lowerer { // Every lowerable body, by emitted-function name. The names double as // retained-function keys and are deterministic by construction // (qualified declaration names). + // `source` is the declaration whose text a unit lowers and `owners` the + // declarations around it (unit-reuse.ts). const units = new Map< string, { order: number; roots: readonly ts.Node[]; lower: () => IrFunction | null; + source?: ts.Node | undefined; + owners?: readonly ts.Node[]; } >(); // Roots are collected with plain pushes: spread arguments into a rest @@ -10047,6 +10060,7 @@ export class Lowerer { order: unitOrder++, roots: functionRoots(decl), lower: () => this.lowerFunction(decl), + source: decl, }); } } @@ -10067,6 +10081,7 @@ export class Lowerer { roots: classCtorRoots(info), lower: () => info.localClass && !info.localClass.ready ? null : this.lowerClassCtor(info), + source: info.decl ?? undefined, }); // The descriptor helpers of a class that extends Array, reached // from its constructor. @@ -10075,11 +10090,13 @@ export class Lowerer { order: unitOrder++, roots: [], lower: () => arrayClassFieldsFn(this, info), + source: info.decl ?? undefined, }); units.set(`%${cName}.species`, { order: unitOrder++, roots: [], lower: () => arrayClassSpeciesFn(this, info), + source: info.decl ?? undefined, }); } for (const { mName, member } of this.classMethodMembers(info)) { @@ -10090,6 +10107,8 @@ export class Lowerer { info.localClass && !info.localClass.ready ? null : this.lowerClassMethodMember(info, member), + source: member, + owners: info.decl ? [info.decl] : [], }); } for (const prop of info.throwingSetters) { @@ -10097,6 +10116,7 @@ export class Lowerer { order: unitOrder++, roots: [], lower: () => this.throwingSetterFn(info, prop), + source: info.decl ?? undefined, }); } } @@ -10106,6 +10126,8 @@ export class Lowerer { order: unitOrder++, roots: functionRoots(entry.member), lower: () => lowerStaticMethod(this, info, name), + source: entry.member, + owners: info.decl ? [info.decl] : [], }); } } @@ -10420,17 +10442,94 @@ export class Lowerer { ), ); }; - const recorded = journalMode() === "serial" ? waveStats() : null; - const host = recorded === null ? null : this.waveHost(loweredUnits, lowerUnitBody); + // With a unit store, every unit is recorded as in the serial mode, and + // a unit applies the record an earlier build stored for it where that + // record still holds (unit-reuse.ts). + const reuse = + unitStore === null + ? null + : UnitReuse.open( + unitStore, + this.program, + parts.map((fp) => fp.sf), + { + facts: (declaration) => this.declarationFacts(declaration), + exportsOf: (specifier) => { + const symbol = this.checker.getSymbolAtLocation(specifier); + if (!symbol) return null; + return this.checker + .getExportsOfModule(symbol) + .map((exported) => ts.symbolName(exported)) + .sort() + .join("\n"); + }, + isLocal: (identifier, container) => { + const declarations = this.checker.getSymbolAtLocation(identifier)?.declarations; + if (declarations === undefined || declarations.length === 0) return false; + const sf = ts.getSourceFileOfNode(container); + return declarations.every( + (declaration) => + ts.getSourceFileOfNode(declaration) === sf && + declaration.pos >= container.pos && + declaration.end <= container.end, + ); + }, + }, + ); + const recorded = + journalMode() === "serial" || (reuse !== null && reuse.records) ? waveStats() : null; + const host = + recorded === null + ? null + : this.waveHost(loweredUnits, lowerUnitBody, (name) => { + const priority = metadataPriority.get(name)!; + return { owner: demandOwner(priority), priority }; + }); const dump = recorded === null ? null : journalDump(); + const fileOfUnit = (name: string): string | null => { + const source = units.get(name)?.source; + return source === undefined ? null : ts.getSourceFileOfNode(source).fileName; + }; + const waveReuse: WaveReuse | null = + reuse === null || !reuse.records + ? null + : { + verify: unitReuseVerify(), + stored: (name) => { + // A unit without a declared rank takes the next expression + // rank when it lowers, which applying a record would not + // advance. + const unit = units.get(name); + const file = fileOfUnit(name); + if (unit === undefined || file === null || !metadataPriority.has(name)) return null; + return reuse.candidate(name, file, unit.source ?? null, unit.owners ?? []); + }, + check: (name, stored, pending, unit) => + verifyStoredUnit(name, stored, pending, unit.record, unit.reasons), + note: (name, record, applied, files) => { + const file = fileOfUnit(name); + if (file !== null) reuse.note(name, file, record, applied, files); + }, + }; const recordLines: string[] = []; const drainUnits = (): void => { + // The waves of one drain have only checker prefetches between them, so + // they share the content their units start from (HeldTables). + const held = + host !== null && recorded !== null ? new HeldTables(host.tables, recorded) : null; while (queue.length > 0) { const wave = queue.splice(0); shuffle?.(wave); this.checker.prefetchRoots(wave.flatMap((name) => units.get(name)!.roots)); if (host !== null && recorded !== null) { - lowerWaveRecorded(host, wave, recorded, dump === null ? null : recordLines); + lowerWaveRecorded( + host, + wave, + recorded, + dump === null ? null : recordLines, + waveReuse, + held, + ); continue; } for (const name of wave) { @@ -10447,6 +10546,7 @@ export class Lowerer { } } } + held?.check(); }; drainUnits(); this.restoreGenericInstanceOrder(); @@ -10491,6 +10591,13 @@ export class Lowerer { } classDispatch.finalize(this); if (dump !== null) writeRecords(dump, recordLines); + if (reuse !== null) { + reuse.finish(); + if (process.env["SCRIPTC_TIMING"] === "1") + process.stderr.write( + `scriptc lowering ${JSON.stringify({ phase: "unit-reuse", ...reuse.stats })}\n`, + ); + } if (recorded !== null && process.env["SCRIPTC_TIMING"] === "1") process.stderr.write( `scriptc lowering ${JSON.stringify({ phase: "unit-records", ...recorded })}\n`, @@ -10539,11 +10646,57 @@ export class Lowerer { return { reachable, result: this.finishModule(functions) }; } + /** What the steps before the worklist settled for a top-level + * declaration, as text without source positions (unit-reuse.ts): a + * function's signature, a class's layout and the signatures of its + * methods and statics, and the globals of a variable statement. */ + private declarationFacts(declaration: ts.Node): string { + const positionFree = (key: string, value: unknown): unknown => + key === "loc" || key === "scope" ? undefined : value; + if (ts.isFunctionDeclaration(declaration)) { + const symbol = declSymbolOf(this, declaration); + const sig = symbol ? this.fnSigsBySymbol.get(symbol) : undefined; + return sig === undefined ? "" : JSON.stringify(sig, positionFree); + } + if (ts.isClassDeclaration(declaration)) { + const info = this.classes.get(this.classNamer(declaration)); + if (info === undefined) return ""; + const staticMethods: { name: string; params: ParamShape[]; ret: IrType }[] = []; + if (info.staticMethods) + for (const [name, method] of info.staticMethods) + staticMethods.push({ name, params: method.params, ret: method.ret }); + return JSON.stringify( + { + def: info.def, + fields: [...info.fields], + methods: [...info.methods], + statics: info.staticFields.map((field) => ({ + name: field.name, + type: field.type, + runtimeOptional: field.runtimeOptional ?? false, + globalId: field.globalId, + readonly: field.readonly, + })), + staticMethods, + }, + positionFree, + ); + } + if (ts.isVariableStatement(declaration)) { + const globals: (IrGlobal | null)[] = []; + for (const decl of declaration.declarationList.declarations.nodes) + globals.push(this.globalsByDeclNode.get(decl) ?? null); + return JSON.stringify(globals, positionFree); + } + return ""; + } + /** The lowerer's side of a recorded wave (unit-journal.ts). * `lowerUnitBody` lowers a unit as reachable emit does. */ private waveHost( loweredUnits: Map, lowerUnitBody: (name: string) => IrFunction | null, + ownerOf: (name: string) => { owner: GenericDemandOwner; priority: readonly [number, number] }, ): WaveHost { const scalars = this.journalScalars(); const flags = new Map(scalars.map((scalar) => [scalar.name, scalar])); @@ -10574,6 +10727,35 @@ export class Lowerer { return undefined; } }; + // Makes a recorded call again (resolves has answered true for it). + const replay = (event: JournalEvent): void => { + switch (event.kind) { + case "edge": + this.noteEdge(event.name); + break; + case "superConstructorEdge": + this.noteSuperConstructorEdge(event.name); + break; + case "virtualEdge": + this.noteVirtualEdge(classNamed(event.name)!, event.detail!); + break; + case "functionDemand": + this.noteGenericInstanceDemand(instanceNamed(event.name)!); + break; + case "classDemand": + this.noteGenericClassInstanceDemand(classNamed(event.name)!); + break; + case "statement": + this.countStatement(statementNode(event)!, event.detail ?? undefined); + break; + case "failedStatement": + this.countStatementFailed(statementNode(event)!, event.detail ?? undefined); + break; + case "flag": + flags.get(event.name)!.set!(); + break; + } + }; return { tables: this.journalTables(), scalars, @@ -10614,6 +10796,15 @@ export class Lowerer { return true; } }, + keep: (name, fn, events) => { + if (fn) loweredUnits.set(name, fn); + const { owner, priority } = ownerOf(name); + this.withGenericDemandOwner(owner, () => + this.shapes.withDeclaredOrderPriority(priority, () => { + for (const event of events) replay(event); + }), + ); + }, }; } diff --git a/packages/compiler/src/frontend/lowering/unit-journal.test.ts b/packages/compiler/src/frontend/lowering/unit-journal.test.ts index 3b9599d7b..55b030c2f 100644 --- a/packages/compiler/src/frontend/lowering/unit-journal.test.ts +++ b/packages/compiler/src/frontend/lowering/unit-journal.test.ts @@ -4,6 +4,7 @@ import { Lowerer } from "./lowerer.js"; import { checkedTable, contentDigest, + decodeRecordFunction, encodeUnitFunction, flagScalar, functionTable, @@ -12,7 +13,9 @@ import { recordUnit, setTable, waveStats, + type UnitRecord, type WaveHost, + type WaveReuse, } from "./unit-journal.js"; import type { IrFunction } from "../../ir/ir.js"; @@ -225,6 +228,7 @@ function host(lower: (name: string, state: HostState) => IrFunction | null): { }, statementCounts: () => ({ total: 0, failed: 0, other: 0 }), resolves: () => true, + keep: () => {}, }, }; } @@ -276,7 +280,10 @@ test("a unit's record lists its events, flags and added entries", () => { ["flag", "flag"], ]); expect(record!.tables).toEqual({ helpers: [`"key.a"\n"%h.a"`] }); - expect(JSON.parse(JSON.stringify(record))).toEqual(record); + // The function is in the binary encoding, and the rest is JSON data. + const { fn: bytes, ...rest } = record!; + expect(bytes).toBeInstanceOf(Uint8Array); + expect(JSON.parse(JSON.stringify(rest))).toEqual(rest); }); test("a unit that changes an entry it did not add has no record", () => { @@ -303,6 +310,7 @@ test("a recorded wave throws when a unit changes a held function in place", () = store: () => {}, statementCounts: () => ({ total: 0, failed: 0, other: 0 }), resolves: () => true, + keep: () => {}, }; lowerWaveRecorded(subject, ["a"], waveStats()); expect(() => lowerWaveRecorded(subject, ["a", "b"], waveStats())).toThrow( @@ -310,6 +318,47 @@ test("a recorded wave throws when a unit changes a held function in place", () = ); }); +test("a recorded wave holds the entries an applied record adds after its first lowering unit", () => { + // `first` lowers, which takes the wave's content; `a` applies a stored + // record that adds `%added`; `b` changes `%added` in place without naming + // it, which the end of the wave must see. + const functions: IrFunction[] = []; + const subject: WaveHost = { + tables: [functionTable("lifted", functions, (name) => functions.some((f) => f.name === name))], + scalars: [], + lowerRecorded: (name) => { + if (name === "b") + functions + .find((f) => f.name === "%added")! + .locals.push({ id: "x.0", name: "x", type: { kind: "f64" } } as never); + return { fn: fn(`%${name}`), poisoned: false }; + }, + store: () => {}, + statementCounts: () => ({ total: 0, failed: 0, other: 0 }), + resolves: () => true, + keep: () => {}, + }; + const stored: UnitRecord = { + name: "a", + fn: null, + poisoned: false, + events: [], + tables: { lifted: [encodeUnitFunction(fn("%added"))!] }, + reads: {}, + }; + const reuse: WaveReuse = { + stored: (name) => (name === "a" ? { record: stored, fn: () => null } : null), + verify: false, + check: () => {}, + note: () => {}, + }; + const stats = waveStats(); + expect(() => lowerWaveRecorded(subject, ["first", "a", "b"], stats, null, reuse)).toThrow( + "a unit changed lifted entry %added in place without naming it", + ); + expect(stats.applied).toBe(1); +}); + /** A wave host over one function table holding `%shared` and one checked * list, whose units change `%shared` in place and name it in their IR. */ function sharedHost(grows: (name: string) => boolean): { host: WaveHost; checked: string[] } { @@ -334,6 +383,7 @@ function sharedHost(grows: (name: string) => boolean): { host: WaveHost; checked store: () => {}, statementCounts: () => ({ total: 0, failed: 0, other: 0 }), resolves: () => true, + keep: () => {}, }, }; } @@ -352,6 +402,40 @@ test("a unit that changes a function it names in place has no record", () => { expect(stats).toMatchObject({ recorded: 0, unrecorded: 1, reasons: { "lifted:changed": 1 } }); }); +test("a record's binary function names the held functions its strings name", () => { + const functions = [fn("%shared")]; + const loc = { file: "b.ts", start: 0, end: 0 }; + const subject: WaveHost = { + tables: [functionTable("lifted", functions, (name) => functions.some((f) => f.name === name))], + scalars: [], + lowerRecorded: () => ({ + fn: { + ...fn("%a"), + body: [ + { + kind: "exprStmt", + expr: { kind: "call", callee: "%shared", args: [], type: { kind: "f64" }, loc }, + loc, + }, + ] as never, + }, + poisoned: false, + }), + store: () => {}, + statementCounts: () => ({ total: 0, failed: 0, other: 0 }), + resolves: () => true, + keep: () => {}, + }; + const unit = recordUnit(subject, "a"); + expect(unit.record!.fn).toBeInstanceOf(Uint8Array); + expect(unit.record!.reads).toEqual({ + lifted: [["%shared", contentDigest(encodeUnitFunction(functions[0]!)!)]], + }); + // The files of the function's source positions, for reuse to check. + expect([...unit.files!].sort()).toEqual(["a.ts", "b.ts"]); + expect(decodeRecordFunction(unit.record!.fn!)).toEqual(unit.fn); +}); + test("a record lists the held functions it names, with their digests", () => { const functions = [fn("%shared"), fn("%unused")]; const subject: WaveHost = { @@ -373,6 +457,7 @@ test("a record lists the held functions it names, with their digests", () => { store: () => {}, statementCounts: () => ({ total: 0, failed: 0, other: 0 }), resolves: () => true, + keep: () => {}, }; const { record } = recordUnit(subject, "a"); // The function the unit added is in its entries, not its reads. diff --git a/packages/compiler/src/frontend/lowering/unit-journal.ts b/packages/compiler/src/frontend/lowering/unit-journal.ts index e349bb2cf..68bd3e778 100644 --- a/packages/compiler/src/frontend/lowering/unit-journal.ts +++ b/packages/compiler/src/frontend/lowering/unit-journal.ts @@ -4,8 +4,9 @@ * unit's lowering reached: the unit's IR, the entries it added to the * tables that collect lowering results (Lowerer.journalTables), and the * calls it made into reachability, generic demand and statement counting - * (JournalEvent). Records hold only strings, numbers and names, so they can - * be written to a file and read by another process or another build. + * (JournalEvent). Records hold only the function's bytes (or its JSON), + * strings, numbers and names, so they can be written to a file and read by + * another process or another build. * * A unit has no record when its lowering changed state that no record * carries: a table that is not appendable grew, a field changed, an entry @@ -23,20 +24,59 @@ import { appendFileSync, mkdirSync } from "node:fs"; import { join } from "node:path"; import { InternalCompilerError } from "../../errors.js"; +import { decodeFunctionBinary, encodeFunctionStrings, functionDigest } from "../../ir/codec.js"; import type { IrFunction } from "../../ir/ir.js"; import { deserializeFunction, encodeFunction } from "../../ir/serialize.js"; -/** A function as records hold it, or null when it does not encode. Every - * function a record holds goes through this pair. */ +/** A function as table entries hold it, or null when it does not encode. + * Every function an entry holds goes through this pair. */ export function encodeUnitFunction(fn: IrFunction): string | null { return encodeFunction(fn) ?? null; } -/** A function from its record encoding (encodeUnitFunction). */ +/** A function from its entry encoding (encodeUnitFunction). */ export function decodeUnitFunction(text: string): IrFunction { return deserializeFunction(text); } +/** A unit's function as its record holds it: the binary IR encoding, or + * JSON (encodeUnitFunction) when an object in it would not read back with + * its own key order, which frontend passes and the IR's text depend on. */ +export type RecordFunction = string | Uint8Array; + +/** `fn` as a record holds it, or null when it does not encode. A binary + * encoding comes with its strings and the files its fields named `file` + * hold (encodeFunctionStrings). */ +export function encodeRecordFunction(fn: IrFunction): { + readonly fn: RecordFunction; + readonly strings: readonly string[] | null; + readonly files: readonly string[] | null; +} | null { + const binary = encodeFunctionStrings(fn); + if (binary !== null) return { fn: binary.bytes, strings: binary.strings, files: binary.files }; + const text = encodeUnitFunction(fn); + return text === null ? null : { fn: text, strings: null, files: null }; +} + +/** A function from its record encoding (encodeRecordFunction). */ +export function decodeRecordFunction(fn: RecordFunction): IrFunction { + return typeof fn === "string" + ? decodeUnitFunction(fn) + : decodeFunctionBinary(Buffer.from(fn.buffer, fn.byteOffset, fn.byteLength)); +} + +/** `record` as a JSON line, with a binary function in base64, marked when + * it was `applied` from an earlier build's store. */ +export function recordLine(record: UnitRecord, applied = false): string { + const fn = + record.fn === null || typeof record.fn === "string" + ? record.fn + : Buffer.from(record.fn.buffer, record.fn.byteOffset, record.fn.byteLength).toString( + "base64", + ); + return JSON.stringify(applied ? { ...record, fn, applied: true } : { ...record, fn }); +} + /** A call a unit made while it lowered, which applying its record makes * again. */ export interface JournalEvent { @@ -62,9 +102,9 @@ export interface JournalEvent { /** One unit's record. */ export interface UnitRecord { readonly name: string; - /** The unit's function in the compact IR encoding, or null when the unit + /** The unit's function (encodeRecordFunction), or null when the unit * lowered to nothing. */ - readonly fn: string | null; + readonly fn: RecordFunction | null; /** True when the unit's lowering ended in a poison: its diagnostics and * the edges it fired before poisoning stay, and it has no function. */ readonly poisoned: boolean; @@ -91,6 +131,12 @@ export interface JournalTable { readonly entries?: (from: number) => string[] | null; /** Adds the encoded entries whose keys this process's table lacks. */ readonly apply?: (entries: readonly string[]) => void; + /** Whether every encoded entry whose key this process's table holds + * holds the same value there. */ + readonly agrees?: (entries: readonly string[]) => boolean; + /** Whether this process's table holds an entry under the encoded entry's + * key. */ + readonly holds?: (entry: string) => boolean; /** A digest of the content of the entry under `key`, when the table * holds one before position `before`; null otherwise. */ readonly digest?: (key: string, before: number) => string | null; @@ -125,10 +171,20 @@ export function contentDigest(text: string): string { return `${text.length}:${(a >>> 0).toString(16)}${(b >>> 0).toString(16)}`; } -/** The names quoted in JSON `texts`, unescaped only as far as their - * delimiting quotes: the strings that start with `%` (helper, lifted and - * IR data names) and union ids (`u` and 12 hex digits), which the tables - * a unit reads and changes are keyed by. */ +/** Adds to `out` the names among `strings`: the strings that start with `%` + * (helper, lifted and IR data names) and union ids (`u` and 12 hex digits), + * which the tables a unit reads and changes are keyed by. */ +export function namedStrings(strings: Iterable, out: Set): void { + for (const text of strings) { + const first = text.charCodeAt(0); + if (first === 37 /* % */) out.add(text); + else if (first === 117 /* u */ && text.length === 13 && /^u[0-9a-f]{12}$/.test(text)) + out.add(text); + } +} + +/** The names (namedStrings) quoted in JSON `texts`, unescaped only as far + * as their delimiting quotes. */ export function quotedStrings(texts: readonly string[]): Set { const out = new Set(); const hex = (char: number): boolean => (char >= 48 && char <= 57) || (char >= 97 && char <= 102); @@ -225,6 +281,10 @@ export interface WaveHost { statementCounts(): { total: number; failed: number; other: number }; /** Whether `event` names something this process holds. */ resolves(event: JournalEvent): boolean; + /** Keeps a unit's function from its record and makes the record's events + * again, under the unit's generic demand owner and declared-order + * priority. */ + keep(name: string, fn: IrFunction | null, events: readonly JournalEvent[]): void; } /** Whether reachable emit records its units (SCRIPTC_LOWER_JOURNAL=serial, @@ -259,10 +319,69 @@ export interface WaveStats { unrecorded: number; /** Per table, field or check, the units it left without a record. */ reasons: Record; + /** Units whose stored record applied in place of lowering them + * (unit-reuse.ts). */ + applied: number; + /** Time taking and checking the tables' contents, finding stored records, + * applying them, and lowering and recording units, in milliseconds. */ + holdMs: number; + storedMs: number; + applyMs: number; + recordMs: number; + /** Of recordMs, the time the units' lowering took, and the time a + * recorded unit's results took to be replaced by its record's. */ + lowerMs: number; + replaceMs: number; } export function waveStats(): WaveStats { - return { waves: 0, units: 0, recorded: 0, unrecorded: 0, reasons: {} }; + return { + waves: 0, + units: 0, + recorded: 0, + unrecorded: 0, + reasons: {}, + applied: 0, + holdMs: 0, + storedMs: 0, + applyMs: 0, + recordMs: 0, + lowerMs: 0, + replaceMs: 0, + }; +} + +/** A wave's use of the records an earlier build stored (unit-reuse.ts). */ +/** A record an earlier build stored, with its function, which the store + * can keep apart from the record (`record.fn` is null then). */ +export interface StoredRecord { + readonly record: UnitRecord; + fn(): IrFunction | null; +} + +export interface WaveReuse { + /** The stored record of `name`, when the unit can apply it. */ + stored(name: string): StoredRecord | null; + /** Whether units with a stored record lower and are checked against it + * instead (SCRIPTC_INCR_VERIFY=1). */ + readonly verify: boolean; + /** Checks the record a unit lowered to against its stored one, whose + * entries the tables did not hold yet at the unit's turn are `pending`. */ + check( + name: string, + stored: StoredRecord, + pending: Readonly>, + unit: RecordedUnit, + ): void; + /** Notes the record a unit lowered to, with the files its binary + * function names (RecordedUnit.files), or the stored record it applied + * (`files` null). */ + note( + name: string, + record: UnitRecord | null, + applied: boolean, + files: readonly string[] | null, + ): void; } /** What lowering one unit with its events recording produced. */ @@ -275,6 +394,11 @@ export interface RecordedUnit { readonly fn: IrFunction | null; /** Each table's size before the unit. */ readonly before: readonly number[]; + /** The time the unit's lowering took. */ + readonly lowerMs: number; + /** The files the fields named `file` in the record's function hold, when + * the function is binary. */ + readonly files: readonly string[] | null; } /** Lowers `name` with its events recording, and makes its record. The unit @@ -297,7 +421,9 @@ export function recordUnit( const marks = host.scalars.map((scalar) => scalar.mark()); const counts = host.statementCounts(); const recorder = new UnitEvents(); + const lowering = performance.now(); const { fn, poisoned } = host.lowerRecorded(name, recorder); + const lowerMs = performance.now() - lowering; const reasons: string[] = []; const grown: number[] = []; tables.forEach((table, index) => { @@ -322,8 +448,8 @@ export function recordUnit( if (now.total - counts.total !== statements || now.failed - counts.failed !== failed) reasons.push("statements"); if (now.other !== counts.other) reasons.push("diagnostics"); - const text = fn === null ? null : encodeUnitFunction(fn); - if (fn !== null && text === null) reasons.push("encoding"); + const encoded = fn === null ? null : encodeRecordFunction(fn); + if (fn !== null && encoded === null) reasons.push("encoding"); const added: Record = {}; if (reasons.length === 0) for (const index of grown) { @@ -335,13 +461,14 @@ export function recordUnit( // A unit uses the entries its IR and new entries name: it reads them, and // a unit that changes one in place changes one it names. const texts: string[] = []; - if (text !== null) texts.push(text); + if (typeof encoded?.fn === "string") texts.push(encoded.fn); for (const index of grown) { const table = tables[index]!; const entries = added[table.name] ?? table.entries!(before[index]!); if (entries !== null) for (const entry of entries) texts.push(entry); } const names = quotedStrings(texts); + if (encoded?.strings != null) namedStrings(encoded.strings, names); if (own !== null) { if (reasons.length === 0) tables.forEach((table, index) => { @@ -369,36 +496,72 @@ export function recordUnit( } if (read.length > 0) reads[table.name] = read; }); - if (reasons.length > 0) return { record: null, reasons, fn, before }; + if (reasons.length > 0) return { record: null, reasons, fn, before, lowerMs, files: null }; return { - record: { name, fn: text, poisoned, events, tables: added, reads }, + record: { name, fn: encoded?.fn ?? null, poisoned, events, tables: added, reads }, reasons, fn, before, + lowerMs, + files: encoded?.files ?? null, }; } -/** Lowers `wave` with every unit recorded. A unit with a record keeps the - * results its record carries: the entries it added are taken out of their - * tables and added again from the record, and its function is the one - * decoded from the record. A unit without one keeps what lowering made. +/** Lowers `wave` with every unit recorded. Without `reuse`, a unit with a + * record keeps the results its record carries: the entries it added are + * taken out of their tables and added again from the record, and its + * function is the one decoded from the record. Otherwise, and for a unit + * without a record, a unit keeps what lowering made. * Throws an InternalCompilerError when a unit changed the content of an * entry a table held when the unit started (JournalTable.contents), which * no record carries. - * `lines`, when given, receives each unit's record as a JSON line. */ + * `lines`, when given, receives each unit's record as a JSON line. A unit + * whose stored record `reuse` gives applies it, and lowers when it does not + * apply. With `held`, the wave continues the content the waves before it + * left (HeldTables), and the check waits for HeldTables.check. */ export function lowerWaveRecorded( host: WaveHost, wave: readonly string[], stats: WaveStats, lines: string[] | null = null, + reuse: WaveReuse | null = null, + held: HeldTables | null = null, ): void { stats.waves++; const tables = host.tables; - // Units keep the content of the entries that tables held when they - // started: a record carries a unit's new entries, not changes to others. - const contents = tables.map((table) => (table.contents === undefined ? null : table.contents())); + const contents = held ?? new HeldTables(tables, stats); + let started = 0; for (const name of wave) { - const unit = recordUnit(host, name, contents); + started = performance.now(); + let stored = reuse?.stored(name) ?? null; + stats.storedMs += performance.now() - started; + let pending: Readonly> = {}; + if (stored !== null && reuse!.verify) { + // Verify mode lowers the units whose stored record would apply and + // checks the record they lower to. + if (recordApplies(host, stored.record).length === 0) + pending = pendingEntries(host, stored.record); + else stored = null; + } else if (stored !== null) { + started = performance.now(); + const reasons = applyRecord(host, stored.record, stored.fn); + stats.applyMs += performance.now() - started; + if (reasons.length === 0) { + stats.units++; + stats.recorded++; + stats.applied++; + contents.extendTaken(); + lines?.push(recordLine(stored.record, true)); + reuse!.note(name, stored.record, true, null); + continue; + } + for (const reason of reasons) + stats.reasons[`stored:${reason}`] = (stats.reasons[`stored:${reason}`] ?? 0) + 1; + } + started = performance.now(); + const unit = recordUnit(host, name, contents.take()); + stats.recordMs += performance.now() - started; + stats.lowerMs += unit.lowerMs; stats.units++; const record = unit.record; if (record === null) { @@ -406,43 +569,174 @@ export function lowerWaveRecorded( stats.unrecorded++; for (const reason of unit.reasons) stats.reasons[reason] = (stats.reasons[reason] ?? 0) + 1; host.store(name, unit.fn); + if (stored !== null && reuse!.verify) reuse!.check(name, stored, pending, unit); + reuse?.note(name, null, false, null); continue; } - lines?.push(JSON.stringify(record)); + lines?.push(recordLine(record)); stats.recorded++; - tables.forEach((table, index) => { - const entries = record.tables[table.name]; - if (entries === undefined) return; - table.truncate!(unit.before[index]!); - table.apply!(entries); - }); - host.store(name, record.fn === null ? null : decodeUnitFunction(record.fn)); + // The serial mode replaces a unit's results by its record's, so that the + // build's output is what applying the records gives. Reuse keeps what + // lowering made: its records apply in later builds, which verify mode + // and the edit-sequence tests check. + let fn = unit.fn; + if (reuse === null) { + const replacing = performance.now(); + tables.forEach((table, index) => { + const entries = record.tables[table.name]; + if (entries === undefined) return; + table.truncate!(unit.before[index]!); + table.apply!(entries); + }); + fn = record.fn === null ? null : decodeRecordFunction(record.fn); + stats.replaceMs += performance.now() - replacing; + } + host.store(name, fn); + if (stored !== null && reuse!.verify) reuse!.check(name, stored, pending, unit); + reuse?.note(name, record, false, unit.files); } - tables.forEach((table, index) => { - const key = contents[index]?.changed() ?? null; - if (key !== null) - throw new InternalCompilerError( - `a unit changed ${table.name} entry ${key} in place without naming it, which no unit record carries`, + if (held === null) contents.check(); +} + +/** The content of the tables' entries that recorded units start from + * (JournalTable.contents): units keep the content of the entries the + * tables held when they started, since a record carries a unit's new + * entries, not changes to others. Applying a stored record only adds + * entries, so the content is taken when the first unit lowers, and not at + * all when none does. Consecutive waves with nothing but checker prefetches + * between them share one: the content a wave leaves, as the check finds it, + * is the content the next one starts with. */ +export class HeldTables { + private contents: (HeldContents | null)[] | null = null; + + constructor( + private readonly tables: readonly JournalTable[], + private readonly stats: WaveStats, + ) {} + + /** The held content, taken now when it was not yet. */ + take(): (HeldContents | null)[] { + if (this.contents === null) { + const taken = performance.now(); + this.contents = this.tables.map((table) => + table.contents === undefined ? null : table.contents(), ); - }); + this.stats.holdMs += performance.now() - taken; + } + return this.contents; + } + + /** Adds the entries past the held ones, when the content is taken. */ + extendTaken(): void { + if (this.contents !== null) for (const entry of this.contents) entry?.extend(); + } + + /** Throws an InternalCompilerError when an entry a table held changed in + * place since the content was taken, other than by the units that keep + * their changes (HeldContents.refresh). */ + check(): void { + const contents = this.contents; + if (contents === null) return; + const started = performance.now(); + this.tables.forEach((table, index) => { + const key = contents[index]?.changed() ?? null; + if (key !== null) + throw new InternalCompilerError( + `a unit changed ${table.name} entry ${key} in place without naming it, which no unit record carries`, + ); + }); + this.stats.holdMs += performance.now() - started; + } +} + +/** Applies `record` in place of lowering its unit, at the unit's turn, or + * returns why the unit has to lower instead. A record applies when every + * entry it adds agrees with what the tables hold, it names only known + * tables, every event resolves and every entry it reads is held with the + * same content. Applying adds its entries, keeps its function (`stored`'s, + * when the store keeps it apart from the record) and makes its events + * again. */ +export function applyRecord( + host: WaveHost, + record: UnitRecord, + stored?: () => IrFunction | null, +): string[] { + const reasons = recordApplies(host, record); + if (reasons.length > 0) return reasons; + for (const table of host.tables) { + const entries = record.tables[table.name]; + if (entries !== undefined) table.apply!(entries); + } + const fn = record.poisoned + ? null + : stored !== undefined + ? stored() + : record.fn === null + ? null + : decodeRecordFunction(record.fn); + host.keep(record.name, fn, record.events); + return []; +} + +/** The entries `record` adds that the tables do not hold yet, by table + * name: what applying it would add at its unit's turn. */ +export function pendingEntries( + host: WaveHost, + record: UnitRecord, +): Record { + const out: Record = {}; + for (const table of host.tables) { + const entries = record.tables[table.name]; + if (entries === undefined) continue; + const pending = entries.filter((entry) => table.holds?.(entry) !== true); + if (pending.length > 0) out[table.name] = pending; + } + return out; +} + +/** Why `record` does not apply at its unit's turn (applyRecord), or no + * reasons when it does. */ +export function recordApplies(host: WaveHost, record: UnitRecord): string[] { + const tables = host.tables; + for (const table of tables) { + const entries = record.tables[table.name]; + if (entries === undefined) continue; + if (table.agrees === undefined || table.apply === undefined || !table.agrees(entries)) + return [`disagrees:${table.name}`]; + } + for (const table of Object.keys(record.tables)) + if (!tables.some((held) => held.name === table)) return [`unknown table:${table}`]; + for (const event of record.events) if (!host.resolves(event)) return [`event:${event.kind}`]; + for (const table of tables) { + const reads = record.reads[table.name]; + if (reads === undefined) continue; + if (table.digest === undefined) return [`unknown read:${table.name}`]; + const size = table.size(); + for (const [key, digest] of reads) + if (table.digest(key, size) !== digest) return [`read:${table.name}`]; + } + for (const table of Object.keys(record.reads)) + if (!tables.some((held) => held.name === table)) return [`unknown read:${table}`]; + return []; } /** The content of a keyed table's entries (JournalTable.contents): each - * entry's encoded value, and the order of the keys, since a table only adds - * entries at the end. An entry is found by its key, or by the name its value - * holds (`valueName`): a unit names the helper it got, not the key it was - * looked up by. `text` answers undefined for a key the table does not + * entry's content (`text`: its encoded value, or the value itself when it + * is a string or a number), and the order of the keys, since a table only + * adds entries at the end. An entry is found by its key, or by the name its + * value holds (`valueName`): a unit names the helper it got, not the key it + * was looked up by. `text` answers undefined for a key the table does not * hold. */ function keyedContents( size: () => number, keys: () => Iterable, - text: (key: string) => string | null | undefined, + text: (key: string) => string | number | null | undefined, valueName: (key: string) => string | null, ): HeldContents { - const texts = new Map(); + const texts = new Map(); let order: string[] = []; const byName = new Map(); - const differs = (key: string): boolean => text(key) !== texts.get(key); + const differs = (key: string): boolean => !Object.is(text(key), texts.get(key)); const keyOf = (name: string): string | undefined => (texts.has(name) ? name : byName.get(name)); const held: HeldContents = { extend: () => { @@ -499,6 +793,7 @@ export function mapTable(name: string, map: Map): JournalTable { (value) => JSON.stringify(value), (text) => JSON.parse(text) as string, (value) => value, + (value) => value, ); } @@ -509,6 +804,8 @@ export function countTable(name: string, map: Map): JournalTable map, (value) => String(value), (text) => Number(text), + null, + (value) => value, ); } @@ -530,13 +827,17 @@ export function irDataTable(name: string, map: Map): JournalTable /** An appendable table over a Map from strings to values that `encode` * writes, or answers null for, and `decode` reads back. An entry is its key * as a JSON string, a line break and its value: a JSON string holds no line - * break. A value that does not encode leaves its unit without a record. */ + * break. A value that does not encode leaves its unit without a record. + * `content` is what held contents compare (JournalTable.contents): the + * encoding, or the value itself for a table of strings or numbers, which + * only change by being replaced. */ export function codecTable( name: string, map: Map, encode: (value: V) => string | null, decode: (text: string) => V, nameOf: ((value: V) => string) | null = null, + content: (value: V) => string | number | null = encode, ): JournalTable { return { name, @@ -552,6 +853,13 @@ export function codecTable( } return out; }, + agrees: (entries) => + entries.every((entry) => { + const split = entry.indexOf("\n"); + const held = map.get(JSON.parse(entry.slice(0, split)) as string); + return held === undefined || encode(held) === entry.slice(split + 1); + }), + holds: (entry) => map.has(JSON.parse(entry.slice(0, entry.indexOf("\n"))) as string), apply: (entries) => { for (const entry of entries) { const split = entry.indexOf("\n"); @@ -569,7 +877,7 @@ export function codecTable( () => map.keys(), (key) => { const value = map.get(key); - return value === undefined ? undefined : encode(value); + return value === undefined ? undefined : content(value); }, (key) => { const value = map.get(key); @@ -585,6 +893,8 @@ export function setTable(name: string, set: Set): JournalTable { name, size: () => set.size, entries: (from) => [...set].slice(from), + agrees: () => true, + holds: (entry) => set.has(entry), apply: (entries) => { for (const entry of entries) set.add(entry); }, @@ -652,6 +962,14 @@ export function functionTable( } return out; }, + agrees: (entries) => + entries.every((entry) => { + const fn = decodeUnitFunction(entry); + if (!has(fn.name)) return true; + const index = indexOf(fn.name); + return index !== undefined && encodeUnitFunction(functions[index]!) === entry; + }), + holds: (entry) => has(decodeUnitFunction(entry).name), apply: (entries) => { for (const entry of entries) { const fn = decodeUnitFunction(entry); @@ -663,16 +981,17 @@ export function functionTable( while (functions.length > from) functions.pop(); changed(); }, + // Held functions are their content digests (heldFunction). contents: () => { const texts: (string | null)[] = []; const indexes = new Map(); const differs = (index: number): boolean => - index < functions.length && encodeUnitFunction(functions[index]!) !== texts[index]; + index < functions.length && heldFunction(functions[index]!) !== texts[index]; const held: HeldContents = { extend: () => { for (let index = texts.length; index < functions.length; index++) { const fn = functions[index]!; - texts.push(encodeUnitFunction(fn)); + texts.push(heldFunction(fn)); indexes.set(fn.name, index); } }, @@ -692,7 +1011,7 @@ export function functionTable( for (const key of keys) { const index = indexes.get(key); if (index !== undefined && index < functions.length) - texts[index] = encodeUnitFunction(functions[index]!); + texts[index] = heldFunction(functions[index]!); } }, }; @@ -702,6 +1021,17 @@ export function functionTable( }; } +/** The content a function table holds of `fn` (HeldContents): its digest + * (functionDigest), or its entry encoding when the binary encoder cannot + * write it. */ +function heldFunction(fn: IrFunction): string | null { + try { + return functionDigest(fn); + } catch { + return encodeUnitFunction(fn); + } +} + /** A table whose growth leaves the unit without a record. */ export function checkedTable(name: string, size: () => number): JournalTable { return { name, size }; diff --git a/packages/compiler/src/frontend/lowering/unit-reuse.ts b/packages/compiler/src/frontend/lowering/unit-reuse.ts new file mode 100644 index 000000000..5de2c7b0a --- /dev/null +++ b/packages/compiler/src/frontend/lowering/unit-reuse.ts @@ -0,0 +1,1030 @@ +/* Reuse of lowered units across builds (SCRIPTC_INCR_LOWERING=1). + * + * A build with a unit store records every reachable-emit unit + * (unit-journal.ts) and writes the records to the store, one entry per + * source file, beside a program entry that describes the build's program. + * The next build applies a unit's stored record in place of lowering the + * unit when nothing the unit's lowering read can have changed: + * + * - the program digest is unchanged: the IR version, the files outside the + * program's own modules (declaration files, the library), the module + * order and the statements with global effect; + * - the unit's source file has the same text, and so does every other file + * whose source positions its record holds; + * - the unit's text spells no name in the build's changed names. + * + * The changed names come from the two builds' declaration tables. A + * declaration is a top-level statement or a member of a top-level class or + * interface. Its surface is its text without the bodies of functions that + * declare their return type: what other declarations' types can depend on. + * Its facts are what the steps before the worklist settled for it: a + * function's signature, a class's layout and method signatures, a + * variable's globals. The changed names are: + * + * - the names of top-level declarations that were added or removed, or + * whose surface or facts changed, and the interfaces whose members did, + * closed over the surfaces that spell one of them (a declaration whose + * surface spells one joins); + * - the names of such class and interface members, and of declarations + * whose text alone changed; + * - the names a changed, added or removed declaration writes to outside + * itself, since analyses read a binding's or property's writes anywhere; + * - the names that refer to a module's namespace object, by a namespace + * import or an `import()` or `require()` call, when the module's exports + * changed. + * + * TypeScript resolves every dependency of a type through a name some text + * spells, so a unit whose text spells none of these sees the same types, + * the same declarations and the same facts. A unit's spelled names include + * its strings, which can name members. + * + * The first build into a cache records nothing: it writes only the + * declaration tables, and the build after it records its units. + * + * Applying a record goes through applyRecord, which still lowers the unit + * when the record disagrees with what this build holds by the unit's turn. + * SCRIPTC_INCR_VERIFY=1 lowers every unit whose record would apply and + * throws an InternalCompilerError naming the unit when the new record + * differs from the stored one. */ +import { createHash } from "node:crypto"; +import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { InternalCompilerError } from "../../errors.js"; +import type { IrFunction } from "../../ir/ir.js"; +import { IrReader, IrWriter, decodeFunctionBinary } from "../../ir/codec.js"; +import { IR_CODEC_LAYOUT } from "../../ir/codec.generated.js"; +import { IR_VERSION } from "../../ir/serialize.js"; +import * as ts from "../ts7/adapter.js"; +import { isJsSourceFileName } from "../tsc-codes.js"; +import { + decodeRecordFunction, + encodeUnitFunction, + type StoredRecord, + type UnitRecord, +} from "./unit-journal.js"; + +/** Where a build's unit entries live. The store's owner keys the program + * entry by everything the lowered module depends on besides the program's + * sources: the compiler, the entry, the options and the target. */ +export interface UnitStore { + /** The program entry the last build with the same key wrote, or null. */ + readProgram(): Uint8Array | null; + writeProgram(bytes: Uint8Array): void; + /** A file entry by the name writeFile gave it, or null. */ + readFile(name: string): Uint8Array | null; + /** Writes a file entry and returns its name. */ + writeFile(bytes: Uint8Array): string; +} + +/** What reuse asks the lowerer. */ +export interface UnitReuseHooks { + /** The facts the steps before the worklist settled for a declaration (a + * top-level statement or a class's header), as text. */ + facts(declaration: ts.Node): string; + /** The names a module specifier's module exports, as text, or null when + * it names no module of the program. */ + exportsOf(specifier: ts.Node): string | null; + /** Whether every declaration of the binding `identifier` names is inside + * `container`. */ + isLocal(identifier: ts.Identifier, container: ts.Node): boolean; +} + +/** A unit store in `directory`: the program entry for `key` under + * `program/`, file entries by content digest under `files/`. Failing reads + * are misses and failing writes leave the store as it was. */ +export function directoryUnitStore(directory: string, key: string): UnitStore { + const read = (path: string): Uint8Array | null => { + try { + return readFileSync(path); + } catch { + return null; + } + }; + const write = (path: string, bytes: Uint8Array): void => { + try { + mkdirSync(join(path, ".."), { recursive: true, mode: 0o700 }); + const stage = `${path}.${process.pid}.tmp`; + writeFileSync(stage, bytes); + renameSync(stage, path); + } catch { + // A failed write only costs the next build its reuse. + } + }; + const programPath = join(directory, "program", key); + return { + readProgram: () => read(programPath), + writeProgram: (bytes) => write(programPath, bytes), + readFile: (name) => (/^[0-9a-f]{64}$/.test(name) ? read(join(directory, "files", name)) : null), + writeFile: (bytes) => { + const name = createHash("sha256").update(bytes).digest("hex"); + const path = join(directory, "files", name); + if (read(path) === null) write(path, bytes); + return name; + }, + }; +} + +/** Whether builds reuse lowered units (SCRIPTC_INCR_LOWERING=1). */ +export function unitReuseEnabled(): boolean { + return process.env["SCRIPTC_INCR_LOWERING"] === "1"; +} + +/** Whether every unit whose record would apply lowers again and is checked + * against it (SCRIPTC_INCR_VERIFY=1). */ +export function unitReuseVerify(): boolean { + return process.env["SCRIPTC_INCR_VERIFY"] === "1"; +} + +/** The first line of a program entry and of a file entry. */ +const PROGRAM_MAGIC = "scriptc-units-program-2"; +const FILE_MAGIC = "scriptc-units-file-3"; + +/** A unit's record as an entry holds it, with the files other than the + * unit's own whose source positions the record holds. The function is in + * the binary encoding (`bytes`, the record's `fn` null) when every object + * in it reads back with its own key order, which frontend passes and the + * IR's text depend on, and in the record's JSON otherwise. */ +interface StoredUnit { + readonly record: UnitRecord; + readonly files: readonly string[]; + readonly bytes: Buffer | null; +} + +/** A recorded unit as an entry holds it. `named` are the files the fields + * named `file` in its binary function hold (RecordedUnit.files). */ +function storedUnit(record: UnitRecord, named: readonly string[] | null, own: string): StoredUnit { + const files = positionFiles(record, named, own); + const fn = record.fn; + if (fn === null || typeof fn === "string") return { record, files, bytes: null }; + const bytes = Buffer.from(fn.buffer, fn.byteOffset, fn.byteLength); + return { record: { ...record, fn: null }, files, bytes }; +} + +/** The record of a stored unit, with its function. */ +function storedRecord(unit: StoredUnit): StoredRecord { + const bytes = unit.bytes; + const record = unit.record; + return { + record, + fn: () => + bytes !== null + ? decodeFunctionBinary(bytes) + : record.fn === null + ? null + : decodeRecordFunction(record.fn), + }; +} + +/** The files whose source positions `record` holds, other than `own`: + * those of its JSON function and entries, and `named` for a binary + * function. */ +function positionFiles(record: UnitRecord, named: readonly string[] | null, own: string): string[] { + const files = new Set(); + if (named !== null) for (const file of named) if (file !== own) files.add(file); + const scan = (text: string): void => { + const pattern = /"file":("(?:[^"\\]|\\.)*")/g; + for (let match = pattern.exec(text); match !== null; match = pattern.exec(text)) { + const file = JSON.parse(match[1]!) as string; + if (file !== own) files.add(file); + } + }; + if (typeof record.fn === "string") scan(record.fn); + for (const entries of Object.values(record.tables)) for (const entry of entries) scan(entry); + return [...files].sort(); +} + +/** A SHA-256 digest of `text`, in hex. */ +function digestOf(text: string): string { + return createHash("sha256").update(text).digest("hex"); +} + +/** One declaration of a module's declaration table. */ +interface Declaration { + /** The declaration's kind and name, with an ordinal for repeated ones: + * the same declaration across builds. */ + readonly key: string; + /** The names other text refers to it by. */ + readonly names: readonly string[]; + /** Whether it is a class or interface member: other text reaches it only + * through a value whose type names the owner. */ + readonly member: boolean; + /** For an interface member, the interface: another surface of the member + * is another type of its owner. A class's type as lowering sees it is in + * its header's facts instead. */ + readonly owner: string | null; + /** Digests of its text, its surface and its facts. */ + readonly text: string; + readonly surface: string; + readonly facts: string; + /** The names its text assigns. */ + readonly assigns: readonly string[]; +} + +/** A site that refers to a module's namespace object: the names that hold + * or contain it, and a digest of the module's exports. */ +interface NamespaceSite { + readonly key: string; + readonly names: readonly string[]; + readonly exports: string; +} + +/** What a build records about one of the program's own modules. */ +interface FileTable { + readonly path: string; + readonly text: string; + /** The file entry holding its units' records, or "" when it has none. */ + entry: string; + readonly declarations: readonly Declaration[]; + readonly namespaces: readonly NamespaceSite[]; +} + +/** A declaration of this build's program, for the closure over surfaces: + * the names it declares, its owner's, and its node. A class or interface + * header is its statement without the members. */ +interface DeclarationNode { + readonly names: readonly string[]; + readonly member: boolean; + readonly owner: string | null; + readonly node: ts.Node; + readonly header: boolean; + readonly js: boolean; +} + +/** The body of a function-like node whose body other declarations' types + * cannot see: it declares its return type, or it is a constructor or set + * accessor. Undefined for other nodes. */ +function opaqueBody(node: ts.Node): ts.Node | undefined { + if (!ts.isFunctionLikeDeclaration(node) || node.body === undefined) return undefined; + return node.type !== undefined || ts.isConstructorDeclaration(node) || ts.isSetAccessor(node) + ? node.body + : undefined; +} + +/** The name node of a declaration or member, if it has one. */ +function nameNodeOf(node: ts.Node): ts.Node | undefined { + if ( + ts.isFunctionDeclaration(node) || + ts.isClassDeclaration(node) || + ts.isClassExpression(node) || + ts.isInterfaceDeclaration(node) || + ts.isTypeAliasDeclaration(node) || + ts.isEnumDeclaration(node) || + ts.isModuleDeclaration(node) || + ts.isMethodDeclaration(node) || + ts.isPropertyDeclaration(node) || + ts.isGetAccessor(node) || + ts.isSetAccessor(node) || + ts.isPropertySignature(node) || + ts.isMethodSignatureDeclaration(node) + ) + return node.name ?? undefined; + return undefined; +} + +/** The kinds the name walks materialize. */ +const NAME_KINDS: ReadonlySet = new Set([ + ts.SyntaxKind.Identifier, + ts.SyntaxKind.PrivateIdentifier, +]); + +/** The kinds of string literals. */ +const STRING_KINDS: ReadonlySet = new Set([ + ts.SyntaxKind.StringLiteral, + ts.SyntaxKind.NoSubstitutionTemplateLiteral, +]); + +/** The kinds that assign to their operand. */ +const WRITE_KINDS: ReadonlySet = new Set([ + ts.SyntaxKind.BinaryExpression, + ts.SyntaxKind.PrefixUnaryExpression, + ts.SyntaxKind.PostfixUnaryExpression, + ts.SyntaxKind.DeleteExpression, +]); + +/** The operators of assignments. */ +const ASSIGNMENTS: ReadonlySet = new Set([ + ts.SyntaxKind.EqualsToken, + ts.SyntaxKind.PlusEqualsToken, + ts.SyntaxKind.MinusEqualsToken, + ts.SyntaxKind.AsteriskEqualsToken, + ts.SyntaxKind.AsteriskAsteriskEqualsToken, + ts.SyntaxKind.SlashEqualsToken, + ts.SyntaxKind.PercentEqualsToken, + ts.SyntaxKind.LessThanLessThanEqualsToken, + ts.SyntaxKind.GreaterThanGreaterThanEqualsToken, + ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken, + ts.SyntaxKind.AmpersandEqualsToken, + ts.SyntaxKind.BarEqualsToken, + ts.SyntaxKind.CaretEqualsToken, + ts.SyntaxKind.BarBarEqualsToken, + ts.SyntaxKind.AmpersandAmpersandEqualsToken, + ts.SyntaxKind.QuestionQuestionEqualsToken, +]); + +/** The identifier names `node`'s text spells, all of them or only those + * outside the bodies opaqueBody names. */ +function spelledNames(node: ts.Node, surfaceOnly: boolean, out: Set): void { + if (!surfaceOnly) { + ts.walkPreorder( + node, + (child) => { + out.add((child as ts.Identifier).text); + }, + NAME_KINDS, + ); + return; + } + ts.walkPreorder(node, (child) => { + if (ts.isIdentifier(child) || ts.isPrivateIdentifier(child)) { + out.add(child.text); + return undefined; + } + const parent = child.parent; + if (parent !== undefined && opaqueBody(parent) === child) return "skip"; + return undefined; + }); +} + +/** The names `node`'s text writes to outside it: the bindings, properties + * and elements that its assignments, `++`, `--` and `delete` target, but + * not bindings declared inside `node`. A property write names the property; + * an element write names what the element's object spells. */ +function assignedNames(node: ts.Node, hooks: UnitReuseHooks): string[] { + const out = new Set(); + const spellsOutside = (expression: ts.Node): void => { + ts.walkPreorder( + expression, + (child) => { + if (ts.isIdentifier(child)) { + if (!hooks.isLocal(child, node)) out.add(child.text); + } else if (ts.isPrivateIdentifier(child)) out.add(child.text); + }, + NAME_KINDS, + ); + }; + const written = (target: ts.Node): void => { + if (ts.isParenthesizedExpression(target)) written(target.expression); + else if (ts.isIdentifier(target)) { + if (!hooks.isLocal(target, node)) out.add(target.text); + } else if (ts.isPropertyAccessExpression(target)) out.add(target.name.text); + else if (ts.isElementAccessExpression(target)) spellsOutside(target.expression); + else if (ts.isObjectLiteralExpression(target)) { + for (const property of target.properties.nodes) { + if (ts.isShorthandPropertyAssignment(property)) written(property.name); + else if (ts.isPropertyAssignment(property)) written(property.initializer); + else if (ts.isSpreadAssignment(property)) written(property.expression); + } + } else if (ts.isArrayLiteralExpression(target)) { + for (const element of target.elements.nodes) + written(ts.isSpreadElement(element) ? element.expression : element); + } else if (ts.isBinaryExpression(target)) written(target.left); + else spellsOutside(target); + }; + ts.walkPreorder( + node, + (child) => { + if (ts.isBinaryExpression(child)) { + if (ASSIGNMENTS.has(child.operatorToken.kind)) written(child.left); + } else if (ts.isPrefixUnaryExpression(child) || ts.isPostfixUnaryExpression(child)) { + if ( + child.operator === ts.SyntaxKind.PlusPlusToken || + child.operator === ts.SyntaxKind.MinusMinusToken + ) + written(child.operand); + } else if (ts.isDeleteExpression(child)) written(child.expression); + }, + WRITE_KINDS, + ); + return [...out].sort(); +} + +/** `node`'s text with the bodies opaqueBody names replaced by `{}`. */ +function surfaceText(sf: ts.SourceFile, node: ts.Node, js: boolean): string { + const text = sf.text; + const start = node.getStart(sf); + if (js) return text.slice(start, node.end); + const cuts: [number, number][] = []; + ts.walkPreorder(node, (child) => { + const body = child === node ? undefined : opaqueBody(child); + if (body === undefined) return undefined; + cuts.push([body.getStart(sf), body.end]); + return "skip"; + }); + let out = ""; + let at = start; + for (const [from, to] of cuts) { + if (from < at) continue; + out += text.slice(at, from) + "{}"; + at = to; + } + return out + text.slice(at, node.end); +} + +/** The names a statement declares. A statement without a name (an + * expression statement, a loop) declares the names it assigns, such as the + * base of an expando `f.prop = value`; in JavaScript, where such statements + * also define exports, every name and string it spells. */ +function statementNames(statement: ts.Statement, js: boolean, hooks: UnitReuseHooks): string[] { + if (ts.isVariableStatement(statement)) { + const names: string[] = []; + for (const declaration of statement.declarationList.declarations.nodes) { + const name = declaration.name; + if (ts.isIdentifier(name)) names.push(name.text); + else + ts.walkPreorder( + name, + (child) => { + names.push((child as ts.Identifier).text); + }, + NAME_KINDS, + ); + } + return names; + } + if (ts.isExportDeclaration(statement) || ts.isExportAssignment(statement)) { + const names = new Set(); + spelledNames(statement, false, names); + return [...names].sort(); + } + const name = nameNodeOf(statement); + if (name !== undefined && (ts.isIdentifier(name) || ts.isStringLiteral(name))) return [name.text]; + if (!js) return assignedNames(statement, hooks); + const names = new Set(); + spelledNames(statement, false, names); + ts.walkPreorder(statement, (child) => { + if (ts.isStringLiteral(child)) names.add(child.text); + return undefined; + }); + return [...names].sort(); +} + +/** Whether a statement of a module changes names outside the module: + * `declare global`, a module augmentation, or `export * from`. */ +function hasGlobalEffect(statement: ts.Statement): boolean { + if (ts.isModuleDeclaration(statement)) { + const name = statement.name; + return !ts.isIdentifier(name) || name.text === "global"; + } + return ts.isExportDeclaration(statement) && statement.exportClause === undefined; +} + +/** The names a class or interface member declares: its name, or the + * identifiers a computed name spells. */ +function memberNames(member: ts.Node): string[] { + const name = nameNodeOf(member); + if (name === undefined) return ts.isConstructorDeclaration(member) ? ["constructor"] : []; + if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name) || ts.isStringLiteral(name)) + return [name.text]; + const names = new Set(); + spelledNames(name, false, names); + if (names.size === 0) names.add(name.getText()); + return [...names].sort(); +} + +/** The sites in `node` that refer to a module's namespace object: a + * namespace import or re-export, `import x = require()`, and `import()` and + * `require()` calls with a string, which `names` stand for. */ +function namespaceSites( + sf: ts.SourceFile, + statement: ts.Statement, + names: readonly string[], + hooks: UnitReuseHooks, + out: NamespaceSite[], +): void { + const add = (specifier: ts.Node, holders: readonly string[]): void => { + const exports = hooks.exportsOf(specifier); + if (exports === null) return; + out.push({ + key: `${specifier.getText(sf)}\n${holders.join(",")}`, + names: holders, + exports: digestOf(exports), + }); + }; + if (ts.isImportDeclaration(statement)) { + const bindings = statement.importClause?.namedBindings; + if (bindings !== undefined && ts.isNamespaceImport(bindings)) + add(statement.moduleSpecifier, [bindings.name.text]); + return; + } + if (ts.isImportEqualsDeclaration(statement)) { + const reference = statement.moduleReference; + if (ts.isExternalModuleReference(reference)) add(reference.expression, [statement.name.text]); + return; + } + if (ts.isExportDeclaration(statement)) { + const clause = statement.exportClause; + if ( + clause !== undefined && + ts.isNamespaceExport(clause) && + statement.moduleSpecifier !== undefined + ) + add(statement.moduleSpecifier, [clause.name.getText(sf)]); + return; + } + const text = sf.text.slice(statement.pos, statement.end); + if (!text.includes("import(") && !text.includes("require(")) return; + ts.walkPreorder(statement, (child) => { + if (!ts.isCallExpression(child)) return undefined; + const callee = child.expression; + const first = child.arguments.nodes[0]; + if (first === undefined || !ts.isStringLiteralLike(first)) return undefined; + if ( + callee.kind === ts.SyntaxKind.ImportKeyword || + (ts.isIdentifier(callee) && callee.text === "require") + ) + add(first, names); + return undefined; + }); +} + +/** A module's declaration table, its declarations' nodes, and the text of + * its statements with global effect. A file that is not a module has global + * effect as a whole. */ +function fileTable( + sf: ts.SourceFile, + hooks: UnitReuseHooks, + previous: FileTable | undefined, +): { table: FileTable; nodes: DeclarationNode[]; global: string } { + // A declaration with the text it had in the previous build writes what it + // wrote then. + const before = new Map((previous?.declarations ?? []).map((d) => [d.key, d])); + const assigns = (key: string, text: string, node: ts.Node): readonly string[] => { + const then = before.get(key); + return then !== undefined && then.text === text ? then.assigns : assignedNames(node, hooks); + }; + const js = isJsSourceFileName(sf.fileName); + const declarations: Declaration[] = []; + const namespaces: NamespaceSite[] = []; + const nodes: DeclarationNode[] = []; + const keys = new Set(); + let global = ""; + const unique = (key: string): string => { + let out = key; + for (let ordinal = 1; keys.has(out); ordinal++) out = `${key}#${ordinal}`; + keys.add(out); + return out; + }; + const module = ts.isExternalModule(sf) || js; + for (const statement of sf.statements.nodes) { + if (ts.isImportDeclaration(statement) || ts.isImportEqualsDeclaration(statement)) { + namespaceSites(sf, statement, [], hooks, namespaces); + continue; + } + if (!module || hasGlobalEffect(statement)) { + global += sf.text.slice(statement.getStart(sf), statement.end) + "\n"; + continue; + } + const names = statementNames(statement, js, hooks); + namespaceSites(sf, statement, names, hooks, namespaces); + const kind = String(statement.kind); + if (ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement)) { + // Each member is a declaration of its own; the rest of the class or + // interface (its name, type parameters, heritage and modifiers) is the + // header, named by the class, and holds the class's facts. + const owner = names[0] ?? ""; + const seeds = ts.isInterfaceDeclaration(statement) ? owner : null; + for (const member of statement.members.nodes) { + const declared = memberNames(member); + const key = unique(`${kind}:${owner}.${declared.join(",")}`); + const text = digestOf(sf.text.slice(member.getStart(sf), member.end)); + declarations.push({ + key, + names: declared, + member: true, + owner: seeds, + text, + surface: digestOf(surfaceText(sf, member, js)), + facts: "", + assigns: assigns(key, text, member), + }); + nodes.push({ + names: declared, + member: true, + owner: seeds, + node: member, + header: false, + js, + }); + } + const members = statement.members; + const header = + sf.text.slice(statement.getStart(sf), members.pos) + + sf.text.slice(members.end, statement.end); + const digest = digestOf(header); + declarations.push({ + key: unique(`${kind}:${owner}`), + names, + member: false, + owner: null, + text: digest, + surface: digest, + facts: digestOf(hooks.facts(statement)), + assigns: [], + }); + nodes.push({ names, member: false, owner: null, node: statement, header: true, js }); + continue; + } + const digest = digestOf(sf.text.slice(statement.getStart(sf), statement.end)); + const key = unique(`${kind}:${names.join(",")}`); + declarations.push({ + key, + names, + member: false, + owner: null, + text: digest, + surface: js ? digest : digestOf(surfaceText(sf, statement, js)), + facts: digestOf(hooks.facts(statement)), + assigns: assigns(key, digest, statement), + }); + nodes.push({ names, member: false, owner: null, node: statement, header: false, js }); + } + return { + table: { + path: sf.fileName, + text: digestOf(sf.text), + entry: "", + declarations, + namespaces, + }, + nodes, + global, + }; +} + +/** The names a declaration's surface spells: a JavaScript declaration's + * whole text, a header without its members. */ +function surfaceNames(declaration: DeclarationNode): Set { + const names = new Set(); + if (!declaration.header) { + spelledNames(declaration.node, !declaration.js, names); + return names; + } + const members = new Set( + (declaration.node as ts.ClassDeclaration | ts.InterfaceDeclaration).members.nodes, + ); + ts.walkPreorder(declaration.node, (child) => { + if (members.has(child)) return "skip"; + if (ts.isIdentifier(child) || ts.isPrivateIdentifier(child)) names.add(child.text); + return undefined; + }); + return names; +} + +/** What a build knows about the units it lowers, and what it writes. */ +export class UnitReuse { + /** Whether this build records its units. A build that finds no program + * entry, the first one into a cache, records none: it lowers as a build + * without reuse does and writes only the declaration tables, so that + * recording costs the next build instead. */ + readonly records: boolean; + /** The previous build's tables by path, when its program digest matches. */ + private readonly previous: Map | null; + /** The names whose declarations changed since the previous build. */ + private readonly changed: Set; + /** The decoded file entries by path. */ + private readonly decoded = new Map | null>(); + /** The records this build made or applied, by file path, in unit order. */ + private readonly written = new Map>(); + /** Files whose units this build lowered. */ + private readonly rewritten = new Set(); + /** Counts, and the time opening (tables and changed names) and finishing + * (writing entries) took, in milliseconds. */ + readonly stats = { + candidates: 0, + hits: 0, + misses: 0, + changedNames: 0, + openMs: 0, + readMs: 0, + finishMs: 0, + /** Functions this build stored in the binary encoding, and as JSON, and + * the time making their units' entries took. */ + binary: 0, + json: 0, + storeMs: 0, + }; + + private constructor( + private readonly store: UnitStore, + private readonly program: string, + private readonly tables: Map, + previous: { program: string; tables: Map } | null, + nodes: readonly DeclarationNode[], + ) { + this.previous = previous !== null && previous.program === program ? previous.tables : null; + this.records = previous !== null; + this.changed = this.previous === null ? new Set() : changedNames(this.previous, tables, nodes); + this.stats.changedNames = this.changed.size; + } + + /** Opens reuse for a build of `files`, the program's own modules in + * module order, within `program`. */ + static open( + store: UnitStore, + program: ts.Program, + files: readonly ts.SourceFile[], + hooks: UnitReuseHooks, + ): UnitReuse { + const started = performance.now(); + const own = new Set(files); + const tables = new Map(); + const nodes: DeclarationNode[] = []; + const previous = readProgram(store); + const digest = createHash("sha256").update( + `${PROGRAM_MAGIC}\n${IR_VERSION}\n${IR_CODEC_LAYOUT}\n`, + ); + for (const sf of files) { + const file = fileTable(sf, hooks, previous?.tables.get(sf.fileName)); + tables.set(sf.fileName, file.table); + for (const node of file.nodes) nodes.push(node); + digest.update(`${sf.fileName}\n${digestOf(file.global)}\n`); + } + const others = program + .getSourceFiles() + .filter((sf) => !own.has(sf)) + .map((sf) => `${sf.fileName}\n${digestOf(sf.text)}\n`) + .sort(); + for (const other of others) digest.update(other); + const reuse = new UnitReuse(store, digest.digest("hex"), tables, previous, nodes); + reuse.stats.openMs = performance.now() - started; + return reuse; + } + + /** The stored record of unit `name`, whose text is `source` in `file` + * inside the declarations `owners`, when the unit can apply it. */ + candidate( + name: string, + file: string, + source: ts.Node | null, + owners: readonly ts.Node[], + ): StoredRecord | null { + if (this.previous === null || source === null) return null; + const before = this.previous.get(file); + const now = this.tables.get(file); + if ( + before === undefined || + now === undefined || + before.text !== now.text || + before.entry === "" + ) + return null; + let units = this.decoded.get(file); + if (units === undefined) { + const started = performance.now(); + units = readFileEntry(this.store, before.entry); + this.decoded.set(file, units); + this.stats.readMs += performance.now() - started; + } + const stored = units?.get(name); + if (stored === undefined) return null; + this.stats.candidates++; + for (const other of stored.files) { + const then = this.previous.get(other); + if (then === undefined || then.text !== this.tables.get(other)?.text) return null; + } + if (this.changed.size > 0) { + const names = new Set(); + spelledNames(source, false, names); + // A string can name a member too (`value["name"]`, `keyof` types). + ts.walkPreorder( + source, + (child) => { + names.add((child as ts.StringLiteral).text); + }, + STRING_KINDS, + ); + for (const owner of owners) { + const ownerName = nameNodeOf(owner); + if (ownerName !== undefined && ts.isIdentifier(ownerName)) names.add(ownerName.text); + } + for (const spelled of names) if (this.changed.has(spelled)) return null; + } + return storedRecord(stored); + } + + /** Notes the record a unit of `file` lowered to, with the files its + * binary function names (RecordedUnit.files), or the stored record it + * applied (`applied`). A unit without a record is noted with null. */ + note( + name: string, + file: string, + record: UnitRecord | null, + applied: boolean, + files: readonly string[] | null, + ): void { + let units = this.written.get(file); + if (units === undefined) { + units = new Map(); + this.written.set(file, units); + } + if (record !== null) { + let unit = applied ? this.decoded.get(file)?.get(name) : undefined; + if (unit === undefined) { + const started = performance.now(); + unit = storedUnit(record, files, file); + this.stats.storeMs += performance.now() - started; + if (unit.bytes !== null) this.stats.binary++; + else if (unit.record.fn !== null) this.stats.json++; + } + units.set(name, unit); + } + if (applied) this.stats.hits++; + else { + this.stats.misses++; + this.rewritten.add(file); + } + } + + /** Writes the file entries of files whose units changed and the program + * entry. A file none of whose units this build lowered or applied keeps + * no entry. */ + finish(): void { + const started = performance.now(); + for (const [path, table] of this.tables) { + const units = this.written.get(path); + if (units === undefined || units.size === 0) continue; + const before = this.previous?.get(path); + const unchanged = + before !== undefined && + before.text === table.text && + before.entry !== "" && + !this.rewritten.has(path) && + this.decoded.get(path)?.size === units.size; + table.entry = unchanged ? before.entry : this.store.writeFile(encodeFileEntry(units)); + } + this.store.writeProgram(encodeProgramEntry(this.program, this.tables)); + this.stats.finishMs = performance.now() - started; + } +} + +/** The changed names of a build (see the comment at the top) from the + * previous build's tables and this build's. */ +function changedNames( + before: Map, + now: Map, + nodes: readonly DeclarationNode[], +): Set { + const surfaces = new Set(); + const direct = new Set(); + // Text reaches a member through a value whose type names its owner, so a + // member with another type stops the units that spell it, and an + // interface's member gives the interface another type. A class's own type + // changes with its header's facts. + const typeChanged = (declaration: { + readonly names: readonly string[]; + readonly member: boolean; + readonly owner: string | null; + }): void => { + for (const name of declaration.names) (declaration.member ? direct : surfaces).add(name); + if (declaration.owner !== null) surfaces.add(declaration.owner); + }; + const compare = (from: FileTable | undefined, to: FileTable | undefined): void => { + const old = new Map((from?.declarations ?? []).map((d) => [d.key, d])); + const seen = new Set(); + for (const declaration of to?.declarations ?? []) { + seen.add(declaration.key); + const previous = old.get(declaration.key); + if ( + previous === undefined || + previous.surface !== declaration.surface || + previous.facts !== declaration.facts + ) + typeChanged(declaration); + else if (previous.text !== declaration.text) + for (const name of declaration.names) direct.add(name); + else continue; + for (const name of declaration.assigns) direct.add(name); + for (const name of previous?.assigns ?? []) direct.add(name); + } + for (const declaration of from?.declarations ?? []) + if (!seen.has(declaration.key)) { + typeChanged(declaration); + for (const name of declaration.assigns) direct.add(name); + } + const sites = new Map((from?.namespaces ?? []).map((site) => [site.key, site.exports])); + for (const site of to?.namespaces ?? []) + if (sites.get(site.key) !== site.exports) for (const name of site.names) surfaces.add(name); + }; + for (const [path, table] of now) compare(before.get(path), table); + for (const [path, table] of before) if (!now.has(path)) compare(table, undefined); + if (surfaces.size > 0) { + // A declaration whose surface spells a changed name can have another + // type now (typeChanged); repeat until no surface adds a name. + const pending = nodes.map((declaration) => ({ + declaration, + spells: surfaceNames(declaration), + })); + for (let grew = true; grew;) { + grew = false; + for (let index = 0; index < pending.length; index++) { + const entry = pending[index]!; + let hit = false; + for (const name of entry.spells) + if (surfaces.has(name)) { + hit = true; + break; + } + if (!hit) continue; + typeChanged(entry.declaration); + // Each declaration joins once. + pending[index] = pending[pending.length - 1]!; + pending.pop(); + index--; + grew = true; + } + } + } + for (const name of direct) surfaces.add(name); + return surfaces; +} + +function encodeProgramEntry(program: string, tables: Map): Uint8Array { + const lines = [PROGRAM_MAGIC, program]; + for (const table of tables.values()) lines.push(JSON.stringify(table)); + return Buffer.from(lines.join("\n"), "utf8"); +} + +function readProgram(store: UnitStore): { program: string; tables: Map } | null { + const bytes = store.readProgram(); + if (bytes === null) return null; + try { + const lines = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength) + .toString("utf8") + .split("\n"); + if (lines[0] !== PROGRAM_MAGIC || lines.length < 2) return null; + const tables = new Map(); + for (const line of lines.slice(2)) { + const table = JSON.parse(line) as FileTable; + tables.set(table.path, table); + } + return { program: lines[1]!, tables }; + } catch { + // An unreadable entry is a miss. + return null; + } +} + +/** A file entry: per stored unit, the unit without its function's bytes as + * JSON, then the bytes when it has them. */ +function encodeFileEntry(units: Map): Uint8Array { + const w = new IrWriter(); + w.str(FILE_MAGIC); + w.length(units.size); + for (const unit of units.values()) { + w.str(JSON.stringify({ record: unit.record, files: unit.files })); + if (unit.bytes === null) w.byte(0); + else { + w.byte(1); + w.blob(unit.bytes); + } + } + return w.finish(); +} + +function readFileEntry(store: UnitStore, name: string): Map | null { + const bytes = store.readFile(name); + if (bytes === null) return null; + try { + const r = new IrReader(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)); + if (r.str() !== FILE_MAGIC) return null; + const units = new Map(); + for (let count = r.length(); count > 0; count--) { + const unit = JSON.parse(r.str()) as { record: UnitRecord; files: string[] }; + const fn = r.byte() === 0 ? null : r.blob(); + units.set(unit.record.name, { record: unit.record, files: unit.files, bytes: fn }); + } + return r.done() ? units : null; + } catch { + return null; + } +} + +/** Throws when a unit lowered to another record than its stored one + * (SCRIPTC_INCR_VERIFY=1), naming the unit and the first part that + * differs. `pending` is the stored record's entries the tables did not hold + * at the unit's turn. */ +export function verifyStoredUnit( + name: string, + storedRecord: StoredRecord, + pending: Readonly>, + lowered: UnitRecord | null, + reasons: readonly string[], +): void { + const stored = storedRecord.record; + const fail = (what: string): never => { + throw new InternalCompilerError( + `SCRIPTC_INCR_VERIFY: unit ${name} lowered to another ${what} than its stored record`, + ); + }; + if (lowered === null) return fail(`result (no record: ${reasons.join(", ")})`); + if (lowered.poisoned !== stored.poisoned) fail("poison"); + // As JSON text, which keeps key order, whichever encoding each record has. + const text = (fn: IrFunction | null): string | null => + fn === null ? null : (encodeUnitFunction(fn) ?? ""); + const loweredFn = lowered.fn === null ? null : decodeRecordFunction(lowered.fn); + if (text(loweredFn) !== text(storedRecord.fn())) fail("function"); + if (JSON.stringify(lowered.events) !== JSON.stringify(stored.events)) fail("event list"); + // A stored entry the tables hold already is one an earlier unit of this + // build added: applying the record leaves it as it is. + if (JSON.stringify(lowered.tables) !== JSON.stringify(pending)) fail("table entries"); +} diff --git a/packages/compiler/src/frontend/type-mapper.ts b/packages/compiler/src/frontend/type-mapper.ts index 02539b610..0fd084979 100644 --- a/packages/compiler/src/frontend/type-mapper.ts +++ b/packages/compiler/src/frontend/type-mapper.ts @@ -1,6 +1,6 @@ import { InternalCompilerError } from "../errors.js"; import { keyedHelperName, type KeyedNameOwner } from "./lowering/stable-names.js"; -import type { HeldContents, JournalTable } from "./lowering/unit-journal.js"; +import { contentDigest, type HeldContents, type JournalTable } from "./lowering/unit-journal.js"; import { mockCallRecordType, mockContextType } from "./mock-shapes.js"; import * as ts from "./ts7/adapter.js"; import { bodyReadsArguments } from "./arguments-usage.js"; @@ -716,9 +716,39 @@ export class UnionRegistry { * the key its arms give. A unit that adds a recursive union changes * journalSizes, so the unions past a unit's start are interned ones. */ journalTable(): JournalTable { + // The position of each union by id, for the unions indexed so far. + let indexes = new Map(); + let indexed = 0; + const indexOf = (id: string): number | undefined => { + if (indexed > this.unions.length) { + indexes = new Map(); + indexed = 0; + } + for (; indexed < this.unions.length; indexed++) + indexes.set(this.unions[indexed]!.id, indexed); + const index = indexes.get(id); + return index !== undefined && this.unions[index]?.id === id ? index : undefined; + }; return { name: "unions", size: () => this.unions.length, + digest: (key, before) => { + if (!this.byId.has(key)) return null; + let index = indexOf(key); + if (index === undefined) { + // A list that was cut and grown again holds other unions at the + // positions indexed before. + indexes = new Map(); + indexed = 0; + index = indexOf(key); + } + if (index === undefined || index >= before) return null; + return contentDigest(JSON.stringify(this.unions[index]!)); + }, + holds: (entry) => { + const def = JSON.parse(entry) as IrUnionDef; + return this.byKey.has(unionKey(def.arms, def.discriminant)); + }, entries: (from) => { const out: string[] = []; for (const def of this.unions.slice(from)) { @@ -729,6 +759,12 @@ export class UnionRegistry { } return out; }, + agrees: (entries) => + entries.every((entry) => { + const def = JSON.parse(entry) as IrUnionDef; + const held = this.byKey.get(unionKey(def.arms, def.discriminant)); + return held === undefined ? !this.byId.has(def.id) : held === def.id; + }), apply: (entries) => { for (const entry of entries) { const def = JSON.parse(entry) as IrUnionDef; diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index b837b74fd..4ed209262 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -23,6 +23,11 @@ import { checkerCacheIdentity } from "./frontend/ts7/checker-backend.js"; import { setLibDiagnosticsStore } from "./frontend/ts7/lib-diagnostics.js"; import { analyzeWithVerdictCache, fileVerdictStore } from "./coverage/verdict-cache.js"; import { coverageCompilerIdentity } from "./coverage/verdict-startup.js"; +import { + directoryUnitStore, + unitReuseEnabled, + type UnitStore, +} from "./frontend/lowering/unit-reuse.js"; import { commentOnlyReplay } from "./coverage/verdict-semantic.js"; import { llvmRefusalDiag, targetRefusalDiag } from "./backend/target-diagnostics.js"; import type { @@ -914,6 +919,37 @@ function runtimePackDiagnostic(error: RuntimePackError, entryPath: string): ScrD ); } +/** The unit store of a Node-hosted build (unit-reuse.ts), keyed by the + * compiler, the entry, the options that reach lowering and the target + * platform. */ +async function nodeUnitStore( + entryPath: string, + opts: CompileRequestOptions, + ffi: FfiProfile | null, + buildPlatform: string, +): Promise { + if (!unitReuseEnabled() || provenanceSources() !== null) return null; + const root = await prepareBuildCacheRoot(buildCacheRoot()); + if (root === null) return null; + const directory = join(root, "units-v1"); + const implementation = await coverageCompilerIdentity(directory); + if (implementation === null) return null; + const { outPath: _outPath, outDir: _outDir, ...lowering } = opts; + const key = createHash("sha256") + .update( + JSON.stringify({ + schema: 1, + entry: resolve(entryPath), + options: lowering, + ffi, + platform: buildPlatform, + compiler: implementation.digest, + }), + ) + .digest("hex"); + return directoryUnitStore(directory, key); +} + interface PreparedExecutable { llvmSource: LlvmInput; /** The kept LLVM text beside the executable, installed when the build ends. */ @@ -945,6 +981,9 @@ async function prepareExecutableInput( buildPlatform, nodeFrontend, timing, + new Map(), + true, + await nodeUnitStore(entryPath, opts, ffi, buildPlatform), ); if (!prepared.ok) return prepared; const { mod, sourceTexts } = prepared; diff --git a/packages/compiler/src/ir/codec.generated.ts b/packages/compiler/src/ir/codec.generated.ts new file mode 100644 index 000000000..40d5fe3d2 --- /dev/null +++ b/packages/compiler/src/ir/codec.generated.ts @@ -0,0 +1,5099 @@ +// Generated from packages/compiler/src/ir/ir.ts by scripts/generate-ir-codec.mjs. +// Run `pnpm generate:ir-codec` after changing the IR's types. +import type { IrBindingSource, IrClassDef, IrContracts, IrExpr, IrFfiCallbackParam, IrFfiContextParam, IrFfiImport, IrFfiParam, IrFfiReleaseParam, IrFunction, IrGlobal, IrLibCallback, IrLibExport, IrLibIdentity, IrLibSection, IrLibTrapOverlay, IrLocal, IrModule, IrNodeIslandModules, IrParam, IrRecordShape, IrStmt, IrType, IrUnionDef, IrUnionDiscriminant, IrWasmModule, IrWasmUntranslated, SrcLoc } from "./ir.js"; +import type { IrReader, IrWriter } from "./codec.js"; + +/** A digest of the coders below: an encoding written with other coders is + * refused instead of misread, also after a change that keeps IR_VERSION. */ +export const IR_CODEC_LAYOUT = "978da33223a8447d"; + +/** Writes `mod` field by field (codec.ts describes the bytes). */ +export function encodeIrModule(w: IrWriter, mod: IrModule): void { + encIrModule(w, mod); +} +/** Reads a module encodeIrModule wrote. */ +export function decodeIrModule(r: IrReader): IrModule { + return decIrModule(r); +} +/** Writes one function. */ +export function encodeIrFunction(w: IrWriter, fn: IrFunction): void { + encIrFunction(w, fn); +} +/** Reads a function encodeIrFunction wrote. */ +export function decodeIrFunction(r: IrReader): IrFunction { + return decIrFunction(r); +} + +const KEYS0: readonly string[] = ["irVersion","sourceFile","functions","classes","globals","records","unions","entry","workers","declBoundary","inferredFactFiles","declOrigins","lifetimeRegions","foreignElementKeys","threadSlotKeys","freezableArrays","sloppyDynWrites","untypedIntegrity","commonJsEntry","nodeIsland","ffiImports","lib","contracts","int32ArrayCandidates","wasmModules","wasmUntranslated","wasmCustomSections"]; +const KEYS1: readonly string[] = ["name","sourceName","ownsPrototype","params","returnType","locals","captures","classCaptures","body","loc","tier","speculativeDispatch","fastRead","async","generator","asyncCacheGlobal","asyncCycleCacheGlobal"]; +const KEYS2: readonly string[] = ["libnode","nodeVersion","modules"]; +const KEYS3: readonly string[] = ["profileName","prefix","initSymbol","sinkRegisterSymbol","collectSymbol","resultResetSymbol","threadInstances","callbackRegisterSymbol","callbacks","exports","trapOverlays","identity"]; +const KEYS4: readonly string[] = ["int32Fields","int32Params","int32Returns","int32Locals","int32Elements","noThrow","acyclic","immortalGlobals","programLifetime","inlineArrays"]; +const KEYS5: readonly string[] = ["kind","elem","optionalElements"]; +const KEYS6: readonly string[] = ["kind"]; +const KEYS7: readonly string[] = ["kind"]; +const KEYS8: readonly string[] = ["kind","elem"]; +const KEYS9: readonly string[] = ["kind"]; +const KEYS10: readonly string[] = ["kind"]; +const KEYS11: readonly string[] = ["kind"]; +const KEYS12: readonly string[] = ["kind"]; +const KEYS13: readonly string[] = ["kind","className"]; +const KEYS14: readonly string[] = ["kind"]; +const KEYS15: readonly string[] = ["kind"]; +const KEYS16: readonly string[] = ["kind"]; +const KEYS17: readonly string[] = ["kind"]; +const KEYS18: readonly string[] = ["kind"]; +const KEYS19: readonly string[] = ["kind"]; +const KEYS20: readonly string[] = ["kind"]; +const KEYS21: readonly string[] = ["kind"]; +const KEYS22: readonly string[] = ["kind"]; +const KEYS23: readonly string[] = ["kind","params","ret","rest","restAbi","argumentsAll"]; +const KEYS24: readonly string[] = ["kind","async","yieldT","retT","nextT"]; +const KEYS25: readonly string[] = ["kind"]; +const KEYS26: readonly string[] = ["kind"]; +const KEYS27: readonly string[] = ["kind"]; +const KEYS28: readonly string[] = ["kind"]; +const KEYS29: readonly string[] = ["kind"]; +const KEYS30: readonly string[] = ["kind"]; +const KEYS31: readonly string[] = ["kind","key","value"]; +const KEYS32: readonly string[] = ["kind","moduleId"]; +const KEYS33: readonly string[] = ["kind"]; +const KEYS34: readonly string[] = ["kind"]; +const KEYS35: readonly string[] = ["kind"]; +const KEYS36: readonly string[] = ["kind","className"]; +const KEYS37: readonly string[] = ["kind"]; +const KEYS38: readonly string[] = ["kind","inner"]; +const KEYS39: readonly string[] = ["kind","shapeId"]; +const KEYS40: readonly string[] = ["kind"]; +const KEYS41: readonly string[] = ["kind"]; +const KEYS42: readonly string[] = ["kind"]; +const KEYS43: readonly string[] = ["kind","elem"]; +const KEYS44: readonly string[] = ["kind"]; +const KEYS45: readonly string[] = ["kind"]; +const KEYS46: readonly string[] = ["kind"]; +const KEYS47: readonly string[] = ["kind"]; +const KEYS48: readonly string[] = ["kind"]; +const KEYS49: readonly string[] = ["kind"]; +const KEYS50: readonly string[] = ["kind","unionId"]; +const KEYS51: readonly string[] = ["kind"]; +const KEYS52: readonly string[] = ["kind"]; +const KEYS53: readonly string[] = ["file","start","end"]; +const KEYS54: readonly string[] = ["yieldT","nextT","resultType","closesSource","tag","inheritsTag"]; +const KEYS55: readonly string[] = ["name","localCaptures","localBaseCapture","baseValueGlobal","jsName","jsLength","ownStaticFields","prototypeDataHelper","instancePrototypeHelper","stringTagHelper","tracksOwnFields","errorNameAfter","errorBagOrder","symbolFields","runtime","arrayElem","arrayFields","arraySpecies","base","fields","methods","prototypeNames","abstract","abstractMethods","genericOf","loc"]; +const KEYS56: readonly string[] = ["id","name","type","mutable","tdz","initFlag","source"]; +const KEYS57: readonly string[] = ["id","fields","tuple","indexValue","freezable","declaredOrder","accessorOrder","orderVariants"]; +const KEYS58: readonly string[] = ["id","arms","discriminant"]; +const KEYS59: readonly string[] = ["file","pkg"]; +const KEYS60: readonly string[] = ["root","files","links","location"]; +const KEYS61: readonly string[] = ["name","symbol","library","callbackOperation","callbackTarget","params","returns"]; +const KEYS62: readonly string[] = ["buildIdSymbol","abiVersionSymbol","buildId","abiVersion"]; +const KEYS63: readonly string[] = ["sha256","bytes"]; +const KEYS64: readonly string[] = ["sha256","size","reason"]; +const KEYS65: readonly string[] = ["localId","name","type","owned"]; +const KEYS66: readonly string[] = ["id","name","type","mutable","source","boxed","tdz"]; +const KEYS67: readonly string[] = ["localId","name","type","owned","slot"]; +const KEYS68: readonly string[] = ["kind","arr","index","loc"]; +const KEYS69: readonly string[] = ["kind","arr","index","value","loc"]; +const KEYS70: readonly string[] = ["kind","arr","length","loc","pop","reserve"]; +const KEYS71: readonly string[] = ["kind","arr","index","loc"]; +const KEYS72: readonly string[] = ["kind","localId","value","initializes","loc"]; +const KEYS73: readonly string[] = ["kind","labels","body","loc"]; +const KEYS74: readonly string[] = ["kind","label","loc"]; +const KEYS75: readonly string[] = ["kind","arr","index","value","loc"]; +const KEYS76: readonly string[] = ["kind","label","loc"]; +const KEYS77: readonly string[] = ["kind","body","cond","labels","loc"]; +const KEYS78: readonly string[] = ["kind","expr","loc"]; +const KEYS79: readonly string[] = ["kind","obj","className","field","value","sloppy","define","attributesChecked","loc"]; +const KEYS80: readonly string[] = ["kind","init","cond","update","body","labels","loc"]; +const KEYS81: readonly string[] = ["kind","localId","iterable","body","labels","loc"]; +const KEYS82: readonly string[] = ["kind","cond","then","else_","loc"]; +const KEYS83: readonly string[] = ["kind","obj","shapeId","key","loc"]; +const KEYS84: readonly string[] = ["kind","obj","shapeId","key","value","overflowOnly","loc"]; +const KEYS85: readonly string[] = ["kind","obj","shapeId","field","value","loc"]; +const KEYS86: readonly string[] = ["kind","localId","loc"]; +const KEYS87: readonly string[] = ["kind","value","awaited","loc"]; +const KEYS88: readonly string[] = ["kind","code","message","loc"]; +const KEYS89: readonly string[] = ["kind","disc","cases","labels","loc"]; +const KEYS90: readonly string[] = ["kind","value","loc"]; +const KEYS91: readonly string[] = ["kind","tryBody","catchBody","catchLocalId","dropFinallyErrors","finallyBody","suppressFinallyErrors","loc"]; +const KEYS92: readonly string[] = ["kind","localId","init","loc"]; +const KEYS93: readonly string[] = ["kind","cond","body","labels","loc"]; +const KEYS94: readonly string[] = ["kind","shapeId"]; +const KEYS95: readonly string[] = ["loc","scope"]; +const KEYS96: readonly string[] = ["field","cases"]; +const KEYS97: readonly string[] = ["name","slot","params","returns","unregisteredTrap"]; +const KEYS98: readonly string[] = ["symbol","fnName","params","returns","inboundBytesTrap","inboundIntTrap","inboundCstringTrap"]; +const KEYS99: readonly string[] = ["code","teaching","remediation"]; +const KEYS100: readonly string[] = ["kind","method","receiver","args","type","loc"]; +const KEYS101: readonly string[] = ["kind","arr","index","type","loc"]; +const KEYS102: readonly string[] = ["kind","arr","index","type","loc"]; +const KEYS103: readonly string[] = ["kind","elems","spreads","capacity","type","loc"]; +const KEYS104: readonly string[] = ["kind","length","type","loc"]; +const KEYS105: readonly string[] = ["kind","arr","index","type","loc"]; +const KEYS106: readonly string[] = ["kind","localId","value","type","loc"]; +const KEYS107: readonly string[] = ["kind","value","type","adopt","loc"]; +const KEYS108: readonly string[] = ["kind","value","promiseTag","type","loc"]; +const KEYS109: readonly string[] = ["kind","op","left","right","type","loc"]; +const KEYS110: readonly string[] = ["kind","value","type","loc"]; +const KEYS111: readonly string[] = ["kind","method","receiver","args","type","loc","invalidNaN"]; +const KEYS112: readonly string[] = ["kind","source","from","type","loc"]; +const KEYS113: readonly string[] = ["kind","callee","args","type","loc"]; +const KEYS114: readonly string[] = ["kind","callee","receiver","args","type","loc"]; +const KEYS115: readonly string[] = ["kind","value","className","type","loc"]; +const KEYS116: readonly string[] = ["kind","value","type","loc"]; +const KEYS117: readonly string[] = ["kind","value","test","className","negated","type","loc"]; +const KEYS118: readonly string[] = ["kind","value","type","loc"]; +const KEYS119: readonly string[] = ["kind","id","type","loc"]; +const KEYS120: readonly string[] = ["kind","className","type","loc","captures"]; +const KEYS121: readonly string[] = ["kind","fnName","captures","adapts","type","loc"]; +const KEYS122: readonly string[] = ["kind","value","type","loc"]; +const KEYS123: readonly string[] = ["kind","elems","type","loc"]; +const KEYS124: readonly string[] = ["kind","callee","receiver","calleeName","calleeNameValue","args","spreads","type","loc"]; +const KEYS125: readonly string[] = ["kind","value","preserveRefs","adopt","exact","inferredSite","origin","type","loc"]; +const KEYS126: readonly string[] = ["kind","value","spelling","firstProp","type","loc"]; +const KEYS127: readonly string[] = ["kind","value","type","loc","liveRef","fnName","fnLength"]; +const KEYS128: readonly string[] = ["kind","key","negated","value","type","loc"]; +const KEYS129: readonly string[] = ["kind","recv","method","calleeName","calleeNameValue","args","type","loc"]; +const KEYS130: readonly string[] = ["kind","value","count","notIterableMessage","type","loc"]; +const KEYS131: readonly string[] = ["kind","key","optional","value","type","loc"]; +const KEYS132: readonly string[] = ["kind","fields","type","loc"]; +const KEYS133: readonly string[] = ["kind","left","right","negated","type","loc"]; +const KEYS134: readonly string[] = ["kind","test","bytesElem","negated","value","type","loc"]; +const KEYS135: readonly string[] = ["kind","import","args","type","loc"]; +const KEYS136: readonly string[] = ["kind","unionId","type","loc"]; +const KEYS137: readonly string[] = ["kind","obj","className","field","type","loc"]; +const KEYS138: readonly string[] = ["kind","op","prefix","obj","className","field","fieldDyn","type","loc"]; +const KEYS139: readonly string[] = ["kind","mode","gen","arg","type","loc"]; +const KEYS140: readonly string[] = ["kind","op","prefix","localId","type","loc"]; +const KEYS141: readonly string[] = ["kind","value","className","type","loc"]; +const KEYS142: readonly string[] = ["kind","value","classValue","type","loc"]; +const KEYS143: readonly string[] = ["kind","name","args","type","loc"]; +const KEYS144: readonly string[] = ["kind","value","indent","circular","type","loc"]; +const KEYS145: readonly string[] = ["kind","fn","args","type","loc","prototypeIdentityOnly","realTimer","assertOptions"]; +const KEYS146: readonly string[] = ["kind","op","left","right","type","loc"]; +const KEYS147: readonly string[] = ["kind","method","receiver","args","type","loc"]; +const KEYS148: readonly string[] = ["kind","seed","type","loc"]; +const KEYS149: readonly string[] = ["kind","moduleId","type","loc"]; +const KEYS150: readonly string[] = ["kind","className","args","type","loc"]; +const KEYS151: readonly string[] = ["kind","executor","type","loc"]; +const KEYS152: readonly string[] = ["kind","callee","args","type","loc"]; +const KEYS153: readonly string[] = ["kind","left","right","type","loc"]; +const KEYS154: readonly string[] = ["kind","value","spelling","type","loc"]; +const KEYS155: readonly string[] = ["kind","id","receiver","body","type","loc"]; +const KEYS156: readonly string[] = ["kind","left","right","retag","type","loc"]; +const KEYS157: readonly string[] = ["kind","value","type","loc"]; +const KEYS158: readonly string[] = ["kind","type","loc"]; +const KEYS159: readonly string[] = ["kind","source","overrides","type","loc"]; +const KEYS160: readonly string[] = ["kind","obj","shapeId","field","type","loc"]; +const KEYS161: readonly string[] = ["kind","obj","shapeId","field","type","loc"]; +const KEYS162: readonly string[] = ["kind","obj","shapeId","key","overflowOnly","missNaN","type","loc"]; +const KEYS163: readonly string[] = ["kind","fields","nullProto","type","loc"]; +const KEYS164: readonly string[] = ["kind","obj","shapeId","type","loc"]; +const KEYS165: readonly string[] = ["kind","obj","shapeId","key","type","loc"]; +const KEYS166: readonly string[] = ["kind","obj","shapeId","type","loc"]; +const KEYS167: readonly string[] = ["kind","method","receiver","args","type","loc"]; +const KEYS168: readonly string[] = ["kind","pattern","flags","type","loc"]; +const KEYS169: readonly string[] = ["kind","type","loc"]; +const KEYS170: readonly string[] = ["kind","stmts","result","generatorDelegate","diverges","type","loc"]; +const KEYS171: readonly string[] = ["kind","method","receiver","args","type","loc"]; +const KEYS172: readonly string[] = ["kind","seed","type","loc"]; +const KEYS173: readonly string[] = ["kind","op","left","right","type","loc"]; +const KEYS174: readonly string[] = ["kind","left","right","type","loc"]; +const KEYS175: readonly string[] = ["kind","negated","left","right","type","loc"]; +const KEYS176: readonly string[] = ["kind","method","receiver","args","type","loc"]; +const KEYS177: readonly string[] = ["kind","value","type","loc"]; +const KEYS178: readonly string[] = ["kind","key","cooked","type","loc"]; +const KEYS179: readonly string[] = ["kind","cond","then","else_","type","loc"]; +const KEYS180: readonly string[] = ["kind","operand","type","loc"]; +const KEYS181: readonly string[] = ["kind","operand","type","loc"]; +const KEYS182: readonly string[] = ["kind","op","operand","type","loc"]; +const KEYS183: readonly string[] = ["kind","unionId","field","value","type","loc"]; +const KEYS184: readonly string[] = ["kind","unionId","negated","sameValue","left","right","type","loc"]; +const KEYS185: readonly string[] = ["kind","unionId","tag","union","func","negated","type","loc"]; +const KEYS186: readonly string[] = ["kind","unionId","tag","negated","value","type","loc"]; +const KEYS187: readonly string[] = ["kind","unionId","key","value","type","loc"]; +const KEYS188: readonly string[] = ["kind","unionId","tag","value","type","loc"]; +const KEYS189: readonly string[] = ["kind","fromId","unionId","tags","value","type","loc"]; +const KEYS190: readonly string[] = ["kind","unionId","tag","value","type","loc"]; +const KEYS191: readonly string[] = ["kind","unit","type","loc"]; +const KEYS192: readonly string[] = ["kind","value","type","loc"]; +const KEYS193: readonly string[] = ["kind","localId","type","loc"]; +const KEYS194: readonly string[] = ["kind","className","method","args","type","loc","targets"]; +const KEYS195: readonly string[] = ["kind","value","awaited","delegated","captureCompletion","type","loc"]; +const KEYS196: readonly string[] = ["name","type"]; +const KEYS197: readonly string[] = ["name","type"]; +const KEYS198: readonly string[] = ["path","size","sha256","placeholder"]; +const KEYS199: readonly string[] = ["path","target"]; +const KEYS200: readonly string[] = ["diff","skipPrototype"]; +const KEYS201: readonly string[] = ["returnType"]; +const KEYS202: readonly string[] = ["test","body"]; +const KEYS203: readonly string[] = ["field","globalId"]; +const KEYS204: readonly string[] = ["field","wellKnown"]; +const KEYS205: readonly string[] = ["tag","values"]; +const KEYS206: readonly string[] = ["context"]; +const KEYS207: readonly string[] = ["callback"]; +const KEYS208: readonly string[] = ["callback"]; +const KEYS209: readonly string[] = ["arg","what"]; +const KEYS210: readonly string[] = ["key","value"]; +const KEYS211: readonly string[] = ["key","value"]; +const KEYS212: readonly string[] = ["name","value"]; +const KEYS213: readonly string[] = ["name","value","overflow"]; +const KEYS214: readonly string[] = ["id","params","returns","lifetime","invoke"]; +const KEYS215: readonly string[] = ["release","params","returns"]; + +function encIrModule(w: IrWriter, v: IrModule): void { + w.keys(Object.keys(v), KEYS0); + w.num(v.irVersion); + w.str(v.sourceFile); + encT2(w, v.functions); + if (v.classes === undefined) w.byte(0); + else { + w.byte(1); + encT3(w, v.classes); + } + if (v.globals === undefined) w.byte(0); + else { + w.byte(1); + encT4(w, v.globals); + } + if (v.records === undefined) w.byte(0); + else { + w.byte(1); + encT5(w, v.records); + } + if (v.unions === undefined) w.byte(0); + else { + w.byte(1); + encT6(w, v.unions); + } + w.str(v.entry); + if (v.workers === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.workers); + } + if (v.declBoundary === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.declBoundary); + } + if (v.inferredFactFiles === undefined) w.byte(0); + else { + w.byte(1); + encT7(w, v.inferredFactFiles); + } + if (v.declOrigins === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.declOrigins); + } + if (v.lifetimeRegions === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.lifetimeRegions); + } + if (v.foreignElementKeys === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.foreignElementKeys); + } + if (v.threadSlotKeys === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.threadSlotKeys); + } + if (v.freezableArrays === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.freezableArrays); + } + if (v.sloppyDynWrites === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.sloppyDynWrites); + } + if (v.untypedIntegrity === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.untypedIntegrity); + } + if (v.commonJsEntry === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.commonJsEntry); + } + if (v.nodeIsland === undefined) w.byte(0); + else { + w.byte(1); + encT9(w, v.nodeIsland); + } + if (v.ffiImports === undefined) w.byte(0); + else { + w.byte(1); + encT10(w, v.ffiImports); + } + if (v.lib === undefined) w.byte(0); + else { + w.byte(1); + encIrLibSection(w, v.lib); + } + if (v.contracts === undefined) w.byte(0); + else { + w.byte(1); + encIrContracts(w, v.contracts); + } + if (v.int32ArrayCandidates === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.int32ArrayCandidates); + } + if (v.wasmModules === undefined) w.byte(0); + else { + w.byte(1); + encT13(w, v.wasmModules); + } + if (v.wasmUntranslated === undefined) w.byte(0); + else { + w.byte(1); + encT14(w, v.wasmUntranslated); + } + if (v.wasmCustomSections === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.wasmCustomSections); + } +} +function decIrModule(r: IrReader): IrModule { + const f0 = (r.num() as NonNullable); + const f1 = r.str(); + const f2 = decT2(r); + const f3 = r.tag(2, "IrModule.classes") === 0 ? undefined : decT3(r); + const f4 = r.tag(2, "IrModule.globals") === 0 ? undefined : decT4(r); + const f5 = r.tag(2, "IrModule.records") === 0 ? undefined : decT5(r); + const f6 = r.tag(2, "IrModule.unions") === 0 ? undefined : decT6(r); + const f7 = r.str(); + const f8 = r.tag(2, "IrModule.workers") === 0 ? undefined : r.bool(); + const f9 = r.tag(2, "IrModule.declBoundary") === 0 ? undefined : (r.bool() as NonNullable); + const f10 = r.tag(2, "IrModule.inferredFactFiles") === 0 ? undefined : decT7(r); + const f11 = r.tag(2, "IrModule.declOrigins") === 0 ? undefined : decT8(r); + const f12 = r.tag(2, "IrModule.lifetimeRegions") === 0 ? undefined : (r.bool() as NonNullable); + const f13 = r.tag(2, "IrModule.foreignElementKeys") === 0 ? undefined : decT8(r); + const f14 = r.tag(2, "IrModule.threadSlotKeys") === 0 ? undefined : decT8(r); + const f15 = r.tag(2, "IrModule.freezableArrays") === 0 ? undefined : decT8(r); + const f16 = r.tag(2, "IrModule.sloppyDynWrites") === 0 ? undefined : decT8(r); + const f17 = r.tag(2, "IrModule.untypedIntegrity") === 0 ? undefined : (r.bool() as NonNullable); + const f18 = r.tag(2, "IrModule.commonJsEntry") === 0 ? undefined : r.bool(); + const f19 = r.tag(2, "IrModule.nodeIsland") === 0 ? undefined : decT9(r); + const f20 = r.tag(2, "IrModule.ffiImports") === 0 ? undefined : decT10(r); + const f21 = r.tag(2, "IrModule.lib") === 0 ? undefined : decIrLibSection(r); + const f22 = r.tag(2, "IrModule.contracts") === 0 ? undefined : decIrContracts(r); + const f23 = r.tag(2, "IrModule.int32ArrayCandidates") === 0 ? undefined : decT8(r); + const f24 = r.tag(2, "IrModule.wasmModules") === 0 ? undefined : decT13(r); + const f25 = r.tag(2, "IrModule.wasmUntranslated") === 0 ? undefined : decT14(r); + const f26 = r.tag(2, "IrModule.wasmCustomSections") === 0 ? undefined : r.bool(); + const o: IrModule = { irVersion: f0, sourceFile: f1, functions: f2, ...(f3 !== undefined ? { classes: f3 } : {}), ...(f4 !== undefined ? { globals: f4 } : {}), ...(f5 !== undefined ? { records: f5 } : {}), ...(f6 !== undefined ? { unions: f6 } : {}), entry: f7, ...(f8 !== undefined ? { workers: f8 } : {}), ...(f9 !== undefined ? { declBoundary: f9 } : {}), ...(f10 !== undefined ? { inferredFactFiles: f10 } : {}), ...(f11 !== undefined ? { declOrigins: f11 } : {}), ...(f12 !== undefined ? { lifetimeRegions: f12 } : {}), ...(f13 !== undefined ? { foreignElementKeys: f13 } : {}), ...(f14 !== undefined ? { threadSlotKeys: f14 } : {}), ...(f15 !== undefined ? { freezableArrays: f15 } : {}), ...(f16 !== undefined ? { sloppyDynWrites: f16 } : {}), ...(f17 !== undefined ? { untypedIntegrity: f17 } : {}), ...(f18 !== undefined ? { commonJsEntry: f18 } : {}), ...(f19 !== undefined ? { nodeIsland: f19 } : {}), ...(f20 !== undefined ? { ffiImports: f20 } : {}), ...(f21 !== undefined ? { lib: f21 } : {}), ...(f22 !== undefined ? { contracts: f22 } : {}), ...(f23 !== undefined ? { int32ArrayCandidates: f23 } : {}), ...(f24 !== undefined ? { wasmModules: f24 } : {}), ...(f25 !== undefined ? { wasmUntranslated: f25 } : {}), ...(f26 !== undefined ? { wasmCustomSections: f26 } : {}) }; + return o; +} + +function encIrFunction(w: IrWriter, v: IrFunction): void { + w.keys(Object.keys(v), KEYS1); + w.str(v.name); + if (v.sourceName === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.sourceName); + } + if (v.ownsPrototype === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.ownsPrototype); + } + encT15(w, v.params); + encIrType(w, v.returnType); + encT17(w, v.locals); + if (v.captures === undefined) w.byte(0); + else { + w.byte(1); + encT15(w, v.captures); + } + if (v.classCaptures === undefined) w.byte(0); + else { + w.byte(1); + encT18(w, v.classCaptures); + } + encT19(w, v.body); + encSrcLoc(w, v.loc); + if (v.tier === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.tier); + } + if (v.speculativeDispatch === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.speculativeDispatch); + } + if (v.fastRead === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.fastRead); + } + if (v.async === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.async); + } + if (v.generator === undefined) w.byte(0); + else { + w.byte(1); + encT21(w, v.generator); + } + if (v.asyncCacheGlobal === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.asyncCacheGlobal); + } + if (v.asyncCycleCacheGlobal === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.asyncCycleCacheGlobal); + } +} +function decIrFunction(r: IrReader): IrFunction { + const f0 = r.str(); + const f1 = r.tag(2, "IrFunction.sourceName") === 0 ? undefined : r.str(); + const f2 = r.tag(2, "IrFunction.ownsPrototype") === 0 ? undefined : (r.bool() as NonNullable); + const f3 = decT15(r); + const f4 = decIrType(r); + const f5 = decT17(r); + const f6 = r.tag(2, "IrFunction.captures") === 0 ? undefined : decT15(r); + const f7 = r.tag(2, "IrFunction.classCaptures") === 0 ? undefined : decT18(r); + const f8 = decT19(r); + const f9 = decSrcLoc(r); + const f10 = r.tag(2, "IrFunction.tier") === 0 ? undefined : (r.str() as NonNullable); + const f11 = r.tag(2, "IrFunction.speculativeDispatch") === 0 ? undefined : r.bool(); + const f12 = r.tag(2, "IrFunction.fastRead") === 0 ? undefined : (r.str() as NonNullable); + const f13 = r.tag(2, "IrFunction.async") === 0 ? undefined : (r.bool() as NonNullable); + const f14 = r.tag(2, "IrFunction.generator") === 0 ? undefined : decT21(r); + const f15 = r.tag(2, "IrFunction.asyncCacheGlobal") === 0 ? undefined : r.str(); + const f16 = r.tag(2, "IrFunction.asyncCycleCacheGlobal") === 0 ? undefined : r.str(); + const o: IrFunction = { name: f0, ...(f1 !== undefined ? { sourceName: f1 } : {}), ...(f2 !== undefined ? { ownsPrototype: f2 } : {}), params: f3, returnType: f4, locals: f5, ...(f6 !== undefined ? { captures: f6 } : {}), ...(f7 !== undefined ? { classCaptures: f7 } : {}), body: f8, loc: f9, ...(f10 !== undefined ? { tier: f10 } : {}), ...(f11 !== undefined ? { speculativeDispatch: f11 } : {}), ...(f12 !== undefined ? { fastRead: f12 } : {}), ...(f13 !== undefined ? { async: f13 } : {}), ...(f14 !== undefined ? { generator: f14 } : {}), ...(f15 !== undefined ? { asyncCacheGlobal: f15 } : {}), ...(f16 !== undefined ? { asyncCycleCacheGlobal: f16 } : {}) }; + return o; +} + +function encT2(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrFunction(w, item); + } +} +function decT2(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrFunction(r)); + return out; +} + +function encT3(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrClassDef(w, item); + } +} +function decT3(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrClassDef(r)); + return out; +} + +function encT4(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrGlobal(w, item); + } +} +function decT4(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrGlobal(r)); + return out; +} + +function encT5(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrRecordShape(w, item); + } +} +function decT5(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrRecordShape(r)); + return out; +} + +function encT6(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrUnionDef(w, item); + } +} +function decT6(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrUnionDef(r)); + return out; +} + +function encT7(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT26(w, item); + } +} +function decT7(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT26(r)); + return out; +} + +function encT8(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + w.str(item); + } +} +function decT8(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(r.str()); + return out; +} + +function encT9(w: IrWriter, v: NonNullable): void { + w.keys(Object.keys(v), KEYS2); + w.str(v.libnode); + w.str(v.nodeVersion); + if (v.modules === undefined) w.byte(0); + else { + w.byte(1); + encIrNodeIslandModules(w, v.modules); + } +} +function decT9(r: IrReader): NonNullable { + const f0 = r.str(); + const f1 = r.str(); + const f2 = r.tag(2, "IrModule.nodeIsland.modules") === 0 ? undefined : decIrNodeIslandModules(r); + const o: NonNullable = { libnode: f0, nodeVersion: f1, ...(f2 !== undefined ? { modules: f2 } : {}) }; + return o; +} + +function encT10(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrFfiImport(w, item); + } +} +function decT10(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrFfiImport(r)); + return out; +} + +function encIrLibSection(w: IrWriter, v: IrLibSection): void { + w.keys(Object.keys(v), KEYS3); + w.str(v.profileName); + w.str(v.prefix); + w.str(v.initSymbol); + w.str(v.sinkRegisterSymbol); + if (v.collectSymbol === null) w.byte(0); + else { + w.byte(1); + w.str(v.collectSymbol); + } + if (v.resultResetSymbol === null) w.byte(0); + else { + w.byte(1); + w.str(v.resultResetSymbol); + } + w.bool(v.threadInstances); + if (v.callbackRegisterSymbol === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.callbackRegisterSymbol); + } + if (v.callbacks === undefined) w.byte(0); + else { + w.byte(1); + encT29(w, v.callbacks); + } + encT30(w, v.exports); + encT31(w, v.trapOverlays); + if (v.identity === undefined) w.byte(0); + else { + w.byte(1); + encIrLibIdentity(w, v.identity); + } +} +function decIrLibSection(r: IrReader): IrLibSection { + const f0 = r.str(); + const f1 = r.str(); + const f2 = r.str(); + const f3 = r.str(); + const f4 = r.tag(2, "IrModule.lib.collectSymbol") === 0 ? null : r.str(); + const f5 = r.tag(2, "IrModule.lib.resultResetSymbol") === 0 ? null : r.str(); + const f6 = r.bool(); + const f7 = r.tag(2, "IrModule.lib.callbackRegisterSymbol") === 0 ? undefined : r.str(); + const f8 = r.tag(2, "IrModule.lib.callbacks") === 0 ? undefined : decT29(r); + const f9 = decT30(r); + const f10 = decT31(r); + const f11 = r.tag(2, "IrModule.lib.identity") === 0 ? undefined : decIrLibIdentity(r); + const o: IrLibSection = { profileName: f0, prefix: f1, initSymbol: f2, sinkRegisterSymbol: f3, collectSymbol: f4, resultResetSymbol: f5, threadInstances: f6, ...(f7 !== undefined ? { callbackRegisterSymbol: f7 } : {}), ...(f8 !== undefined ? { callbacks: f8 } : {}), exports: f9, trapOverlays: f10, ...(f11 !== undefined ? { identity: f11 } : {}) }; + return o; +} + +function encIrContracts(w: IrWriter, v: IrContracts): void { + w.keys(Object.keys(v), KEYS4); + if (v.int32Fields === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.int32Fields); + } + if (v.int32Params === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.int32Params); + } + if (v.int32Returns === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.int32Returns); + } + if (v.int32Locals === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.int32Locals); + } + if (v.int32Elements === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.int32Elements); + } + if (v.noThrow === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.noThrow); + } + if (v.acyclic === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.acyclic); + } + if (v.immortalGlobals === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.immortalGlobals); + } + if (v.programLifetime === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.programLifetime); + } + if (v.inlineArrays === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.inlineArrays); + } +} +function decIrContracts(r: IrReader): IrContracts { + const f0 = r.tag(2, "IrModule.contracts.int32Fields") === 0 ? undefined : decT8(r); + const f1 = r.tag(2, "IrModule.contracts.int32Params") === 0 ? undefined : decT8(r); + const f2 = r.tag(2, "IrModule.contracts.int32Returns") === 0 ? undefined : decT8(r); + const f3 = r.tag(2, "IrModule.contracts.int32Locals") === 0 ? undefined : decT8(r); + const f4 = r.tag(2, "IrModule.contracts.int32Elements") === 0 ? undefined : decT8(r); + const f5 = r.tag(2, "IrModule.contracts.noThrow") === 0 ? undefined : decT8(r); + const f6 = r.tag(2, "IrModule.contracts.acyclic") === 0 ? undefined : decT8(r); + const f7 = r.tag(2, "IrModule.contracts.immortalGlobals") === 0 ? undefined : decT8(r); + const f8 = r.tag(2, "IrModule.contracts.programLifetime") === 0 ? undefined : decT8(r); + const f9 = r.tag(2, "IrModule.contracts.inlineArrays") === 0 ? undefined : decT8(r); + const o: IrContracts = { ...(f0 !== undefined ? { int32Fields: f0 } : {}), ...(f1 !== undefined ? { int32Params: f1 } : {}), ...(f2 !== undefined ? { int32Returns: f2 } : {}), ...(f3 !== undefined ? { int32Locals: f3 } : {}), ...(f4 !== undefined ? { int32Elements: f4 } : {}), ...(f5 !== undefined ? { noThrow: f5 } : {}), ...(f6 !== undefined ? { acyclic: f6 } : {}), ...(f7 !== undefined ? { immortalGlobals: f7 } : {}), ...(f8 !== undefined ? { programLifetime: f8 } : {}), ...(f9 !== undefined ? { inlineArrays: f9 } : {}) }; + return o; +} + +function encT13(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrWasmModule(w, item); + } +} +function decT13(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrWasmModule(r)); + return out; +} + +function encT14(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrWasmUntranslated(w, item); + } +} +function decT14(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrWasmUntranslated(r)); + return out; +} + +function encT15(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrParam(w, item); + } +} +function decT15(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrParam(r)); + return out; +} + +function encIrType(w: IrWriter, v: IrType): void { + switch (v.kind) { + case "array": { + w.length(0); + w.keys(Object.keys(v), KEYS5); + encIrType(w, v.elem); + if (v.optionalElements === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.optionalElements); + } + return; + } + case "bigint": { + w.length(1); + w.keys(Object.keys(v), KEYS6); + return; + } + case "bool": { + w.length(2); + w.keys(Object.keys(v), KEYS7); + return; + } + case "bytes": { + w.length(3); + w.keys(Object.keys(v), KEYS8); + w.str(v.elem); + return; + } + case "caught": { + w.length(4); + w.keys(Object.keys(v), KEYS9); + return; + } + case "child": { + w.length(5); + w.keys(Object.keys(v), KEYS10); + return; + } + case "childStream": { + w.length(6); + w.keys(Object.keys(v), KEYS11); + return; + } + case "childWriter": { + w.length(7); + w.keys(Object.keys(v), KEYS12); + return; + } + case "classval": { + w.length(8); + w.keys(Object.keys(v), KEYS13); + w.str(v.className); + return; + } + case "cryptoHash": { + w.length(9); + w.keys(Object.keys(v), KEYS14); + return; + } + case "cryptoHmac": { + w.length(10); + w.keys(Object.keys(v), KEYS15); + return; + } + case "date": { + w.length(11); + w.keys(Object.keys(v), KEYS16); + return; + } + case "dgramSocket": { + w.length(12); + w.keys(Object.keys(v), KEYS17); + return; + } + case "dir": { + w.length(13); + w.keys(Object.keys(v), KEYS18); + return; + } + case "dyn": { + w.length(14); + w.keys(Object.keys(v), KEYS19); + return; + } + case "f64": { + w.length(15); + w.keys(Object.keys(v), KEYS20); + return; + } + case "fileHandle": { + w.length(16); + w.keys(Object.keys(v), KEYS21); + return; + } + case "fsWatcher": { + w.length(17); + w.keys(Object.keys(v), KEYS22); + return; + } + case "func": { + w.length(18); + w.keys(Object.keys(v), KEYS23); + encT36(w, v.params); + encIrType(w, v.ret); + if (v.rest === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.rest); + } + if (v.restAbi === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.restAbi); + } + if (v.argumentsAll === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.argumentsAll); + } + return; + } + case "generator": { + w.length(19); + w.keys(Object.keys(v), KEYS24); + if (v.async === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.async); + } + encIrType(w, v.yieldT); + encIrType(w, v.retT); + encIrType(w, v.nextT); + return; + } + case "http2Session": { + w.length(20); + w.keys(Object.keys(v), KEYS25); + return; + } + case "http2Stream": { + w.length(21); + w.keys(Object.keys(v), KEYS26); + return; + } + case "httpClientReq": { + w.length(22); + w.keys(Object.keys(v), KEYS27); + return; + } + case "httpReq": { + w.length(23); + w.keys(Object.keys(v), KEYS28); + return; + } + case "httpRes": { + w.length(24); + w.keys(Object.keys(v), KEYS29); + return; + } + case "jsval": { + w.length(25); + w.keys(Object.keys(v), KEYS30); + return; + } + case "map": { + w.length(26); + w.keys(Object.keys(v), KEYS31); + encIrType(w, v.key); + encIrType(w, v.value); + return; + } + case "moduleNs": { + w.length(27); + w.keys(Object.keys(v), KEYS32); + w.str(v.moduleId); + return; + } + case "netServer": { + w.length(28); + w.keys(Object.keys(v), KEYS33); + return; + } + case "netSocket": { + w.length(29); + w.keys(Object.keys(v), KEYS34); + return; + } + case "nullT": { + w.length(30); + w.keys(Object.keys(v), KEYS35); + return; + } + case "object": { + w.length(31); + w.keys(Object.keys(v), KEYS36); + w.str(v.className); + return; + } + case "procStream": { + w.length(32); + w.keys(Object.keys(v), KEYS37); + return; + } + case "promise": { + w.length(33); + w.keys(Object.keys(v), KEYS38); + encIrType(w, v.inner); + return; + } + case "record": { + w.length(34); + w.keys(Object.keys(v), KEYS39); + w.str(v.shapeId); + return; + } + case "regex": { + w.length(35); + w.keys(Object.keys(v), KEYS40); + return; + } + case "searchParams": { + w.length(36); + w.keys(Object.keys(v), KEYS41); + return; + } + case "secureCtx": { + w.length(37); + w.keys(Object.keys(v), KEYS42); + return; + } + case "set": { + w.length(38); + w.keys(Object.keys(v), KEYS43); + encIrType(w, v.elem); + return; + } + case "spawnRes": { + w.length(39); + w.keys(Object.keys(v), KEYS44); + return; + } + case "stats": { + w.length(40); + w.keys(Object.keys(v), KEYS45); + return; + } + case "string": { + w.length(41); + w.keys(Object.keys(v), KEYS46); + return; + } + case "symbol": { + w.length(42); + w.keys(Object.keys(v), KEYS47); + return; + } + case "testCtx": { + w.length(43); + w.keys(Object.keys(v), KEYS48); + return; + } + case "undefinedT": { + w.length(44); + w.keys(Object.keys(v), KEYS49); + return; + } + case "union": { + w.length(45); + w.keys(Object.keys(v), KEYS50); + w.str(v.unionId); + return; + } + case "url": { + w.length(46); + w.keys(Object.keys(v), KEYS51); + return; + } + case "void": { + w.length(47); + w.keys(Object.keys(v), KEYS52); + return; + } + } + w.refuse("IrType kind " + String((v as { kind: unknown }).kind)); +} +function decIrType(r: IrReader): IrType { + switch (r.length()) { + case 0: { + const f0 = decIrType(r); + const f1 = r.tag(2, "IrFunction.returnType.optionalElements") === 0 ? undefined : (r.bool() as NonNullable["optionalElements"]>); + const o: Extract = { kind: "array", elem: f0, ...(f1 !== undefined ? { optionalElements: f1 } : {}) }; + return o; + } + case 1: { + const o: Extract = { kind: "bigint" }; + return o; + } + case 2: { + const o: Extract = { kind: "bool" }; + return o; + } + case 3: { + const f0 = (r.str() as NonNullable["elem"]>); + const o: Extract = { kind: "bytes", elem: f0 }; + return o; + } + case 4: { + const o: Extract = { kind: "caught" }; + return o; + } + case 5: { + const o: Extract = { kind: "child" }; + return o; + } + case 6: { + const o: Extract = { kind: "childStream" }; + return o; + } + case 7: { + const o: Extract = { kind: "childWriter" }; + return o; + } + case 8: { + const f0 = r.str(); + const o: Extract = { kind: "classval", className: f0 }; + return o; + } + case 9: { + const o: Extract = { kind: "cryptoHash" }; + return o; + } + case 10: { + const o: Extract = { kind: "cryptoHmac" }; + return o; + } + case 11: { + const o: Extract = { kind: "date" }; + return o; + } + case 12: { + const o: Extract = { kind: "dgramSocket" }; + return o; + } + case 13: { + const o: Extract = { kind: "dir" }; + return o; + } + case 14: { + const o: Extract = { kind: "dyn" }; + return o; + } + case 15: { + const o: Extract = { kind: "f64" }; + return o; + } + case 16: { + const o: Extract = { kind: "fileHandle" }; + return o; + } + case 17: { + const o: Extract = { kind: "fsWatcher" }; + return o; + } + case 18: { + const f0 = decT36(r); + const f1 = decIrType(r); + const f2 = r.tag(2, "IrFunction.returnType.rest") === 0 ? undefined : (r.bool() as NonNullable["rest"]>); + const f3 = r.tag(2, "IrFunction.returnType.restAbi") === 0 ? undefined : (r.str() as NonNullable["restAbi"]>); + const f4 = r.tag(2, "IrFunction.returnType.argumentsAll") === 0 ? undefined : (r.bool() as NonNullable["argumentsAll"]>); + const o: Extract = { kind: "func", params: f0, ret: f1, ...(f2 !== undefined ? { rest: f2 } : {}), ...(f3 !== undefined ? { restAbi: f3 } : {}), ...(f4 !== undefined ? { argumentsAll: f4 } : {}) }; + return o; + } + case 19: { + const f0 = r.tag(2, "IrFunction.returnType.async") === 0 ? undefined : (r.bool() as NonNullable["async"]>); + const f1 = decIrType(r); + const f2 = decIrType(r); + const f3 = decIrType(r); + const o: Extract = { kind: "generator", ...(f0 !== undefined ? { async: f0 } : {}), yieldT: f1, retT: f2, nextT: f3 }; + return o; + } + case 20: { + const o: Extract = { kind: "http2Session" }; + return o; + } + case 21: { + const o: Extract = { kind: "http2Stream" }; + return o; + } + case 22: { + const o: Extract = { kind: "httpClientReq" }; + return o; + } + case 23: { + const o: Extract = { kind: "httpReq" }; + return o; + } + case 24: { + const o: Extract = { kind: "httpRes" }; + return o; + } + case 25: { + const o: Extract = { kind: "jsval" }; + return o; + } + case 26: { + const f0 = decIrType(r); + const f1 = decIrType(r); + const o: Extract = { kind: "map", key: f0, value: f1 }; + return o; + } + case 27: { + const f0 = r.str(); + const o: Extract = { kind: "moduleNs", moduleId: f0 }; + return o; + } + case 28: { + const o: Extract = { kind: "netServer" }; + return o; + } + case 29: { + const o: Extract = { kind: "netSocket" }; + return o; + } + case 30: { + const o: Extract = { kind: "nullT" }; + return o; + } + case 31: { + const f0 = r.str(); + const o: Extract = { kind: "object", className: f0 }; + return o; + } + case 32: { + const o: Extract = { kind: "procStream" }; + return o; + } + case 33: { + const f0 = decIrType(r); + const o: Extract = { kind: "promise", inner: f0 }; + return o; + } + case 34: { + const f0 = r.str(); + const o: Extract = { kind: "record", shapeId: f0 }; + return o; + } + case 35: { + const o: Extract = { kind: "regex" }; + return o; + } + case 36: { + const o: Extract = { kind: "searchParams" }; + return o; + } + case 37: { + const o: Extract = { kind: "secureCtx" }; + return o; + } + case 38: { + const f0 = decIrType(r); + const o: Extract = { kind: "set", elem: f0 }; + return o; + } + case 39: { + const o: Extract = { kind: "spawnRes" }; + return o; + } + case 40: { + const o: Extract = { kind: "stats" }; + return o; + } + case 41: { + const o: Extract = { kind: "string" }; + return o; + } + case 42: { + const o: Extract = { kind: "symbol" }; + return o; + } + case 43: { + const o: Extract = { kind: "testCtx" }; + return o; + } + case 44: { + const o: Extract = { kind: "undefinedT" }; + return o; + } + case 45: { + const f0 = r.str(); + const o: Extract = { kind: "union", unionId: f0 }; + return o; + } + case 46: { + const o: Extract = { kind: "url" }; + return o; + } + case 47: { + const o: Extract = { kind: "void" }; + return o; + } + } + throw new Error("IR codec: invalid IrType kind"); +} + +function encT17(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrLocal(w, item); + } +} +function decT17(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrLocal(r)); + return out; +} + +function encT18(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT38(w, item); + } +} +function decT18(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT38(r)); + return out; +} + +function encT19(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrStmt(w, item); + } +} +function decT19(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrStmt(r)); + return out; +} + +function encSrcLoc(w: IrWriter, v: SrcLoc): void { + w.keys(Object.keys(v), KEYS53); + w.file(v.file); + w.num(v.start); + w.num(v.end); +} +function decSrcLoc(r: IrReader): SrcLoc { + const f0 = r.str(); + const f1 = r.num(); + const f2 = r.num(); + const o: SrcLoc = { file: f0, start: f1, end: f2 }; + return o; +} + +function encT21(w: IrWriter, v: NonNullable): void { + w.keys(Object.keys(v), KEYS54); + encIrType(w, v.yieldT); + encIrType(w, v.nextT); + encT40(w, v.resultType); + if (v.closesSource === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.closesSource); + } + if (v.tag === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.tag); + } + if (v.inheritsTag === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.inheritsTag); + } +} +function decT21(r: IrReader): NonNullable { + const f0 = decIrType(r); + const f1 = decIrType(r); + const f2 = decT40(r); + const f3 = r.tag(2, "IrFunction.generator.closesSource") === 0 ? undefined : (r.bool() as NonNullable["closesSource"]>); + const f4 = r.tag(2, "IrFunction.generator.tag") === 0 ? undefined : r.str(); + const f5 = r.tag(2, "IrFunction.generator.inheritsTag") === 0 ? undefined : (r.bool() as NonNullable["inheritsTag"]>); + const o: NonNullable = { yieldT: f0, nextT: f1, resultType: f2, ...(f3 !== undefined ? { closesSource: f3 } : {}), ...(f4 !== undefined ? { tag: f4 } : {}), ...(f5 !== undefined ? { inheritsTag: f5 } : {}) }; + return o; +} + +function encIrClassDef(w: IrWriter, v: IrClassDef): void { + w.keys(Object.keys(v), KEYS55); + w.str(v.name); + if (v.localCaptures === undefined) w.byte(0); + else { + w.byte(1); + encT15(w, v.localCaptures); + } + if (v.localBaseCapture === undefined) w.byte(0); + else { + w.byte(1); + w.num(v.localBaseCapture); + } + if (v.baseValueGlobal === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.baseValueGlobal); + } + if (v.jsName === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.jsName); + } + if (v.jsLength === undefined) w.byte(0); + else { + w.byte(1); + w.num(v.jsLength); + } + if (v.ownStaticFields === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.ownStaticFields); + } + if (v.prototypeDataHelper === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.prototypeDataHelper); + } + if (v.instancePrototypeHelper === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.instancePrototypeHelper); + } + if (v.stringTagHelper === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.stringTagHelper); + } + if (v.tracksOwnFields === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.tracksOwnFields); + } + if (v.errorNameAfter === undefined) w.byte(0); + else if (v.errorNameAfter === null) w.byte(1); + else { + w.byte(2); + w.str(v.errorNameAfter); + } + if (v.errorBagOrder === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.errorBagOrder); + } + if (v.symbolFields === undefined) w.byte(0); + else { + w.byte(1); + encT41(w, v.symbolFields); + } + if (v.runtime === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.runtime); + } + if (v.arrayElem === undefined) w.byte(0); + else { + w.byte(1); + encIrType(w, v.arrayElem); + } + if (v.arrayFields === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.arrayFields); + } + if (v.arraySpecies === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.arraySpecies); + } + if (v.base === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.base); + } + encT42(w, v.fields); + if (v.methods === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.methods); + } + if (v.prototypeNames === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.prototypeNames); + } + if (v.abstract === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.abstract); + } + if (v.abstractMethods === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.abstractMethods); + } + if (v.genericOf === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.genericOf); + } + encSrcLoc(w, v.loc); +} +function decIrClassDef(r: IrReader): IrClassDef { + const f0 = r.str(); + const f1 = r.tag(2, "IrModule.classes[].localCaptures") === 0 ? undefined : decT15(r); + const f2 = r.tag(2, "IrModule.classes[].localBaseCapture") === 0 ? undefined : r.num(); + const f3 = r.tag(2, "IrModule.classes[].baseValueGlobal") === 0 ? undefined : r.str(); + const f4 = r.tag(2, "IrModule.classes[].jsName") === 0 ? undefined : r.str(); + const f5 = r.tag(2, "IrModule.classes[].jsLength") === 0 ? undefined : r.num(); + const f6 = r.tag(2, "IrModule.classes[].ownStaticFields") === 0 ? undefined : (r.bool() as NonNullable); + const f7 = r.tag(2, "IrModule.classes[].prototypeDataHelper") === 0 ? undefined : r.str(); + const f8 = r.tag(2, "IrModule.classes[].instancePrototypeHelper") === 0 ? undefined : r.str(); + const f9 = r.tag(2, "IrModule.classes[].stringTagHelper") === 0 ? undefined : r.str(); + const f10 = r.tag(2, "IrModule.classes[].tracksOwnFields") === 0 ? undefined : r.bool(); + const f11 = r.tag(3, "IrModule.classes[].errorNameAfter") === 0 ? undefined : r.lastTag === 1 ? null : r.str(); + const f12 = r.tag(2, "IrModule.classes[].errorBagOrder") === 0 ? undefined : r.bool(); + const f13 = r.tag(2, "IrModule.classes[].symbolFields") === 0 ? undefined : decT41(r); + const f14 = r.tag(2, "IrModule.classes[].runtime") === 0 ? undefined : (r.bool() as NonNullable); + const f15 = r.tag(2, "IrModule.classes[].arrayElem") === 0 ? undefined : decIrType(r); + const f16 = r.tag(2, "IrModule.classes[].arrayFields") === 0 ? undefined : r.str(); + const f17 = r.tag(2, "IrModule.classes[].arraySpecies") === 0 ? undefined : r.str(); + const f18 = r.tag(2, "IrModule.classes[].base") === 0 ? undefined : r.str(); + const f19 = decT42(r); + const f20 = r.tag(2, "IrModule.classes[].methods") === 0 ? undefined : decT8(r); + const f21 = r.tag(2, "IrModule.classes[].prototypeNames") === 0 ? undefined : decT8(r); + const f22 = r.tag(2, "IrModule.classes[].abstract") === 0 ? undefined : (r.bool() as NonNullable); + const f23 = r.tag(2, "IrModule.classes[].abstractMethods") === 0 ? undefined : decT8(r); + const f24 = r.tag(2, "IrModule.classes[].genericOf") === 0 ? undefined : r.str(); + const f25 = decSrcLoc(r); + const o: IrClassDef = { name: f0, ...(f1 !== undefined ? { localCaptures: f1 } : {}), ...(f2 !== undefined ? { localBaseCapture: f2 } : {}), ...(f3 !== undefined ? { baseValueGlobal: f3 } : {}), ...(f4 !== undefined ? { jsName: f4 } : {}), ...(f5 !== undefined ? { jsLength: f5 } : {}), ...(f6 !== undefined ? { ownStaticFields: f6 } : {}), ...(f7 !== undefined ? { prototypeDataHelper: f7 } : {}), ...(f8 !== undefined ? { instancePrototypeHelper: f8 } : {}), ...(f9 !== undefined ? { stringTagHelper: f9 } : {}), ...(f10 !== undefined ? { tracksOwnFields: f10 } : {}), ...(f11 !== undefined ? { errorNameAfter: f11 } : {}), ...(f12 !== undefined ? { errorBagOrder: f12 } : {}), ...(f13 !== undefined ? { symbolFields: f13 } : {}), ...(f14 !== undefined ? { runtime: f14 } : {}), ...(f15 !== undefined ? { arrayElem: f15 } : {}), ...(f16 !== undefined ? { arrayFields: f16 } : {}), ...(f17 !== undefined ? { arraySpecies: f17 } : {}), ...(f18 !== undefined ? { base: f18 } : {}), fields: f19, ...(f20 !== undefined ? { methods: f20 } : {}), ...(f21 !== undefined ? { prototypeNames: f21 } : {}), ...(f22 !== undefined ? { abstract: f22 } : {}), ...(f23 !== undefined ? { abstractMethods: f23 } : {}), ...(f24 !== undefined ? { genericOf: f24 } : {}), loc: f25 }; + return o; +} + +function encIrGlobal(w: IrWriter, v: IrGlobal): void { + w.keys(Object.keys(v), KEYS56); + w.str(v.id); + w.str(v.name); + encIrType(w, v.type); + w.bool(v.mutable); + if (v.tdz === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.tdz); + } + if (v.initFlag === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.initFlag); + } + if (v.source === undefined) w.byte(0); + else { + w.byte(1); + encIrBindingSource(w, v.source); + } +} +function decIrGlobal(r: IrReader): IrGlobal { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = r.bool(); + const f4 = r.tag(2, "IrModule.globals[].tdz") === 0 ? undefined : (r.bool() as NonNullable); + const f5 = r.tag(2, "IrModule.globals[].initFlag") === 0 ? undefined : r.str(); + const f6 = r.tag(2, "IrModule.globals[].source") === 0 ? undefined : decIrBindingSource(r); + const o: IrGlobal = { id: f0, name: f1, type: f2, mutable: f3, ...(f4 !== undefined ? { tdz: f4 } : {}), ...(f5 !== undefined ? { initFlag: f5 } : {}), ...(f6 !== undefined ? { source: f6 } : {}) }; + return o; +} + +function encIrRecordShape(w: IrWriter, v: IrRecordShape): void { + w.keys(Object.keys(v), KEYS57); + w.str(v.id); + encT44(w, v.fields); + if (v.tuple === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.tuple); + } + if (v.indexValue === undefined) w.byte(0); + else { + w.byte(1); + encIrType(w, v.indexValue); + } + if (v.freezable === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.freezable); + } + if (v.declaredOrder === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.declaredOrder); + } + if (v.accessorOrder === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.accessorOrder); + } + if (v.orderVariants === undefined) w.byte(0); + else { + w.byte(1); + encT45(w, v.orderVariants); + } +} +function decIrRecordShape(r: IrReader): IrRecordShape { + const f0 = r.str(); + const f1 = decT44(r); + const f2 = r.tag(2, "IrModule.records[].tuple") === 0 ? undefined : (r.bool() as NonNullable); + const f3 = r.tag(2, "IrModule.records[].indexValue") === 0 ? undefined : decIrType(r); + const f4 = r.tag(2, "IrModule.records[].freezable") === 0 ? undefined : (r.bool() as NonNullable); + const f5 = r.tag(2, "IrModule.records[].declaredOrder") === 0 ? undefined : decT8(r); + const f6 = r.tag(2, "IrModule.records[].accessorOrder") === 0 ? undefined : decT8(r); + const f7 = r.tag(2, "IrModule.records[].orderVariants") === 0 ? undefined : decT45(r); + const o: IrRecordShape = { id: f0, fields: f1, ...(f2 !== undefined ? { tuple: f2 } : {}), ...(f3 !== undefined ? { indexValue: f3 } : {}), ...(f4 !== undefined ? { freezable: f4 } : {}), ...(f5 !== undefined ? { declaredOrder: f5 } : {}), ...(f6 !== undefined ? { accessorOrder: f6 } : {}), ...(f7 !== undefined ? { orderVariants: f7 } : {}) }; + return o; +} + +function encIrUnionDef(w: IrWriter, v: IrUnionDef): void { + w.keys(Object.keys(v), KEYS58); + w.str(v.id); + encT36(w, v.arms); + if (v.discriminant === undefined) w.byte(0); + else { + w.byte(1); + encIrUnionDiscriminant(w, v.discriminant); + } +} +function decIrUnionDef(r: IrReader): IrUnionDef { + const f0 = r.str(); + const f1 = decT36(r); + const f2 = r.tag(2, "IrModule.unions[].discriminant") === 0 ? undefined : decIrUnionDiscriminant(r); + const o: IrUnionDef = { id: f0, arms: f1, ...(f2 !== undefined ? { discriminant: f2 } : {}) }; + return o; +} + +function encT26(w: IrWriter, v: NonNullable[number]>): void { + w.keys(Object.keys(v), KEYS59); + w.file(v.file); + w.str(v.pkg); +} +function decT26(r: IrReader): NonNullable[number]> { + const f0 = r.str(); + const f1 = r.str(); + const o: NonNullable[number]> = { file: f0, pkg: f1 }; + return o; +} + +function encIrNodeIslandModules(w: IrWriter, v: IrNodeIslandModules): void { + w.keys(Object.keys(v), KEYS60); + w.str(v.root); + encT47(w, v.files); + encT48(w, v.links); + if (v.location === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.location); + } +} +function decIrNodeIslandModules(r: IrReader): IrNodeIslandModules { + const f0 = r.str(); + const f1 = decT47(r); + const f2 = decT48(r); + const f3 = r.tag(2, "IrModule.nodeIsland.modules.location") === 0 ? undefined : r.str(); + const o: IrNodeIslandModules = { root: f0, files: f1, links: f2, ...(f3 !== undefined ? { location: f3 } : {}) }; + return o; +} + +function encIrFfiImport(w: IrWriter, v: IrFfiImport): void { + w.keys(Object.keys(v), KEYS61); + w.str(v.name); + w.str(v.symbol); + if (v.library === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.library); + } + if (v.callbackOperation === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.callbackOperation); + } + if (v.callbackTarget === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.callbackTarget); + } + encT49(w, v.params); + w.str(v.returns); +} +function decIrFfiImport(r: IrReader): IrFfiImport { + const f0 = r.str(); + const f1 = r.str(); + const f2 = r.tag(2, "IrModule.ffiImports[].library") === 0 ? undefined : r.str(); + const f3 = r.tag(2, "IrModule.ffiImports[].callbackOperation") === 0 ? undefined : (r.str() as NonNullable); + const f4 = r.tag(2, "IrModule.ffiImports[].callbackTarget") === 0 ? undefined : r.str(); + const f5 = decT49(r); + const f6 = (r.str() as NonNullable); + const o: IrFfiImport = { name: f0, symbol: f1, ...(f2 !== undefined ? { library: f2 } : {}), ...(f3 !== undefined ? { callbackOperation: f3 } : {}), ...(f4 !== undefined ? { callbackTarget: f4 } : {}), params: f5, returns: f6 }; + return o; +} + +function encT29(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrLibCallback(w, item); + } +} +function decT29(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrLibCallback(r)); + return out; +} + +function encT30(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrLibExport(w, item); + } +} +function decT30(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrLibExport(r)); + return out; +} + +function encT31(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrLibTrapOverlay(w, item); + } +} +function decT31(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrLibTrapOverlay(r)); + return out; +} + +function encIrLibIdentity(w: IrWriter, v: IrLibIdentity): void { + w.keys(Object.keys(v), KEYS62); + w.str(v.buildIdSymbol); + w.str(v.abiVersionSymbol); + w.str(v.buildId); + w.num(v.abiVersion); +} +function decIrLibIdentity(r: IrReader): IrLibIdentity { + const f0 = r.str(); + const f1 = r.str(); + const f2 = r.str(); + const f3 = r.num(); + const o: IrLibIdentity = { buildIdSymbol: f0, abiVersionSymbol: f1, buildId: f2, abiVersion: f3 }; + return o; +} + +function encIrWasmModule(w: IrWriter, v: IrWasmModule): void { + w.keys(Object.keys(v), KEYS63); + w.str(v.sha256); + w.str(v.bytes); +} +function decIrWasmModule(r: IrReader): IrWasmModule { + const f0 = r.str(); + const f1 = r.str(); + const o: IrWasmModule = { sha256: f0, bytes: f1 }; + return o; +} + +function encIrWasmUntranslated(w: IrWriter, v: IrWasmUntranslated): void { + w.keys(Object.keys(v), KEYS64); + w.str(v.sha256); + w.num(v.size); + w.str(v.reason); +} +function decIrWasmUntranslated(r: IrReader): IrWasmUntranslated { + const f0 = r.str(); + const f1 = r.num(); + const f2 = r.str(); + const o: IrWasmUntranslated = { sha256: f0, size: f1, reason: f2 }; + return o; +} + +function encIrParam(w: IrWriter, v: IrParam): void { + w.keys(Object.keys(v), KEYS65); + w.str(v.localId); + w.str(v.name); + encIrType(w, v.type); + if (v.owned === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.owned); + } +} +function decIrParam(r: IrReader): IrParam { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = r.tag(2, "IrFunction.params[].owned") === 0 ? undefined : (r.bool() as NonNullable); + const o: IrParam = { localId: f0, name: f1, type: f2, ...(f3 !== undefined ? { owned: f3 } : {}) }; + return o; +} + +function encT36(w: IrWriter, v: NonNullable["params"]>): void { + w.length(v.length); + for (const item of v) { + encIrType(w, item); + } +} +function decT36(r: IrReader): NonNullable["params"]> { + const length = r.length(); + const out: NonNullable["params"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrType(r)); + return out; +} + +function encIrLocal(w: IrWriter, v: IrLocal): void { + w.keys(Object.keys(v), KEYS66); + w.str(v.id); + w.str(v.name); + encIrType(w, v.type); + w.bool(v.mutable); + if (v.source === undefined) w.byte(0); + else { + w.byte(1); + encIrBindingSource(w, v.source); + } + if (v.boxed === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.boxed); + } + if (v.tdz === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.tdz); + } +} +function decIrLocal(r: IrReader): IrLocal { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = r.bool(); + const f4 = r.tag(2, "IrFunction.locals[].source") === 0 ? undefined : decIrBindingSource(r); + const f5 = r.tag(2, "IrFunction.locals[].boxed") === 0 ? undefined : (r.bool() as NonNullable); + const f6 = r.tag(2, "IrFunction.locals[].tdz") === 0 ? undefined : (r.bool() as NonNullable); + const o: IrLocal = { id: f0, name: f1, type: f2, mutable: f3, ...(f4 !== undefined ? { source: f4 } : {}), ...(f5 !== undefined ? { boxed: f5 } : {}), ...(f6 !== undefined ? { tdz: f6 } : {}) }; + return o; +} + +function encT38(w: IrWriter, v: NonNullable[number]>): void { + w.keys(Object.keys(v), KEYS67); + w.str(v.localId); + w.str(v.name); + encIrType(w, v.type); + if (v.owned === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.owned); + } + w.num(v.slot); +} +function decT38(r: IrReader): NonNullable[number]> { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = r.tag(2, "IrFunction.classCaptures[].owned") === 0 ? undefined : (r.bool() as NonNullable[number]>["owned"]>); + const f4 = r.num(); + const o: NonNullable[number]> = { localId: f0, name: f1, type: f2, ...(f3 !== undefined ? { owned: f3 } : {}), slot: f4 }; + return o; +} + +function encIrStmt(w: IrWriter, v: IrStmt): void { + switch (v.kind) { + case "arrayDelete": { + w.length(0); + w.keys(Object.keys(v), KEYS68); + encIrExpr(w, v.arr); + encIrExpr(w, v.index); + encSrcLoc(w, v.loc); + return; + } + case "arraySet": { + w.length(1); + w.keys(Object.keys(v), KEYS69); + encIrExpr(w, v.arr); + encIrExpr(w, v.index); + encIrExpr(w, v.value); + encSrcLoc(w, v.loc); + return; + } + case "arraySetLength": { + w.length(2); + w.keys(Object.keys(v), KEYS70); + encIrExpr(w, v.arr); + encIrExpr(w, v.length); + encSrcLoc(w, v.loc); + if (v.pop === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.pop); + } + if (v.reserve === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.reserve); + } + return; + } + case "arraySetUndefined": { + w.length(3); + w.keys(Object.keys(v), KEYS71); + encIrExpr(w, v.arr); + encIrExpr(w, v.index); + encSrcLoc(w, v.loc); + return; + } + case "assign": { + w.length(4); + w.keys(Object.keys(v), KEYS72); + w.str(v.localId); + encIrExpr(w, v.value); + if (v.initializes === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.initializes); + } + encSrcLoc(w, v.loc); + return; + } + case "block": { + w.length(5); + w.keys(Object.keys(v), KEYS73); + if (v.labels === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.labels); + } + encT19(w, v.body); + encSrcLoc(w, v.loc); + return; + } + case "break": { + w.length(6); + w.keys(Object.keys(v), KEYS74); + if (v.label === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.label); + } + encSrcLoc(w, v.loc); + return; + } + case "bytesSet": { + w.length(7); + w.keys(Object.keys(v), KEYS75); + encIrExpr(w, v.arr); + encIrExpr(w, v.index); + encIrExpr(w, v.value); + encSrcLoc(w, v.loc); + return; + } + case "continue": { + w.length(8); + w.keys(Object.keys(v), KEYS76); + if (v.label === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.label); + } + encSrcLoc(w, v.loc); + return; + } + case "doWhile": { + w.length(9); + w.keys(Object.keys(v), KEYS77); + encT19(w, v.body); + encIrExpr(w, v.cond); + if (v.labels === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.labels); + } + encSrcLoc(w, v.loc); + return; + } + case "exprStmt": { + w.length(10); + w.keys(Object.keys(v), KEYS78); + encIrExpr(w, v.expr); + encSrcLoc(w, v.loc); + return; + } + case "fieldSet": { + w.length(11); + w.keys(Object.keys(v), KEYS79); + encIrExpr(w, v.obj); + w.str(v.className); + w.str(v.field); + encIrExpr(w, v.value); + if (v.sloppy === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.sloppy); + } + if (v.define === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.define); + } + if (v.attributesChecked === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.attributesChecked); + } + encSrcLoc(w, v.loc); + return; + } + case "for": { + w.length(12); + w.keys(Object.keys(v), KEYS80); + if (v.init === null) w.byte(0); + else { + w.byte(1); + encIrStmt(w, v.init); + } + if (v.cond === null) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.cond); + } + if (v.update === null) w.byte(0); + else { + w.byte(1); + encIrStmt(w, v.update); + } + encT19(w, v.body); + if (v.labels === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.labels); + } + encSrcLoc(w, v.loc); + return; + } + case "forOf": { + w.length(13); + w.keys(Object.keys(v), KEYS81); + w.str(v.localId); + encIrExpr(w, v.iterable); + encT19(w, v.body); + if (v.labels === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.labels); + } + encSrcLoc(w, v.loc); + return; + } + case "if": { + w.length(14); + w.keys(Object.keys(v), KEYS82); + encIrExpr(w, v.cond); + encT19(w, v.then); + if (v.else_ === null) w.byte(0); + else { + w.byte(1); + encT19(w, v.else_); + } + encSrcLoc(w, v.loc); + return; + } + case "recordKeyDelete": { + w.length(15); + w.keys(Object.keys(v), KEYS83); + encIrExpr(w, v.obj); + w.str(v.shapeId); + encIrExpr(w, v.key); + encSrcLoc(w, v.loc); + return; + } + case "recordKeySet": { + w.length(16); + w.keys(Object.keys(v), KEYS84); + encIrExpr(w, v.obj); + w.str(v.shapeId); + encIrExpr(w, v.key); + encIrExpr(w, v.value); + if (v.overflowOnly === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.overflowOnly); + } + encSrcLoc(w, v.loc); + return; + } + case "recordSet": { + w.length(17); + w.keys(Object.keys(v), KEYS85); + encIrExpr(w, v.obj); + w.str(v.shapeId); + w.str(v.field); + encIrExpr(w, v.value); + encSrcLoc(w, v.loc); + return; + } + case "rethrow": { + w.length(18); + w.keys(Object.keys(v), KEYS86); + w.str(v.localId); + encSrcLoc(w, v.loc); + return; + } + case "return": { + w.length(19); + w.keys(Object.keys(v), KEYS87); + if (v.value === null) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.value); + } + if (v.awaited === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.awaited); + } + encSrcLoc(w, v.loc); + return; + } + case "runtimeFence": { + w.length(20); + w.keys(Object.keys(v), KEYS88); + w.str(v.code); + w.str(v.message); + encSrcLoc(w, v.loc); + return; + } + case "switch": { + w.length(21); + w.keys(Object.keys(v), KEYS89); + encIrExpr(w, v.disc); + encT54(w, v.cases); + if (v.labels === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.labels); + } + encSrcLoc(w, v.loc); + return; + } + case "throw": { + w.length(22); + w.keys(Object.keys(v), KEYS90); + encIrExpr(w, v.value); + encSrcLoc(w, v.loc); + return; + } + case "tryCatch": { + w.length(23); + w.keys(Object.keys(v), KEYS91); + encT19(w, v.tryBody); + if (v.catchBody === null) w.byte(0); + else { + w.byte(1); + encT19(w, v.catchBody); + } + if (v.catchLocalId === null) w.byte(0); + else { + w.byte(1); + w.str(v.catchLocalId); + } + if (v.dropFinallyErrors === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.dropFinallyErrors); + } + if (v.finallyBody === null) w.byte(0); + else { + w.byte(1); + encT19(w, v.finallyBody); + } + if (v.suppressFinallyErrors === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.suppressFinallyErrors); + } + encSrcLoc(w, v.loc); + return; + } + case "varDecl": { + w.length(24); + w.keys(Object.keys(v), KEYS92); + w.str(v.localId); + if (v.init === null) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.init); + } + encSrcLoc(w, v.loc); + return; + } + case "while": { + w.length(25); + w.keys(Object.keys(v), KEYS93); + encIrExpr(w, v.cond); + encT19(w, v.body); + if (v.labels === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.labels); + } + encSrcLoc(w, v.loc); + return; + } + } + w.refuse("IrStmt kind " + String((v as { kind: unknown }).kind)); +} +function decIrStmt(r: IrReader): IrStmt { + switch (r.length()) { + case 0: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "arrayDelete", arr: f0, index: f1, loc: f2 }; + return o; + } + case 1: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "arraySet", arr: f0, index: f1, value: f2, loc: f3 }; + return o; + } + case 2: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decSrcLoc(r); + const f3 = r.tag(2, "IrFunction.body[].pop") === 0 ? undefined : r.bool(); + const f4 = r.tag(2, "IrFunction.body[].reserve") === 0 ? undefined : r.bool(); + const o: Extract = { kind: "arraySetLength", arr: f0, length: f1, loc: f2, ...(f3 !== undefined ? { pop: f3 } : {}), ...(f4 !== undefined ? { reserve: f4 } : {}) }; + return o; + } + case 3: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "arraySetUndefined", arr: f0, index: f1, loc: f2 }; + return o; + } + case 4: { + const f0 = r.str(); + const f1 = decIrExpr(r); + const f2 = r.tag(2, "IrFunction.body[].initializes") === 0 ? undefined : (r.bool() as NonNullable["initializes"]>); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "assign", localId: f0, value: f1, ...(f2 !== undefined ? { initializes: f2 } : {}), loc: f3 }; + return o; + } + case 5: { + const f0 = r.tag(2, "IrFunction.body[].labels") === 0 ? undefined : decT8(r); + const f1 = decT19(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "block", ...(f0 !== undefined ? { labels: f0 } : {}), body: f1, loc: f2 }; + return o; + } + case 6: { + const f0 = r.tag(2, "IrFunction.body[].label") === 0 ? undefined : r.str(); + const f1 = decSrcLoc(r); + const o: Extract = { kind: "break", ...(f0 !== undefined ? { label: f0 } : {}), loc: f1 }; + return o; + } + case 7: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "bytesSet", arr: f0, index: f1, value: f2, loc: f3 }; + return o; + } + case 8: { + const f0 = r.tag(2, "IrFunction.body[].label") === 0 ? undefined : r.str(); + const f1 = decSrcLoc(r); + const o: Extract = { kind: "continue", ...(f0 !== undefined ? { label: f0 } : {}), loc: f1 }; + return o; + } + case 9: { + const f0 = decT19(r); + const f1 = decIrExpr(r); + const f2 = r.tag(2, "IrFunction.body[].labels") === 0 ? undefined : decT8(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "doWhile", body: f0, cond: f1, ...(f2 !== undefined ? { labels: f2 } : {}), loc: f3 }; + return o; + } + case 10: { + const f0 = decIrExpr(r); + const f1 = decSrcLoc(r); + const o: Extract = { kind: "exprStmt", expr: f0, loc: f1 }; + return o; + } + case 11: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = r.str(); + const f3 = decIrExpr(r); + const f4 = r.tag(2, "IrFunction.body[]
.sloppy") === 0 ? undefined : (r.bool() as NonNullable["sloppy"]>); + const f5 = r.tag(2, "IrFunction.body[]
.define") === 0 ? undefined : (r.bool() as NonNullable["define"]>); + const f6 = r.tag(2, "IrFunction.body[]
.attributesChecked") === 0 ? undefined : (r.bool() as NonNullable["attributesChecked"]>); + const f7 = decSrcLoc(r); + const o: Extract = { kind: "fieldSet", obj: f0, className: f1, field: f2, value: f3, ...(f4 !== undefined ? { sloppy: f4 } : {}), ...(f5 !== undefined ? { define: f5 } : {}), ...(f6 !== undefined ? { attributesChecked: f6 } : {}), loc: f7 }; + return o; + } + case 12: { + const f0 = r.tag(2, "IrFunction.body[].init") === 0 ? null : decIrStmt(r); + const f1 = r.tag(2, "IrFunction.body[].cond") === 0 ? null : decIrExpr(r); + const f2 = r.tag(2, "IrFunction.body[].update") === 0 ? null : decIrStmt(r); + const f3 = decT19(r); + const f4 = r.tag(2, "IrFunction.body[].labels") === 0 ? undefined : decT8(r); + const f5 = decSrcLoc(r); + const o: Extract = { kind: "for", init: f0, cond: f1, update: f2, body: f3, ...(f4 !== undefined ? { labels: f4 } : {}), loc: f5 }; + return o; + } + case 13: { + const f0 = r.str(); + const f1 = decIrExpr(r); + const f2 = decT19(r); + const f3 = r.tag(2, "IrFunction.body[].labels") === 0 ? undefined : decT8(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "forOf", localId: f0, iterable: f1, body: f2, ...(f3 !== undefined ? { labels: f3 } : {}), loc: f4 }; + return o; + } + case 14: { + const f0 = decIrExpr(r); + const f1 = decT19(r); + const f2 = r.tag(2, "IrFunction.body[].else_") === 0 ? null : decT19(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "if", cond: f0, then: f1, else_: f2, loc: f3 }; + return o; + } + case 15: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = decIrExpr(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "recordKeyDelete", obj: f0, shapeId: f1, key: f2, loc: f3 }; + return o; + } + case 16: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = decIrExpr(r); + const f3 = decIrExpr(r); + const f4 = r.tag(2, "IrFunction.body[].overflowOnly") === 0 ? undefined : (r.bool() as NonNullable["overflowOnly"]>); + const f5 = decSrcLoc(r); + const o: Extract = { kind: "recordKeySet", obj: f0, shapeId: f1, key: f2, value: f3, ...(f4 !== undefined ? { overflowOnly: f4 } : {}), loc: f5 }; + return o; + } + case 17: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = r.str(); + const f3 = decIrExpr(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "recordSet", obj: f0, shapeId: f1, field: f2, value: f3, loc: f4 }; + return o; + } + case 18: { + const f0 = r.str(); + const f1 = decSrcLoc(r); + const o: Extract = { kind: "rethrow", localId: f0, loc: f1 }; + return o; + } + case 19: { + const f0 = r.tag(2, "IrFunction.body[].value") === 0 ? null : decIrExpr(r); + const f1 = r.tag(2, "IrFunction.body[].awaited") === 0 ? undefined : (r.bool() as NonNullable["awaited"]>); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "return", value: f0, ...(f1 !== undefined ? { awaited: f1 } : {}), loc: f2 }; + return o; + } + case 20: { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "runtimeFence", code: f0, message: f1, loc: f2 }; + return o; + } + case 21: { + const f0 = decIrExpr(r); + const f1 = decT54(r); + const f2 = r.tag(2, "IrFunction.body[].labels") === 0 ? undefined : decT8(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "switch", disc: f0, cases: f1, ...(f2 !== undefined ? { labels: f2 } : {}), loc: f3 }; + return o; + } + case 22: { + const f0 = decIrExpr(r); + const f1 = decSrcLoc(r); + const o: Extract = { kind: "throw", value: f0, loc: f1 }; + return o; + } + case 23: { + const f0 = decT19(r); + const f1 = r.tag(2, "IrFunction.body[].catchBody") === 0 ? null : decT19(r); + const f2 = r.tag(2, "IrFunction.body[].catchLocalId") === 0 ? null : r.str(); + const f3 = r.tag(2, "IrFunction.body[].dropFinallyErrors") === 0 ? undefined : (r.bool() as NonNullable["dropFinallyErrors"]>); + const f4 = r.tag(2, "IrFunction.body[].finallyBody") === 0 ? null : decT19(r); + const f5 = r.tag(2, "IrFunction.body[].suppressFinallyErrors") === 0 ? undefined : (r.bool() as NonNullable["suppressFinallyErrors"]>); + const f6 = decSrcLoc(r); + const o: Extract = { kind: "tryCatch", tryBody: f0, catchBody: f1, catchLocalId: f2, ...(f3 !== undefined ? { dropFinallyErrors: f3 } : {}), finallyBody: f4, ...(f5 !== undefined ? { suppressFinallyErrors: f5 } : {}), loc: f6 }; + return o; + } + case 24: { + const f0 = r.str(); + const f1 = r.tag(2, "IrFunction.body[].init") === 0 ? null : decIrExpr(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "varDecl", localId: f0, init: f1, loc: f2 }; + return o; + } + case 25: { + const f0 = decIrExpr(r); + const f1 = decT19(r); + const f2 = r.tag(2, "IrFunction.body[].labels") === 0 ? undefined : decT8(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "while", cond: f0, body: f1, ...(f2 !== undefined ? { labels: f2 } : {}), loc: f3 }; + return o; + } + } + throw new Error("IR codec: invalid IrStmt kind"); +} + +function encT40(w: IrWriter, v: NonNullable["resultType"]>): void { + w.keys(Object.keys(v), KEYS94); + w.str(v.kind); + w.str(v.shapeId); +} +function decT40(r: IrReader): NonNullable["resultType"]> { + const f0 = (r.str() as NonNullable["resultType"]>["kind"]>); + const f1 = r.str(); + const o: NonNullable["resultType"]> = { kind: f0, shapeId: f1 }; + return o; +} + +function encT41(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT55(w, item); + } +} +function decT41(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT55(r)); + return out; +} + +function encT42(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT56(w, item); + } +} +function decT42(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT56(r)); + return out; +} + +function encIrBindingSource(w: IrWriter, v: IrBindingSource): void { + w.keys(Object.keys(v), KEYS95); + encSrcLoc(w, v.loc); + encSrcLoc(w, v.scope); +} +function decIrBindingSource(r: IrReader): IrBindingSource { + const f0 = decSrcLoc(r); + const f1 = decSrcLoc(r); + const o: IrBindingSource = { loc: f0, scope: f1 }; + return o; +} + +function encT44(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT57(w, item); + } +} +function decT44(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT57(r)); + return out; +} + +function encT45(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT8(w, item); + } +} +function decT45(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT8(r)); + return out; +} + +function encIrUnionDiscriminant(w: IrWriter, v: IrUnionDiscriminant): void { + w.keys(Object.keys(v), KEYS96); + w.str(v.field); + encT58(w, v.cases); +} +function decIrUnionDiscriminant(r: IrReader): IrUnionDiscriminant { + const f0 = r.str(); + const f1 = decT58(r); + const o: IrUnionDiscriminant = { field: f0, cases: f1 }; + return o; +} + +function encT47(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT59(w, item); + } +} +function decT47(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT59(r)); + return out; +} + +function encT48(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT60(w, item); + } +} +function decT48(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT60(r)); + return out; +} + +function encT49(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encIrFfiParam(w, item); + } +} +function decT49(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrFfiParam(r)); + return out; +} + +function encIrLibCallback(w: IrWriter, v: IrLibCallback): void { + w.keys(Object.keys(v), KEYS97); + w.str(v.name); + w.num(v.slot); + encT62(w, v.params); + w.str(v.returns); + w.str(v.unregisteredTrap); +} +function decIrLibCallback(r: IrReader): IrLibCallback { + const f0 = r.str(); + const f1 = r.num(); + const f2 = decT62(r); + const f3 = (r.str() as NonNullable); + const f4 = r.str(); + const o: IrLibCallback = { name: f0, slot: f1, params: f2, returns: f3, unregisteredTrap: f4 }; + return o; +} + +function encIrLibExport(w: IrWriter, v: IrLibExport): void { + w.keys(Object.keys(v), KEYS98); + w.str(v.symbol); + w.str(v.fnName); + encT63(w, v.params); + w.str(v.returns); + if (v.inboundBytesTrap === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.inboundBytesTrap); + } + if (v.inboundIntTrap === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.inboundIntTrap); + } + if (v.inboundCstringTrap === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.inboundCstringTrap); + } +} +function decIrLibExport(r: IrReader): IrLibExport { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decT63(r); + const f3 = (r.str() as NonNullable); + const f4 = r.tag(2, "IrModule.lib.exports[].inboundBytesTrap") === 0 ? undefined : r.str(); + const f5 = r.tag(2, "IrModule.lib.exports[].inboundIntTrap") === 0 ? undefined : r.str(); + const f6 = r.tag(2, "IrModule.lib.exports[].inboundCstringTrap") === 0 ? undefined : r.str(); + const o: IrLibExport = { symbol: f0, fnName: f1, params: f2, returns: f3, ...(f4 !== undefined ? { inboundBytesTrap: f4 } : {}), ...(f5 !== undefined ? { inboundIntTrap: f5 } : {}), ...(f6 !== undefined ? { inboundCstringTrap: f6 } : {}) }; + return o; +} + +function encIrLibTrapOverlay(w: IrWriter, v: IrLibTrapOverlay): void { + w.keys(Object.keys(v), KEYS99); + w.str(v.code); + if (v.teaching === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.teaching); + } + if (v.remediation === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.remediation); + } +} +function decIrLibTrapOverlay(r: IrReader): IrLibTrapOverlay { + const f0 = r.str(); + const f1 = r.tag(2, "IrModule.lib.trapOverlays[].teaching") === 0 ? undefined : r.str(); + const f2 = r.tag(2, "IrModule.lib.trapOverlays[].remediation") === 0 ? undefined : r.str(); + const o: IrLibTrapOverlay = { code: f0, ...(f1 !== undefined ? { teaching: f1 } : {}), ...(f2 !== undefined ? { remediation: f2 } : {}) }; + return o; +} + +function encIrExpr(w: IrWriter, v: IrExpr): void { + switch (v.kind) { + case "arrIntrinsic": { + w.length(0); + w.keys(Object.keys(v), KEYS100); + w.str(v.method); + encIrExpr(w, v.receiver); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "arrayGet": { + w.length(1); + w.keys(Object.keys(v), KEYS101); + encIrExpr(w, v.arr); + encIrExpr(w, v.index); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "arrayHas": { + w.length(2); + w.keys(Object.keys(v), KEYS102); + encIrExpr(w, v.arr); + encIrExpr(w, v.index); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "arrayLit": { + w.length(3); + w.keys(Object.keys(v), KEYS103); + encT64(w, v.elems); + if (v.spreads === undefined) w.byte(0); + else { + w.byte(1); + encT65(w, v.spreads); + } + if (v.capacity === undefined) w.byte(0); + else { + w.byte(1); + w.num(v.capacity); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "arrayNewLen": { + w.length(4); + w.keys(Object.keys(v), KEYS104); + encIrExpr(w, v.length); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "arrayState": { + w.length(5); + w.keys(Object.keys(v), KEYS105); + encIrExpr(w, v.arr); + encIrExpr(w, v.index); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "assignExpr": { + w.length(6); + w.keys(Object.keys(v), KEYS106); + w.str(v.localId); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "awaitExpr": { + w.length(7); + w.keys(Object.keys(v), KEYS107); + encIrExpr(w, v.value); + encIrType(w, v.type); + if (v.adopt === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.adopt); + } + encSrcLoc(w, v.loc); + return; + } + case "awaitUnionExpr": { + w.length(8); + w.keys(Object.keys(v), KEYS108); + encIrExpr(w, v.value); + w.num(v.promiseTag); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "bin": { + w.length(9); + w.keys(Object.keys(v), KEYS109); + w.str(v.op); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "boolLit": { + w.length(10); + w.keys(Object.keys(v), KEYS110); + w.bool(v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "bytesIntrinsic": { + w.length(11); + w.keys(Object.keys(v), KEYS111); + w.str(v.method); + encIrExpr(w, v.receiver); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + if (v.invalidNaN === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.invalidNaN); + } + return; + } + case "bytesNew": { + w.length(12); + w.keys(Object.keys(v), KEYS112); + if (v.source === null) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.source); + } + if (v.from === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.from); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "call": { + w.length(13); + w.keys(Object.keys(v), KEYS113); + w.str(v.callee); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "callValue": { + w.length(14); + w.keys(Object.keys(v), KEYS114); + encIrExpr(w, v.callee); + if (v.receiver === undefined) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.receiver); + } + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "caughtCheck": { + w.length(15); + w.keys(Object.keys(v), KEYS115); + encIrExpr(w, v.value); + w.str(v.className); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "caughtNarrow": { + w.length(16); + w.keys(Object.keys(v), KEYS116); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "caughtTest": { + w.length(17); + w.keys(Object.keys(v), KEYS117); + encIrExpr(w, v.value); + w.str(v.test); + if (v.className === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.className); + } + if (v.negated === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.negated); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "caughtToDyn": { + w.length(18); + w.keys(Object.keys(v), KEYS118); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "chainRecv": { + w.length(19); + w.keys(Object.keys(v), KEYS119); + w.str(v.id); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "classRef": { + w.length(20); + w.keys(Object.keys(v), KEYS120); + w.str(v.className); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + if (v.captures === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.captures); + } + return; + } + case "closure": { + w.length(21); + w.keys(Object.keys(v), KEYS121); + w.str(v.fnName); + encT8(w, v.captures); + if (v.adapts === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.adapts); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "downcast": { + w.length(22); + w.keys(Object.keys(v), KEYS122); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynArrLit": { + w.length(23); + w.keys(Object.keys(v), KEYS123); + encT64(w, v.elems); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynCall": { + w.length(24); + w.keys(Object.keys(v), KEYS124); + encIrExpr(w, v.callee); + if (v.receiver === undefined) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.receiver); + } + w.str(v.calleeName); + if (v.calleeNameValue === undefined) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.calleeNameValue); + } + encT64(w, v.args); + if (v.spreads === undefined) w.byte(0); + else { + w.byte(1); + encT66(w, v.spreads); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynCheck": { + w.length(25); + w.keys(Object.keys(v), KEYS125); + encIrExpr(w, v.value); + if (v.preserveRefs === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.preserveRefs); + } + if (v.adopt === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.adopt); + } + if (v.exact === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.exact); + } + if (v.inferredSite === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.inferredSite); + } + if (v.origin === undefined) w.byte(0); + else { + w.byte(1); + w.num(v.origin); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynDestrCheck": { + w.length(26); + w.keys(Object.keys(v), KEYS126); + encIrExpr(w, v.value); + w.str(v.spelling); + if (v.firstProp === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.firstProp); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynFrom": { + w.length(27); + w.keys(Object.keys(v), KEYS127); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + if (v.liveRef === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.liveRef); + } + if (v.fnName === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.fnName); + } + if (v.fnLength === undefined) w.byte(0); + else { + w.byte(1); + w.num(v.fnLength); + } + return; + } + case "dynHasKey": { + w.length(28); + w.keys(Object.keys(v), KEYS128); + w.str(v.key); + if (v.negated === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.negated); + } + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynInvoke": { + w.length(29); + w.keys(Object.keys(v), KEYS129); + encIrExpr(w, v.recv); + w.str(v.method); + w.str(v.calleeName); + if (v.calleeNameValue === undefined) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.calleeNameValue); + } + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynIterN": { + w.length(30); + w.keys(Object.keys(v), KEYS130); + encIrExpr(w, v.value); + w.num(v.count); + if (v.notIterableMessage === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.notIterableMessage); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynKeyGet": { + w.length(31); + w.keys(Object.keys(v), KEYS131); + encIrExpr(w, v.key); + if (v.optional === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.optional); + } + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynObjLit": { + w.length(32); + w.keys(Object.keys(v), KEYS132); + if (v.fields === undefined) w.byte(0); + else { + w.byte(1); + encT67(w, v.fields); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynScalarEq": { + w.length(33); + w.keys(Object.keys(v), KEYS133); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + if (v.negated === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.negated); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "dynTest": { + w.length(34); + w.keys(Object.keys(v), KEYS134); + w.str(v.test); + if (v.bytesElem === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.bytesElem); + } + if (v.negated === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.negated); + } + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "ffiCall": { + w.length(35); + w.keys(Object.keys(v), KEYS135); + w.str(v.import); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "fieldAbsent": { + w.length(36); + w.keys(Object.keys(v), KEYS136); + w.str(v.unionId); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "fieldGet": { + w.length(37); + w.keys(Object.keys(v), KEYS137); + encIrExpr(w, v.obj); + w.str(v.className); + w.str(v.field); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "fieldIncDec": { + w.length(38); + w.keys(Object.keys(v), KEYS138); + w.str(v.op); + w.bool(v.prefix); + encIrExpr(w, v.obj); + w.str(v.className); + w.str(v.field); + w.bool(v.fieldDyn); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "genResume": { + w.length(39); + w.keys(Object.keys(v), KEYS139); + w.str(v.mode); + encIrExpr(w, v.gen); + if (v.arg === null) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.arg); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "incDec": { + w.length(40); + w.keys(Object.keys(v), KEYS140); + w.str(v.op); + w.bool(v.prefix); + w.str(v.localId); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "instanceOf": { + w.length(41); + w.keys(Object.keys(v), KEYS141); + encIrExpr(w, v.value); + w.str(v.className); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "instanceOfValue": { + w.length(42); + w.keys(Object.keys(v), KEYS142); + encIrExpr(w, v.value); + encIrExpr(w, v.classValue); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "intrinsic": { + w.length(43); + w.keys(Object.keys(v), KEYS143); + w.str(v.name); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "jsonStringify": { + w.length(44); + w.keys(Object.keys(v), KEYS144); + encIrExpr(w, v.value); + if (v.indent === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.indent); + } + if (v.circular === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.circular); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "libCall": { + w.length(45); + w.keys(Object.keys(v), KEYS145); + w.str(v.fn); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + if (v.prototypeIdentityOnly === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.prototypeIdentityOnly); + } + if (v.realTimer === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.realTimer); + } + if (v.assertOptions === undefined) w.byte(0); + else { + w.byte(1); + encT68(w, v.assertOptions); + } + return; + } + case "logical": { + w.length(46); + w.keys(Object.keys(v), KEYS146); + w.str(v.op); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "mapIntrinsic": { + w.length(47); + w.keys(Object.keys(v), KEYS147); + w.str(v.method); + encIrExpr(w, v.receiver); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "mapNew": { + w.length(48); + w.keys(Object.keys(v), KEYS148); + if (v.seed === undefined) w.byte(0); + else { + w.byte(1); + encT69(w, v.seed); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "moduleNsRef": { + w.length(49); + w.keys(Object.keys(v), KEYS149); + w.str(v.moduleId); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "new": { + w.length(50); + w.keys(Object.keys(v), KEYS150); + w.str(v.className); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "newPromise": { + w.length(51); + w.keys(Object.keys(v), KEYS151); + encIrExpr(w, v.executor); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "newValue": { + w.length(52); + w.keys(Object.keys(v), KEYS152); + encIrExpr(w, v.callee); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "nullish": { + w.length(53); + w.keys(Object.keys(v), KEYS153); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "numLit": { + w.length(54); + w.keys(Object.keys(v), KEYS154); + w.num(v.value); + if (v.spelling === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.spelling); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "optChain": { + w.length(55); + w.keys(Object.keys(v), KEYS155); + w.str(v.id); + encIrExpr(w, v.receiver); + encIrExpr(w, v.body); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "orDefault": { + w.length(56); + w.keys(Object.keys(v), KEYS156); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + if (v.retag === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.retag); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "promiseVoidWiden": { + w.length(57); + w.keys(Object.keys(v), KEYS157); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "promiseWithResolvers": { + w.length(58); + w.keys(Object.keys(v), KEYS158); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "recordClone": { + w.length(59); + w.keys(Object.keys(v), KEYS159); + encIrExpr(w, v.source); + encT70(w, v.overrides); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "recordGet": { + w.length(60); + w.keys(Object.keys(v), KEYS160); + encIrExpr(w, v.obj); + w.str(v.shapeId); + w.str(v.field); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "recordHas": { + w.length(61); + w.keys(Object.keys(v), KEYS161); + encIrExpr(w, v.obj); + w.str(v.shapeId); + w.str(v.field); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "recordKeyGet": { + w.length(62); + w.keys(Object.keys(v), KEYS162); + encIrExpr(w, v.obj); + w.str(v.shapeId); + encIrExpr(w, v.key); + if (v.overflowOnly === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.overflowOnly); + } + if (v.missNaN === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.missNaN); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "recordLit": { + w.length(63); + w.keys(Object.keys(v), KEYS163); + encT71(w, v.fields); + if (v.nullProto === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.nullProto); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "recordNullProto": { + w.length(64); + w.keys(Object.keys(v), KEYS164); + encIrExpr(w, v.obj); + w.str(v.shapeId); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "recordOvfHas": { + w.length(65); + w.keys(Object.keys(v), KEYS165); + encIrExpr(w, v.obj); + w.str(v.shapeId); + encIrExpr(w, v.key); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "recordOvfKeys": { + w.length(66); + w.keys(Object.keys(v), KEYS166); + encIrExpr(w, v.obj); + w.str(v.shapeId); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "regexIntrinsic": { + w.length(67); + w.keys(Object.keys(v), KEYS167); + w.str(v.method); + encIrExpr(w, v.receiver); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "regexLit": { + w.length(68); + w.keys(Object.keys(v), KEYS168); + w.str(v.pattern); + w.str(v.flags); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "selfRef": { + w.length(69); + w.keys(Object.keys(v), KEYS169); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "seqExpr": { + w.length(70); + w.keys(Object.keys(v), KEYS170); + encT19(w, v.stmts); + encIrExpr(w, v.result); + if (v.generatorDelegate === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.generatorDelegate); + } + if (v.diverges === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.diverges); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "setIntrinsic": { + w.length(71); + w.keys(Object.keys(v), KEYS171); + w.str(v.method); + encIrExpr(w, v.receiver); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "setNew": { + w.length(72); + w.keys(Object.keys(v), KEYS172); + if (v.seed === undefined) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.seed); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "strCmp": { + w.length(73); + w.keys(Object.keys(v), KEYS173); + w.str(v.op); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "strConcat": { + w.length(74); + w.keys(Object.keys(v), KEYS174); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "strEq": { + w.length(75); + w.keys(Object.keys(v), KEYS175); + w.bool(v.negated); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "strIntrinsic": { + w.length(76); + w.keys(Object.keys(v), KEYS176); + w.str(v.method); + encIrExpr(w, v.receiver); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "strLit": { + w.length(77); + w.keys(Object.keys(v), KEYS177); + w.str(v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "templateStrings": { + w.length(78); + w.keys(Object.keys(v), KEYS178); + w.str(v.key); + encT8(w, v.cooked); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "ternary": { + w.length(79); + w.keys(Object.keys(v), KEYS179); + encIrExpr(w, v.cond); + encIrExpr(w, v.then); + encIrExpr(w, v.else_); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "toBool": { + w.length(80); + w.keys(Object.keys(v), KEYS180); + encIrExpr(w, v.operand); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "toString": { + w.length(81); + w.keys(Object.keys(v), KEYS181); + encIrExpr(w, v.operand); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unary": { + w.length(82); + w.keys(Object.keys(v), KEYS182); + w.str(v.op); + encIrExpr(w, v.operand); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unionDisc": { + w.length(83); + w.keys(Object.keys(v), KEYS183); + w.str(v.unionId); + w.str(v.field); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unionEq": { + w.length(84); + w.keys(Object.keys(v), KEYS184); + w.str(v.unionId); + w.bool(v.negated); + w.bool(v.sameValue); + encIrExpr(w, v.left); + encIrExpr(w, v.right); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unionFuncEq": { + w.length(85); + w.keys(Object.keys(v), KEYS185); + w.str(v.unionId); + w.num(v.tag); + encIrExpr(w, v.union); + encIrExpr(w, v.func); + w.bool(v.negated); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unionIsTag": { + w.length(86); + w.keys(Object.keys(v), KEYS186); + w.str(v.unionId); + w.num(v.tag); + w.bool(v.negated); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unionKeyGet": { + w.length(87); + w.keys(Object.keys(v), KEYS187); + w.str(v.unionId); + encIrExpr(w, v.key); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unionNarrow": { + w.length(88); + w.keys(Object.keys(v), KEYS188); + w.str(v.unionId); + w.num(v.tag); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unionRetype": { + w.length(89); + w.keys(Object.keys(v), KEYS189); + w.str(v.fromId); + w.str(v.unionId); + encT65(w, v.tags); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unionWrap": { + w.length(90); + w.keys(Object.keys(v), KEYS190); + w.str(v.unionId); + w.num(v.tag); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "unitLit": { + w.length(91); + w.keys(Object.keys(v), KEYS191); + w.str(v.unit); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "upcast": { + w.length(92); + w.keys(Object.keys(v), KEYS192); + encIrExpr(w, v.value); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "varRef": { + w.length(93); + w.keys(Object.keys(v), KEYS193); + w.str(v.localId); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + case "virtualCall": { + w.length(94); + w.keys(Object.keys(v), KEYS194); + w.str(v.className); + w.str(v.method); + encT64(w, v.args); + encIrType(w, v.type); + encSrcLoc(w, v.loc); + if (v.targets === undefined) w.byte(0); + else { + w.byte(1); + encT8(w, v.targets); + } + return; + } + case "yieldExpr": { + w.length(95); + w.keys(Object.keys(v), KEYS195); + if (v.value === null) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.value); + } + if (v.awaited === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.awaited); + } + if (v.delegated === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.delegated); + } + if (v.captureCompletion === undefined) w.byte(0); + else { + w.byte(1); + encT72(w, v.captureCompletion); + } + encIrType(w, v.type); + encSrcLoc(w, v.loc); + return; + } + } + w.refuse("IrExpr kind " + String((v as { kind: unknown }).kind)); +} +function decIrExpr(r: IrReader): IrExpr { + switch (r.length()) { + case 0: { + const f0 = (r.str() as NonNullable["method"]>); + const f1 = decIrExpr(r); + const f2 = decT64(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "arrIntrinsic", method: f0, receiver: f1, args: f2, type: f3, loc: f4 }; + return o; + } + case 1: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "arrayGet", arr: f0, index: f1, type: f2, loc: f3 }; + return o; + } + case 2: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "arrayHas", arr: f0, index: f1, type: f2, loc: f3 }; + return o; + } + case 3: { + const f0 = decT64(r); + const f1 = r.tag(2, "IrFunction.body[].arr.spreads") === 0 ? undefined : decT65(r); + const f2 = r.tag(2, "IrFunction.body[].arr.capacity") === 0 ? undefined : r.num(); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "arrayLit", elems: f0, ...(f1 !== undefined ? { spreads: f1 } : {}), ...(f2 !== undefined ? { capacity: f2 } : {}), type: f3, loc: f4 }; + return o; + } + case 4: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "arrayNewLen", length: f0, type: f1, loc: f2 }; + return o; + } + case 5: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "arrayState", arr: f0, index: f1, type: f2, loc: f3 }; + return o; + } + case 6: { + const f0 = r.str(); + const f1 = decIrExpr(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "assignExpr", localId: f0, value: f1, type: f2, loc: f3 }; + return o; + } + case 7: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = r.tag(2, "IrFunction.body[].arr.adopt") === 0 ? undefined : (r.bool() as NonNullable["adopt"]>); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "awaitExpr", value: f0, type: f1, ...(f2 !== undefined ? { adopt: f2 } : {}), loc: f3 }; + return o; + } + case 8: { + const f0 = decIrExpr(r); + const f1 = r.num(); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "awaitUnionExpr", value: f0, promiseTag: f1, type: f2, loc: f3 }; + return o; + } + case 9: { + const f0 = (r.str() as NonNullable["op"]>); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "bin", op: f0, left: f1, right: f2, type: f3, loc: f4 }; + return o; + } + case 10: { + const f0 = r.bool(); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "boolLit", value: f0, type: f1, loc: f2 }; + return o; + } + case 11: { + const f0 = (r.str() as NonNullable["method"]>); + const f1 = decIrExpr(r); + const f2 = decT64(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const f5 = r.tag(2, "IrFunction.body[].arr.invalidNaN") === 0 ? undefined : (r.bool() as NonNullable["invalidNaN"]>); + const o: Extract = { kind: "bytesIntrinsic", method: f0, receiver: f1, args: f2, type: f3, loc: f4, ...(f5 !== undefined ? { invalidNaN: f5 } : {}) }; + return o; + } + case 12: { + const f0 = r.tag(2, "IrFunction.body[].arr.source") === 0 ? null : decIrExpr(r); + const f1 = r.tag(2, "IrFunction.body[].arr.from") === 0 ? undefined : (r.bool() as NonNullable["from"]>); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "bytesNew", source: f0, ...(f1 !== undefined ? { from: f1 } : {}), type: f2, loc: f3 }; + return o; + } + case 13: { + const f0 = r.str(); + const f1 = decT64(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "call", callee: f0, args: f1, type: f2, loc: f3 }; + return o; + } + case 14: { + const f0 = decIrExpr(r); + const f1 = r.tag(2, "IrFunction.body[].arr.receiver") === 0 ? undefined : decIrExpr(r); + const f2 = decT64(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "callValue", callee: f0, ...(f1 !== undefined ? { receiver: f1 } : {}), args: f2, type: f3, loc: f4 }; + return o; + } + case 15: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "caughtCheck", value: f0, className: f1, type: f2, loc: f3 }; + return o; + } + case 16: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "caughtNarrow", value: f0, type: f1, loc: f2 }; + return o; + } + case 17: { + const f0 = decIrExpr(r); + const f1 = (r.str() as NonNullable["test"]>); + const f2 = r.tag(2, "IrFunction.body[].arr.className") === 0 ? undefined : r.str(); + const f3 = r.tag(2, "IrFunction.body[].arr.negated") === 0 ? undefined : r.bool(); + const f4 = decIrType(r); + const f5 = decSrcLoc(r); + const o: Extract = { kind: "caughtTest", value: f0, test: f1, ...(f2 !== undefined ? { className: f2 } : {}), ...(f3 !== undefined ? { negated: f3 } : {}), type: f4, loc: f5 }; + return o; + } + case 18: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "caughtToDyn", value: f0, type: f1, loc: f2 }; + return o; + } + case 19: { + const f0 = r.str(); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "chainRecv", id: f0, type: f1, loc: f2 }; + return o; + } + case 20: { + const f0 = r.str(); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const f3 = r.tag(2, "IrFunction.body[].arr.captures") === 0 ? undefined : decT8(r); + const o: Extract = { kind: "classRef", className: f0, type: f1, loc: f2, ...(f3 !== undefined ? { captures: f3 } : {}) }; + return o; + } + case 21: { + const f0 = r.str(); + const f1 = decT8(r); + const f2 = r.tag(2, "IrFunction.body[].arr.adapts") === 0 ? undefined : (r.bool() as NonNullable["adapts"]>); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "closure", fnName: f0, captures: f1, ...(f2 !== undefined ? { adapts: f2 } : {}), type: f3, loc: f4 }; + return o; + } + case 22: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "downcast", value: f0, type: f1, loc: f2 }; + return o; + } + case 23: { + const f0 = decT64(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "dynArrLit", elems: f0, type: f1, loc: f2 }; + return o; + } + case 24: { + const f0 = decIrExpr(r); + const f1 = r.tag(2, "IrFunction.body[].arr.receiver") === 0 ? undefined : decIrExpr(r); + const f2 = r.str(); + const f3 = r.tag(2, "IrFunction.body[].arr.calleeNameValue") === 0 ? undefined : decIrExpr(r); + const f4 = decT64(r); + const f5 = r.tag(2, "IrFunction.body[].arr.spreads") === 0 ? undefined : decT66(r); + const f6 = decIrType(r); + const f7 = decSrcLoc(r); + const o: Extract = { kind: "dynCall", callee: f0, ...(f1 !== undefined ? { receiver: f1 } : {}), calleeName: f2, ...(f3 !== undefined ? { calleeNameValue: f3 } : {}), args: f4, ...(f5 !== undefined ? { spreads: f5 } : {}), type: f6, loc: f7 }; + return o; + } + case 25: { + const f0 = decIrExpr(r); + const f1 = r.tag(2, "IrFunction.body[].arr.preserveRefs") === 0 ? undefined : (r.bool() as NonNullable["preserveRefs"]>); + const f2 = r.tag(2, "IrFunction.body[].arr.adopt") === 0 ? undefined : (r.bool() as NonNullable["adopt"]>); + const f3 = r.tag(2, "IrFunction.body[].arr.exact") === 0 ? undefined : (r.bool() as NonNullable["exact"]>); + const f4 = r.tag(2, "IrFunction.body[].arr.inferredSite") === 0 ? undefined : r.str(); + const f5 = r.tag(2, "IrFunction.body[].arr.origin") === 0 ? undefined : r.num(); + const f6 = decIrType(r); + const f7 = decSrcLoc(r); + const o: Extract = { kind: "dynCheck", value: f0, ...(f1 !== undefined ? { preserveRefs: f1 } : {}), ...(f2 !== undefined ? { adopt: f2 } : {}), ...(f3 !== undefined ? { exact: f3 } : {}), ...(f4 !== undefined ? { inferredSite: f4 } : {}), ...(f5 !== undefined ? { origin: f5 } : {}), type: f6, loc: f7 }; + return o; + } + case 26: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = r.tag(2, "IrFunction.body[].arr.firstProp") === 0 ? undefined : r.str(); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "dynDestrCheck", value: f0, spelling: f1, ...(f2 !== undefined ? { firstProp: f2 } : {}), type: f3, loc: f4 }; + return o; + } + case 27: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const f3 = r.tag(2, "IrFunction.body[].arr.liveRef") === 0 ? undefined : (r.bool() as NonNullable["liveRef"]>); + const f4 = r.tag(2, "IrFunction.body[].arr.fnName") === 0 ? undefined : r.str(); + const f5 = r.tag(2, "IrFunction.body[].arr.fnLength") === 0 ? undefined : r.num(); + const o: Extract = { kind: "dynFrom", value: f0, type: f1, loc: f2, ...(f3 !== undefined ? { liveRef: f3 } : {}), ...(f4 !== undefined ? { fnName: f4 } : {}), ...(f5 !== undefined ? { fnLength: f5 } : {}) }; + return o; + } + case 28: { + const f0 = r.str(); + const f1 = r.tag(2, "IrFunction.body[].arr.negated") === 0 ? undefined : (r.bool() as NonNullable["negated"]>); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "dynHasKey", key: f0, ...(f1 !== undefined ? { negated: f1 } : {}), value: f2, type: f3, loc: f4 }; + return o; + } + case 29: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = r.str(); + const f3 = r.tag(2, "IrFunction.body[].arr.calleeNameValue") === 0 ? undefined : decIrExpr(r); + const f4 = decT64(r); + const f5 = decIrType(r); + const f6 = decSrcLoc(r); + const o: Extract = { kind: "dynInvoke", recv: f0, method: f1, calleeName: f2, ...(f3 !== undefined ? { calleeNameValue: f3 } : {}), args: f4, type: f5, loc: f6 }; + return o; + } + case 30: { + const f0 = decIrExpr(r); + const f1 = r.num(); + const f2 = r.tag(2, "IrFunction.body[].arr.notIterableMessage") === 0 ? undefined : r.str(); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "dynIterN", value: f0, count: f1, ...(f2 !== undefined ? { notIterableMessage: f2 } : {}), type: f3, loc: f4 }; + return o; + } + case 31: { + const f0 = decIrExpr(r); + const f1 = r.tag(2, "IrFunction.body[].arr.optional") === 0 ? undefined : (r.bool() as NonNullable["optional"]>); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "dynKeyGet", key: f0, ...(f1 !== undefined ? { optional: f1 } : {}), value: f2, type: f3, loc: f4 }; + return o; + } + case 32: { + const f0 = r.tag(2, "IrFunction.body[].arr.fields") === 0 ? undefined : decT67(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "dynObjLit", ...(f0 !== undefined ? { fields: f0 } : {}), type: f1, loc: f2 }; + return o; + } + case 33: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = r.tag(2, "IrFunction.body[].arr.negated") === 0 ? undefined : (r.bool() as NonNullable["negated"]>); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "dynScalarEq", left: f0, right: f1, ...(f2 !== undefined ? { negated: f2 } : {}), type: f3, loc: f4 }; + return o; + } + case 34: { + const f0 = (r.str() as NonNullable["test"]>); + const f1 = r.tag(2, "IrFunction.body[].arr.bytesElem") === 0 ? undefined : (r.str() as NonNullable["bytesElem"]>); + const f2 = r.tag(2, "IrFunction.body[].arr.negated") === 0 ? undefined : (r.bool() as NonNullable["negated"]>); + const f3 = decIrExpr(r); + const f4 = decIrType(r); + const f5 = decSrcLoc(r); + const o: Extract = { kind: "dynTest", test: f0, ...(f1 !== undefined ? { bytesElem: f1 } : {}), ...(f2 !== undefined ? { negated: f2 } : {}), value: f3, type: f4, loc: f5 }; + return o; + } + case 35: { + const f0 = r.str(); + const f1 = decT64(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "ffiCall", import: f0, args: f1, type: f2, loc: f3 }; + return o; + } + case 36: { + const f0 = r.str(); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "fieldAbsent", unionId: f0, type: f1, loc: f2 }; + return o; + } + case 37: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = r.str(); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "fieldGet", obj: f0, className: f1, field: f2, type: f3, loc: f4 }; + return o; + } + case 38: { + const f0 = (r.str() as NonNullable["op"]>); + const f1 = r.bool(); + const f2 = decIrExpr(r); + const f3 = r.str(); + const f4 = r.str(); + const f5 = r.bool(); + const f6 = decIrType(r); + const f7 = decSrcLoc(r); + const o: Extract = { kind: "fieldIncDec", op: f0, prefix: f1, obj: f2, className: f3, field: f4, fieldDyn: f5, type: f6, loc: f7 }; + return o; + } + case 39: { + const f0 = (r.str() as NonNullable["mode"]>); + const f1 = decIrExpr(r); + const f2 = r.tag(2, "IrFunction.body[].arr.arg") === 0 ? null : decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "genResume", mode: f0, gen: f1, arg: f2, type: f3, loc: f4 }; + return o; + } + case 40: { + const f0 = (r.str() as NonNullable["op"]>); + const f1 = r.bool(); + const f2 = r.str(); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "incDec", op: f0, prefix: f1, localId: f2, type: f3, loc: f4 }; + return o; + } + case 41: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "instanceOf", value: f0, className: f1, type: f2, loc: f3 }; + return o; + } + case 42: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "instanceOfValue", value: f0, classValue: f1, type: f2, loc: f3 }; + return o; + } + case 43: { + const f0 = (r.str() as NonNullable["name"]>); + const f1 = decT64(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "intrinsic", name: f0, args: f1, type: f2, loc: f3 }; + return o; + } + case 44: { + const f0 = decIrExpr(r); + const f1 = r.tag(2, "IrFunction.body[].arr.indent") === 0 ? undefined : r.str(); + const f2 = r.tag(2, "IrFunction.body[].arr.circular") === 0 ? undefined : (r.bool() as NonNullable["circular"]>); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "jsonStringify", value: f0, ...(f1 !== undefined ? { indent: f1 } : {}), ...(f2 !== undefined ? { circular: f2 } : {}), type: f3, loc: f4 }; + return o; + } + case 45: { + const f0 = (r.str() as NonNullable["fn"]>); + const f1 = decT64(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const f4 = r.tag(2, "IrFunction.body[].arr.prototypeIdentityOnly") === 0 ? undefined : (r.bool() as NonNullable["prototypeIdentityOnly"]>); + const f5 = r.tag(2, "IrFunction.body[].arr.realTimer") === 0 ? undefined : (r.bool() as NonNullable["realTimer"]>); + const f6 = r.tag(2, "IrFunction.body[].arr.assertOptions") === 0 ? undefined : decT68(r); + const o: Extract = { kind: "libCall", fn: f0, args: f1, type: f2, loc: f3, ...(f4 !== undefined ? { prototypeIdentityOnly: f4 } : {}), ...(f5 !== undefined ? { realTimer: f5 } : {}), ...(f6 !== undefined ? { assertOptions: f6 } : {}) }; + return o; + } + case 46: { + const f0 = (r.str() as NonNullable["op"]>); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "logical", op: f0, left: f1, right: f2, type: f3, loc: f4 }; + return o; + } + case 47: { + const f0 = (r.str() as NonNullable["method"]>); + const f1 = decIrExpr(r); + const f2 = decT64(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "mapIntrinsic", method: f0, receiver: f1, args: f2, type: f3, loc: f4 }; + return o; + } + case 48: { + const f0 = r.tag(2, "IrFunction.body[].arr.seed") === 0 ? undefined : decT69(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "mapNew", ...(f0 !== undefined ? { seed: f0 } : {}), type: f1, loc: f2 }; + return o; + } + case 49: { + const f0 = r.str(); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "moduleNsRef", moduleId: f0, type: f1, loc: f2 }; + return o; + } + case 50: { + const f0 = r.str(); + const f1 = decT64(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "new", className: f0, args: f1, type: f2, loc: f3 }; + return o; + } + case 51: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "newPromise", executor: f0, type: f1, loc: f2 }; + return o; + } + case 52: { + const f0 = decIrExpr(r); + const f1 = decT64(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "newValue", callee: f0, args: f1, type: f2, loc: f3 }; + return o; + } + case 53: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "nullish", left: f0, right: f1, type: f2, loc: f3 }; + return o; + } + case 54: { + const f0 = r.num(); + const f1 = r.tag(2, "IrFunction.body[].arr.spelling") === 0 ? undefined : r.str(); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "numLit", value: f0, ...(f1 !== undefined ? { spelling: f1 } : {}), type: f2, loc: f3 }; + return o; + } + case 55: { + const f0 = r.str(); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "optChain", id: f0, receiver: f1, body: f2, type: f3, loc: f4 }; + return o; + } + case 56: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = r.tag(2, "IrFunction.body[].arr.retag") === 0 ? undefined : r.str(); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "orDefault", left: f0, right: f1, ...(f2 !== undefined ? { retag: f2 } : {}), type: f3, loc: f4 }; + return o; + } + case 57: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "promiseVoidWiden", value: f0, type: f1, loc: f2 }; + return o; + } + case 58: { + const f0 = decIrType(r); + const f1 = decSrcLoc(r); + const o: Extract = { kind: "promiseWithResolvers", type: f0, loc: f1 }; + return o; + } + case 59: { + const f0 = decIrExpr(r); + const f1 = decT70(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "recordClone", source: f0, overrides: f1, type: f2, loc: f3 }; + return o; + } + case 60: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = r.str(); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "recordGet", obj: f0, shapeId: f1, field: f2, type: f3, loc: f4 }; + return o; + } + case 61: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = r.str(); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "recordHas", obj: f0, shapeId: f1, field: f2, type: f3, loc: f4 }; + return o; + } + case 62: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = decIrExpr(r); + const f3 = r.tag(2, "IrFunction.body[].arr.overflowOnly") === 0 ? undefined : (r.bool() as NonNullable["overflowOnly"]>); + const f4 = r.tag(2, "IrFunction.body[].arr.missNaN") === 0 ? undefined : (r.bool() as NonNullable["missNaN"]>); + const f5 = decIrType(r); + const f6 = decSrcLoc(r); + const o: Extract = { kind: "recordKeyGet", obj: f0, shapeId: f1, key: f2, ...(f3 !== undefined ? { overflowOnly: f3 } : {}), ...(f4 !== undefined ? { missNaN: f4 } : {}), type: f5, loc: f6 }; + return o; + } + case 63: { + const f0 = decT71(r); + const f1 = r.tag(2, "IrFunction.body[].arr.nullProto") === 0 ? undefined : (r.bool() as NonNullable["nullProto"]>); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "recordLit", fields: f0, ...(f1 !== undefined ? { nullProto: f1 } : {}), type: f2, loc: f3 }; + return o; + } + case 64: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "recordNullProto", obj: f0, shapeId: f1, type: f2, loc: f3 }; + return o; + } + case 65: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "recordOvfHas", obj: f0, shapeId: f1, key: f2, type: f3, loc: f4 }; + return o; + } + case 66: { + const f0 = decIrExpr(r); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "recordOvfKeys", obj: f0, shapeId: f1, type: f2, loc: f3 }; + return o; + } + case 67: { + const f0 = (r.str() as NonNullable["method"]>); + const f1 = decIrExpr(r); + const f2 = decT64(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "regexIntrinsic", method: f0, receiver: f1, args: f2, type: f3, loc: f4 }; + return o; + } + case 68: { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "regexLit", pattern: f0, flags: f1, type: f2, loc: f3 }; + return o; + } + case 69: { + const f0 = decIrType(r); + const f1 = decSrcLoc(r); + const o: Extract = { kind: "selfRef", type: f0, loc: f1 }; + return o; + } + case 70: { + const f0 = decT19(r); + const f1 = decIrExpr(r); + const f2 = r.tag(2, "IrFunction.body[].arr.generatorDelegate") === 0 ? undefined : (r.bool() as NonNullable["generatorDelegate"]>); + const f3 = r.tag(2, "IrFunction.body[].arr.diverges") === 0 ? undefined : (r.bool() as NonNullable["diverges"]>); + const f4 = decIrType(r); + const f5 = decSrcLoc(r); + const o: Extract = { kind: "seqExpr", stmts: f0, result: f1, ...(f2 !== undefined ? { generatorDelegate: f2 } : {}), ...(f3 !== undefined ? { diverges: f3 } : {}), type: f4, loc: f5 }; + return o; + } + case 71: { + const f0 = (r.str() as NonNullable["method"]>); + const f1 = decIrExpr(r); + const f2 = decT64(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "setIntrinsic", method: f0, receiver: f1, args: f2, type: f3, loc: f4 }; + return o; + } + case 72: { + const f0 = r.tag(2, "IrFunction.body[].arr.seed") === 0 ? undefined : decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "setNew", ...(f0 !== undefined ? { seed: f0 } : {}), type: f1, loc: f2 }; + return o; + } + case 73: { + const f0 = (r.str() as NonNullable["op"]>); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "strCmp", op: f0, left: f1, right: f2, type: f3, loc: f4 }; + return o; + } + case 74: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "strConcat", left: f0, right: f1, type: f2, loc: f3 }; + return o; + } + case 75: { + const f0 = r.bool(); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "strEq", negated: f0, left: f1, right: f2, type: f3, loc: f4 }; + return o; + } + case 76: { + const f0 = (r.str() as NonNullable["method"]>); + const f1 = decIrExpr(r); + const f2 = decT64(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "strIntrinsic", method: f0, receiver: f1, args: f2, type: f3, loc: f4 }; + return o; + } + case 77: { + const f0 = r.str(); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "strLit", value: f0, type: f1, loc: f2 }; + return o; + } + case 78: { + const f0 = r.str(); + const f1 = decT8(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "templateStrings", key: f0, cooked: f1, type: f2, loc: f3 }; + return o; + } + case 79: { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "ternary", cond: f0, then: f1, else_: f2, type: f3, loc: f4 }; + return o; + } + case 80: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "toBool", operand: f0, type: f1, loc: f2 }; + return o; + } + case 81: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "toString", operand: f0, type: f1, loc: f2 }; + return o; + } + case 82: { + const f0 = (r.str() as NonNullable["op"]>); + const f1 = decIrExpr(r); + const f2 = decIrType(r); + const f3 = decSrcLoc(r); + const o: Extract = { kind: "unary", op: f0, operand: f1, type: f2, loc: f3 }; + return o; + } + case 83: { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "unionDisc", unionId: f0, field: f1, value: f2, type: f3, loc: f4 }; + return o; + } + case 84: { + const f0 = r.str(); + const f1 = r.bool(); + const f2 = r.bool(); + const f3 = decIrExpr(r); + const f4 = decIrExpr(r); + const f5 = decIrType(r); + const f6 = decSrcLoc(r); + const o: Extract = { kind: "unionEq", unionId: f0, negated: f1, sameValue: f2, left: f3, right: f4, type: f5, loc: f6 }; + return o; + } + case 85: { + const f0 = r.str(); + const f1 = r.num(); + const f2 = decIrExpr(r); + const f3 = decIrExpr(r); + const f4 = r.bool(); + const f5 = decIrType(r); + const f6 = decSrcLoc(r); + const o: Extract = { kind: "unionFuncEq", unionId: f0, tag: f1, union: f2, func: f3, negated: f4, type: f5, loc: f6 }; + return o; + } + case 86: { + const f0 = r.str(); + const f1 = r.num(); + const f2 = r.bool(); + const f3 = decIrExpr(r); + const f4 = decIrType(r); + const f5 = decSrcLoc(r); + const o: Extract = { kind: "unionIsTag", unionId: f0, tag: f1, negated: f2, value: f3, type: f4, loc: f5 }; + return o; + } + case 87: { + const f0 = r.str(); + const f1 = decIrExpr(r); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "unionKeyGet", unionId: f0, key: f1, value: f2, type: f3, loc: f4 }; + return o; + } + case 88: { + const f0 = r.str(); + const f1 = r.num(); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "unionNarrow", unionId: f0, tag: f1, value: f2, type: f3, loc: f4 }; + return o; + } + case 89: { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decT65(r); + const f3 = decIrExpr(r); + const f4 = decIrType(r); + const f5 = decSrcLoc(r); + const o: Extract = { kind: "unionRetype", fromId: f0, unionId: f1, tags: f2, value: f3, type: f4, loc: f5 }; + return o; + } + case 90: { + const f0 = r.str(); + const f1 = r.num(); + const f2 = decIrExpr(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const o: Extract = { kind: "unionWrap", unionId: f0, tag: f1, value: f2, type: f3, loc: f4 }; + return o; + } + case 91: { + const f0 = (r.str() as NonNullable["unit"]>); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "unitLit", unit: f0, type: f1, loc: f2 }; + return o; + } + case 92: { + const f0 = decIrExpr(r); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "upcast", value: f0, type: f1, loc: f2 }; + return o; + } + case 93: { + const f0 = r.str(); + const f1 = decIrType(r); + const f2 = decSrcLoc(r); + const o: Extract = { kind: "varRef", localId: f0, type: f1, loc: f2 }; + return o; + } + case 94: { + const f0 = r.str(); + const f1 = r.str(); + const f2 = decT64(r); + const f3 = decIrType(r); + const f4 = decSrcLoc(r); + const f5 = r.tag(2, "IrFunction.body[].arr.targets") === 0 ? undefined : decT8(r); + const o: Extract = { kind: "virtualCall", className: f0, method: f1, args: f2, type: f3, loc: f4, ...(f5 !== undefined ? { targets: f5 } : {}) }; + return o; + } + case 95: { + const f0 = r.tag(2, "IrFunction.body[].arr.value") === 0 ? null : decIrExpr(r); + const f1 = r.tag(2, "IrFunction.body[].arr.awaited") === 0 ? undefined : (r.bool() as NonNullable["awaited"]>); + const f2 = r.tag(2, "IrFunction.body[].arr.delegated") === 0 ? undefined : (r.bool() as NonNullable["delegated"]>); + const f3 = r.tag(2, "IrFunction.body[].arr.captureCompletion") === 0 ? undefined : decT72(r); + const f4 = decIrType(r); + const f5 = decSrcLoc(r); + const o: Extract = { kind: "yieldExpr", value: f0, ...(f1 !== undefined ? { awaited: f1 } : {}), ...(f2 !== undefined ? { delegated: f2 } : {}), ...(f3 !== undefined ? { captureCompletion: f3 } : {}), type: f4, loc: f5 }; + return o; + } + } + throw new Error("IR codec: invalid IrExpr kind"); +} + +function encT54(w: IrWriter, v: NonNullable["cases"]>): void { + w.length(v.length); + for (const item of v) { + encT73(w, item); + } +} +function decT54(r: IrReader): NonNullable["cases"]> { + const length = r.length(); + const out: NonNullable["cases"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decT73(r)); + return out; +} + +function encT55(w: IrWriter, v: NonNullable[number]>): void { + if (typeof v === "object" && "globalId" in v) { + w.length(0); + encT74(w, v as Extract[number]>, { globalId: unknown }>); + return; + } + if (typeof v === "object" && "wellKnown" in v) { + w.length(1); + encT75(w, v as Extract[number]>, { wellKnown: unknown }>); + return; + } + throw new Error("IR codec: no arm of T55 matches"); +} +function decT55(r: IrReader): NonNullable[number]> { + switch (r.length()) { + case 0: + return decT74(r) as NonNullable[number]>; + case 1: + return decT75(r) as NonNullable[number]>; + } + throw new Error("IR codec: invalid T55 arm"); +} + +function encT56(w: IrWriter, v: NonNullable[number]>): void { + w.keys(Object.keys(v), KEYS196); + w.str(v.name); + encIrType(w, v.type); +} +function decT56(r: IrReader): NonNullable[number]> { + const f0 = r.str(); + const f1 = decIrType(r); + const o: NonNullable[number]> = { name: f0, type: f1 }; + return o; +} + +function encT57(w: IrWriter, v: NonNullable[number]>): void { + w.keys(Object.keys(v), KEYS197); + w.str(v.name); + encIrType(w, v.type); +} +function decT57(r: IrReader): NonNullable[number]> { + const f0 = r.str(); + const f1 = decIrType(r); + const o: NonNullable[number]> = { name: f0, type: f1 }; + return o; +} + +function encT58(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + encT76(w, item); + } +} +function decT58(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push(decT76(r)); + return out; +} + +function encT59(w: IrWriter, v: NonNullable[number]>): void { + w.keys(Object.keys(v), KEYS198); + w.str(v.path); + w.num(v.size); + w.str(v.sha256); + if (v.placeholder === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.placeholder); + } +} +function decT59(r: IrReader): NonNullable[number]> { + const f0 = r.str(); + const f1 = r.num(); + const f2 = r.str(); + const f3 = r.tag(2, "IrModule.nodeIsland.modules.files[].placeholder") === 0 ? undefined : (r.bool() as NonNullable[number]>["placeholder"]>); + const o: NonNullable[number]> = { path: f0, size: f1, sha256: f2, ...(f3 !== undefined ? { placeholder: f3 } : {}) }; + return o; +} + +function encT60(w: IrWriter, v: NonNullable[number]>): void { + w.keys(Object.keys(v), KEYS199); + w.str(v.path); + w.str(v.target); +} +function decT60(r: IrReader): NonNullable[number]> { + const f0 = r.str(); + const f1 = r.str(); + const o: NonNullable[number]> = { path: f0, target: f1 }; + return o; +} + +function encIrFfiParam(w: IrWriter, v: IrFfiParam): void { + if (typeof v === "string") { + w.length(0); + w.str(v); + return; + } + if (typeof v === "object" && "context" in v) { + w.length(1); + encIrFfiContextParam(w, v as Extract); + return; + } + if (typeof v === "object" && "callback" in v && "id" in (v as { callback: object }).callback) { + w.length(2); + encIrFfiCallbackParam(w, v as Extract); + return; + } + if (typeof v === "object" && "callback" in v && "release" in (v as { callback: object }).callback) { + w.length(3); + encIrFfiReleaseParam(w, v as Extract); + return; + } + throw new Error("IR codec: no arm of IrFfiParam matches"); +} +function decIrFfiParam(r: IrReader): IrFfiParam { + switch (r.length()) { + case 0: + return r.str() as IrFfiParam; + case 1: + return decIrFfiContextParam(r) as IrFfiParam; + case 2: + return decIrFfiCallbackParam(r) as IrFfiParam; + case 3: + return decIrFfiReleaseParam(r) as IrFfiParam; + } + throw new Error("IR codec: invalid IrFfiParam arm"); +} + +function encT62(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + w.str(item); + } +} +function decT62(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push((r.str() as NonNullable[number]>)); + return out; +} + +function encT63(w: IrWriter, v: NonNullable): void { + w.length(v.length); + for (const item of v) { + w.str(item); + } +} +function decT63(r: IrReader): NonNullable { + const length = r.length(); + const out: NonNullable[number][] = []; + for (let i = 0; i < length; i++) out.push((r.str() as NonNullable[number]>)); + return out; +} + +function encT64(w: IrWriter, v: NonNullable["args"]>): void { + w.length(v.length); + for (const item of v) { + encIrExpr(w, item); + } +} +function decT64(r: IrReader): NonNullable["args"]> { + const length = r.length(); + const out: NonNullable["args"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decIrExpr(r)); + return out; +} + +function encT65(w: IrWriter, v: NonNullable["spreads"]>): void { + w.length(v.length); + for (const item of v) { + w.num(item); + } +} +function decT65(r: IrReader): NonNullable["spreads"]> { + const length = r.length(); + const out: NonNullable["spreads"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(r.num()); + return out; +} + +function encT66(w: IrWriter, v: NonNullable["spreads"]>): void { + w.length(v.length); + for (const item of v) { + encT80(w, item); + } +} +function decT66(r: IrReader): NonNullable["spreads"]> { + const length = r.length(); + const out: NonNullable["spreads"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decT80(r)); + return out; +} + +function encT67(w: IrWriter, v: NonNullable["fields"]>): void { + w.length(v.length); + for (const item of v) { + encT81(w, item); + } +} +function decT67(r: IrReader): NonNullable["fields"]> { + const length = r.length(); + const out: NonNullable["fields"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decT81(r)); + return out; +} + +function encT68(w: IrWriter, v: NonNullable["assertOptions"]>): void { + w.keys(Object.keys(v), KEYS200); + if (v.diff === undefined) w.byte(0); + else { + w.byte(1); + w.str(v.diff); + } + if (v.skipPrototype === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.skipPrototype); + } +} +function decT68(r: IrReader): NonNullable["assertOptions"]> { + const f0 = r.tag(2, "IrFunction.body[].arr.assertOptions.diff") === 0 ? undefined : (r.str() as NonNullable["assertOptions"]>["diff"]>); + const f1 = r.tag(2, "IrFunction.body[].arr.assertOptions.skipPrototype") === 0 ? undefined : (r.bool() as NonNullable["assertOptions"]>["skipPrototype"]>); + const o: NonNullable["assertOptions"]> = { ...(f0 !== undefined ? { diff: f0 } : {}), ...(f1 !== undefined ? { skipPrototype: f1 } : {}) }; + return o; +} + +function encT69(w: IrWriter, v: NonNullable["seed"]>): void { + w.length(v.length); + for (const item of v) { + encT82(w, item); + } +} +function decT69(r: IrReader): NonNullable["seed"]> { + const length = r.length(); + const out: NonNullable["seed"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decT82(r)); + return out; +} + +function encT70(w: IrWriter, v: NonNullable["overrides"]>): void { + w.length(v.length); + for (const item of v) { + encT83(w, item); + } +} +function decT70(r: IrReader): NonNullable["overrides"]> { + const length = r.length(); + const out: NonNullable["overrides"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decT83(r)); + return out; +} + +function encT71(w: IrWriter, v: NonNullable["fields"]>): void { + w.length(v.length); + for (const item of v) { + encT84(w, item); + } +} +function decT71(r: IrReader): NonNullable["fields"]> { + const length = r.length(); + const out: NonNullable["fields"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decT84(r)); + return out; +} + +function encT72(w: IrWriter, v: NonNullable["captureCompletion"]>): void { + w.keys(Object.keys(v), KEYS201); + encIrType(w, v.returnType); +} +function decT72(r: IrReader): NonNullable["captureCompletion"]> { + const f0 = decIrType(r); + const o: NonNullable["captureCompletion"]> = { returnType: f0 }; + return o; +} + +function encT73(w: IrWriter, v: NonNullable["cases"]>[number]>): void { + w.keys(Object.keys(v), KEYS202); + if (v.test === null) w.byte(0); + else { + w.byte(1); + encIrExpr(w, v.test); + } + encT19(w, v.body); +} +function decT73(r: IrReader): NonNullable["cases"]>[number]> { + const f0 = r.tag(2, "IrFunction.body[].cases[].test") === 0 ? null : decIrExpr(r); + const f1 = decT19(r); + const o: NonNullable["cases"]>[number]> = { test: f0, body: f1 }; + return o; +} + +function encT74(w: IrWriter, v: Extract[number]>, { globalId: unknown }>): void { + w.keys(Object.keys(v), KEYS203); + w.str(v.field); + w.str(v.globalId); +} +function decT74(r: IrReader): Extract[number]>, { globalId: unknown }> { + const f0 = r.str(); + const f1 = r.str(); + const o: Extract[number]>, { globalId: unknown }> = { field: f0, globalId: f1 }; + return o; +} + +function encT75(w: IrWriter, v: Extract[number]>, { wellKnown: unknown }>): void { + w.keys(Object.keys(v), KEYS204); + w.str(v.field); + w.str(v.wellKnown); +} +function decT75(r: IrReader): Extract[number]>, { wellKnown: unknown }> { + const f0 = r.str(); + const f1 = r.str(); + const o: Extract[number]>, { wellKnown: unknown }> = { field: f0, wellKnown: f1 }; + return o; +} + +function encT76(w: IrWriter, v: NonNullable[number]>): void { + w.keys(Object.keys(v), KEYS205); + w.num(v.tag); + encT85(w, v.values); +} +function decT76(r: IrReader): NonNullable[number]> { + const f0 = r.num(); + const f1 = decT85(r); + const o: NonNullable[number]> = { tag: f0, values: f1 }; + return o; +} + +function encIrFfiContextParam(w: IrWriter, v: IrFfiContextParam): void { + w.keys(Object.keys(v), KEYS206); + w.str(v.context); +} +function decIrFfiContextParam(r: IrReader): IrFfiContextParam { + const f0 = r.str(); + const o: IrFfiContextParam = { context: f0 }; + return o; +} + +function encIrFfiCallbackParam(w: IrWriter, v: IrFfiCallbackParam): void { + w.keys(Object.keys(v), KEYS207); + encT86(w, v.callback); +} +function decIrFfiCallbackParam(r: IrReader): IrFfiCallbackParam { + const f0 = decT86(r); + const o: IrFfiCallbackParam = { callback: f0 }; + return o; +} + +function encIrFfiReleaseParam(w: IrWriter, v: IrFfiReleaseParam): void { + w.keys(Object.keys(v), KEYS208); + encT87(w, v.callback); +} +function decIrFfiReleaseParam(r: IrReader): IrFfiReleaseParam { + const f0 = decT87(r); + const o: IrFfiReleaseParam = { callback: f0 }; + return o; +} + +function encT80(w: IrWriter, v: NonNullable["spreads"]>[number]>): void { + w.keys(Object.keys(v), KEYS209); + w.num(v.arg); + w.str(v.what); +} +function decT80(r: IrReader): NonNullable["spreads"]>[number]> { + const f0 = r.num(); + const f1 = r.str(); + const o: NonNullable["spreads"]>[number]> = { arg: f0, what: f1 }; + return o; +} + +function encT81(w: IrWriter, v: NonNullable["fields"]>[number]>): void { + w.keys(Object.keys(v), KEYS210); + encIrExpr(w, v.key); + encIrExpr(w, v.value); +} +function decT81(r: IrReader): NonNullable["fields"]>[number]> { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const o: NonNullable["fields"]>[number]> = { key: f0, value: f1 }; + return o; +} + +function encT82(w: IrWriter, v: NonNullable["seed"]>[number]>): void { + w.keys(Object.keys(v), KEYS211); + encIrExpr(w, v.key); + encIrExpr(w, v.value); +} +function decT82(r: IrReader): NonNullable["seed"]>[number]> { + const f0 = decIrExpr(r); + const f1 = decIrExpr(r); + const o: NonNullable["seed"]>[number]> = { key: f0, value: f1 }; + return o; +} + +function encT83(w: IrWriter, v: NonNullable["overrides"]>[number]>): void { + w.keys(Object.keys(v), KEYS212); + w.str(v.name); + encIrExpr(w, v.value); +} +function decT83(r: IrReader): NonNullable["overrides"]>[number]> { + const f0 = r.str(); + const f1 = decIrExpr(r); + const o: NonNullable["overrides"]>[number]> = { name: f0, value: f1 }; + return o; +} + +function encT84(w: IrWriter, v: NonNullable["fields"]>[number]>): void { + w.keys(Object.keys(v), KEYS213); + w.str(v.name); + encIrExpr(w, v.value); + if (v.overflow === undefined) w.byte(0); + else { + w.byte(1); + w.bool(v.overflow); + } +} +function decT84(r: IrReader): NonNullable["fields"]>[number]> { + const f0 = r.str(); + const f1 = decIrExpr(r); + const f2 = r.tag(2, "IrFunction.body[].arr.fields[].overflow") === 0 ? undefined : (r.bool() as NonNullable["fields"]>[number]>["overflow"]>); + const o: NonNullable["fields"]>[number]> = { name: f0, value: f1, ...(f2 !== undefined ? { overflow: f2 } : {}) }; + return o; +} + +function encT85(w: IrWriter, v: NonNullable[number]>["values"]>): void { + w.length(v.length); + for (const item of v) { + encT88(w, item); + } +} +function decT85(r: IrReader): NonNullable[number]>["values"]> { + const length = r.length(); + const out: NonNullable[number]>["values"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decT88(r)); + return out; +} + +function encT86(w: IrWriter, v: NonNullable): void { + w.keys(Object.keys(v), KEYS214); + w.str(v.id); + encT89(w, v.params); + w.str(v.returns); + w.str(v.lifetime); + w.str(v.invoke); +} +function decT86(r: IrReader): NonNullable { + const f0 = r.str(); + const f1 = decT89(r); + const f2 = (r.str() as NonNullable["returns"]>); + const f3 = (r.str() as NonNullable["lifetime"]>); + const f4 = (r.str() as NonNullable["invoke"]>); + const o: NonNullable = { id: f0, params: f1, returns: f2, lifetime: f3, invoke: f4 }; + return o; +} + +function encT87(w: IrWriter, v: NonNullable): void { + w.keys(Object.keys(v), KEYS215); + w.str(v.release); + encT89(w, v.params); + w.str(v.returns); +} +function decT87(r: IrReader): NonNullable { + const f0 = r.str(); + const f1 = decT89(r); + const f2 = (r.str() as NonNullable["returns"]>); + const o: NonNullable = { release: f0, params: f1, returns: f2 }; + return o; +} + +function encT88(w: IrWriter, v: NonNullable[number]>["values"]>[number]>): void { + if (typeof v === "string") { + w.length(0); + w.str(v); + return; + } + if (typeof v === "number") { + w.length(1); + w.num(v); + return; + } + if (typeof v === "boolean") { + w.length(2); + w.bool(v); + return; + } + throw new Error("IR codec: no arm of T88 matches"); +} +function decT88(r: IrReader): NonNullable[number]>["values"]>[number]> { + switch (r.length()) { + case 0: + return r.str() as NonNullable[number]>["values"]>[number]>; + case 1: + return r.num() as NonNullable[number]>["values"]>[number]>; + case 2: + return r.bool() as NonNullable[number]>["values"]>[number]>; + } + throw new Error("IR codec: invalid T88 arm"); +} + +function encT89(w: IrWriter, v: NonNullable["params"]>): void { + w.length(v.length); + for (const item of v) { + encT90(w, item); + } +} +function decT89(r: IrReader): NonNullable["params"]> { + const length = r.length(); + const out: NonNullable["params"]>[number][] = []; + for (let i = 0; i < length; i++) out.push(decT90(r)); + return out; +} + +function encT90(w: IrWriter, v: NonNullable["params"]>[number]>): void { + if (typeof v === "string") { + w.length(0); + w.str(v); + return; + } + if (true) { + w.length(1); + encIrFfiContextParam(w, v as Exclude["params"]>[number]>, string | number | boolean | readonly unknown[]>); + return; + } + throw new Error("IR codec: no arm of T90 matches"); +} +function decT90(r: IrReader): NonNullable["params"]>[number]> { + switch (r.length()) { + case 0: + return r.str() as NonNullable["params"]>[number]>; + case 1: + return decIrFfiContextParam(r) as NonNullable["params"]>[number]>; + } + throw new Error("IR codec: invalid T90 arm"); +} diff --git a/packages/compiler/src/ir/codec.test.ts b/packages/compiler/src/ir/codec.test.ts new file mode 100644 index 000000000..538bd85de --- /dev/null +++ b/packages/compiler/src/ir/codec.test.ts @@ -0,0 +1,235 @@ +import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { expect, test } from "vitest"; +import { compile, deserializeModule } from "../index.js"; +import { + decodeFunctionBinary, + decodeModuleBinary, + encodeFunctionBinary, + encodeFunctionInOrder, + encodeFunctionStrings, + encodeModuleBinary, + functionDigest, +} from "./codec.js"; +import { IR_CODEC_LAYOUT } from "./codec.generated.js"; +import { F64, STRING, VOID, type IrFunction, type IrModule } from "./ir.js"; +import { deserializeModuleBytes } from "./serialize.js"; + +// Programs with many node kinds: classes, closures, async, generators, +// dynamic values, streams, records, regexes and lone surrogates. +const PROGRAMS = [ + "1003-json-parse-unions.ts", + "114-live-record-delete.ts", + "1200-regex-test-basics.ts", + "1631-inspect-arrays.ts", + "4900-console-instances.ts", + "4991-lone-surrogate-halves.ts", + "5612-readable-from-iterables.cjs", + "7110-als-completion-callbacks.ts", + "7321-speculative-loop-bail-sites.ts", +]; + +/** Whether `a` and `b` hold the same data: the same keys with defined + * values, in any order, and numbers equal by Object.is. */ +function sameData(a: unknown, b: unknown): boolean { + if (typeof a === "number" && typeof b === "number") return Object.is(a, b); + if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return a === b; + if (Array.isArray(a) !== Array.isArray(b)) return false; + const record = (value: object) => value as Record; + const keys = (value: object) => + Object.keys(value) + .filter((key) => record(value)[key] !== undefined) + .sort(); + const ka = keys(a); + const kb = keys(b); + return ( + ka.length === kb.length && + ka.every((key, index) => key === kb[index] && sameData(record(a)[key], record(b)[key])) + ); +} + +test.each(PROGRAMS)("%s round-trips through the binary encoding", async (program) => { + const dir = await mkdtemp(join(tmpdir(), "scriptc-codec-")); + try { + const outPath = join(dir, "main.ir.json"); + const entry = join(import.meta.dirname, "../../../../tests/corpus", program); + 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")); + const mod = deserializeModule(await readFile(outPath, "utf8")); + const bytes = encodeModuleBinary(mod); + const back = decodeModuleBinary(bytes); + expect(sameData(mod, back)).toBe(true); + expect(Buffer.compare(encodeModuleBinary(back), bytes)).toBe(0); + // The JSON reader takes the binary encoding too. + expect(sameData(deserializeModuleBytes(bytes), mod)).toBe(true); + } finally { + await rm(dir, { recursive: true, force: true }); + } +}); + +const loc = { file: "/src/main.ts", start: 0, end: 1 }; +const fn = (body: IrFunction["body"]): IrFunction => ({ + name: "%main", + params: [], + locals: [], + body, + returnType: VOID, + loc, +}); + +test("numbers keep NaN, the infinities, -0 and integers outside int32", () => { + const values = [ + NaN, + Infinity, + -Infinity, + -0, + 0, + 1, + -1, + 2 ** 31, + -(2 ** 31) - 1, + 2 ** 53, + 0.1, + -1.5e300, + ]; + const source = fn( + values.map((value) => ({ + kind: "exprStmt", + expr: { kind: "numLit", value, type: F64, loc }, + loc, + })), + ); + const back = decodeFunctionBinary(encodeFunctionBinary(source)); + const read = back.body.map((s) => + s.kind === "exprStmt" && s.expr.kind === "numLit" ? s.expr.value : null, + ); + expect(read.length).toBe(values.length); + read.forEach((value, index) => expect(Object.is(value, values[index])).toBe(true)); +}); + +test("strings keep lone surrogates and repeat through the string table", () => { + const texts = ["plain", "\ud83d", "a\udc00b", "😀", "plain", "é"]; + const source = fn( + texts.map((value) => ({ + kind: "exprStmt", + expr: { kind: "strLit", value, type: STRING, loc }, + loc, + })), + ); + const back = decodeFunctionBinary(encodeFunctionBinary(source)); + expect( + back.body.map((s) => (s.kind === "exprStmt" && s.expr.kind === "strLit" ? s.expr.value : null)), + ).toEqual(texts); +}); + +test("absent optional fields stay absent", () => { + const source: IrModule = { + irVersion: 17, + sourceFile: "/src/main.ts", + functions: [fn([])], + entry: "%main", + }; + const back = decodeModuleBinary(encodeModuleBinary(source)); + expect(Object.keys(back).sort()).toEqual(["entry", "functions", "irVersion", "sourceFile"]); + expect(Object.keys(back.functions[0]!).sort()).toEqual(Object.keys(source.functions[0]!).sort()); +}); + +test("a function whose objects follow the declared field order reads back with its key order", () => { + // As lowering builds functions: the source name after the name, and the + // async flag set after the literal. + const source: IrFunction = { + name: "%f", + sourceName: "f", + ownsPrototype: true, + params: [], + returnType: VOID, + locals: [{ id: "x.0", name: "x", type: F64, mutable: true }], + body: [{ kind: "return", value: null, loc }], + loc, + }; + source.async = true; + const bytes = encodeFunctionInOrder(source); + expect(bytes).not.toBeNull(); + const back = decodeFunctionBinary(bytes!); + expect(JSON.stringify(back)).toBe(JSON.stringify(source)); + expect(Object.keys(back)).toEqual(Object.keys(source)); +}); + +test("a function with an object out of the declared field order has no in-order encoding", () => { + const source = fn([{ kind: "return", value: null, loc }]); + // `loc` first: spreading the rest keeps its place. + const { loc: first, ...rest } = source; + const shuffled = { loc: first, ...rest } as IrFunction; + expect(encodeFunctionInOrder(shuffled)).toBeNull(); + // An object with a key its type does not declare. + expect(encodeFunctionInOrder({ ...source, extra: 1 } as IrFunction)).toBeNull(); + // The helper above builds `locals` before `returnType`. + expect(encodeFunctionInOrder(source)).toBeNull(); + const ordered: IrFunction = { + name: source.name, + params: [], + returnType: VOID, + locals: [], + body: source.body, + loc, + }; + expect(encodeFunctionInOrder(ordered)).not.toBeNull(); +}); + +test("a node whose kind its union does not declare is refused", () => { + const source = fn([{ kind: "noSuchStatement", loc } as never]); + expect(encodeFunctionInOrder(source)).toBeNull(); + expect(() => encodeFunctionBinary(source)).toThrow(/cannot encode a IrStmt kind noSuchStatement/); +}); + +test("an encoding lists its strings and the files its fields named file hold", () => { + const other = { file: "/src/other.ts", start: 2, end: 3 }; + const source: IrFunction = { + name: "%f", + params: [], + returnType: VOID, + locals: [], + body: [ + { + kind: "exprStmt", + expr: { kind: "strLit", value: "%named", type: STRING, loc: other }, + loc, + }, + ], + loc, + }; + const encoded = encodeFunctionStrings(source)!; + expect(encoded.strings).toEqual(expect.arrayContaining(["%f", "%named", "/src/main.ts"])); + expect([...encoded.files].sort()).toEqual(["/src/main.ts", "/src/other.ts"]); + expect(Buffer.compare(encoded.bytes, encodeFunctionInOrder(source)!)).toBe(0); +}); + +test("a function's digest follows its content and not its key order", () => { + const source = fn([{ kind: "return", value: null, loc }]); + const digest = functionDigest(source); + expect(digest).toMatch(/^\d+:[0-9a-f]{16}$/); + expect(functionDigest(fn([{ kind: "return", value: null, loc }]))).toBe(digest); + // The same fields in another order. + const { loc: first, ...rest } = source; + expect(functionDigest({ loc: first, ...rest } as IrFunction)).toBe(digest); + // A change in place. + source.locals.push({ id: "x.0", name: "x", type: F64, mutable: true }); + expect(functionDigest(source)).not.toBe(digest); + // A node the encoder refuses is covered by the function's JSON text. + const refused = fn([{ kind: "noSuchStatement", loc } as never]); + const text = functionDigest(refused); + expect(text).toMatch(/^json:/); + (refused.body[0] as unknown as { extra: number }).extra = 1; + expect(functionDigest(refused)).not.toBe(text); +}); + +test("bytes another layout wrote are refused", () => { + const bytes = Buffer.from(encodeFunctionBinary(fn([]))); + // The layout digest follows the magic as the first new string. + const at = bytes.indexOf(Buffer.from(IR_CODEC_LAYOUT)); + bytes[at] = bytes[at] === 0x30 ? 0x31 : 0x30; + expect(() => decodeFunctionBinary(bytes)).toThrow(/layout/); + expect(() => decodeFunctionBinary(Buffer.from("{}"))).toThrow(/not an IR encoding/); +}); diff --git a/packages/compiler/src/ir/codec.ts b/packages/compiler/src/ir/codec.ts new file mode 100644 index 000000000..301533903 --- /dev/null +++ b/packages/compiler/src/ir/codec.ts @@ -0,0 +1,376 @@ +/* The IR's binary encoding: the fields of every node and record, by name in + * the order ir.ts declares them (codec.generated.ts), written without field + * names or JSON structure. + * + * - A length, a union's member or arm index and a string reference are + * unsigned varints (7 bits per byte, low bits first). + * - A number is one byte, 0 for an integer in the int32 range other than + * -0, followed by its zigzag varint, or 1 followed by its IEEE 754 bytes + * (little-endian), which keep NaN, the infinities and -0. + * - A string is a varint: 0 for a new string, which also gives it the next + * index in the string table, otherwise one more than the index of a + * string the encoding already has. A new string's bytes follow, after a + * varint of twice their count: plus one when they are UTF-16LE code units + * (a string with a lone surrogate, which UTF-8 cannot spell), else UTF-8. + * - A boolean is one byte. + * - A field that can be absent or null starts with a byte: 0 absent, then 1 + * for null and 2 for a value when both are possible, otherwise 1 for a + * value. + * + * An encoding starts with MAGIC and IR_CODEC_LAYOUT, a digest of the + * generated coders, so a reader refuses bytes that other coders wrote. Decoded objects carry their fields in + * declaration order, with absent optional fields left out; an object read + * back has the key order of the object written when IrWriter.inOrder held. */ +import type { IrFunction, IrModule } from "./ir.js"; +import { + IR_CODEC_LAYOUT, + decodeIrFunction, + decodeIrModule, + encodeIrFunction, + encodeIrModule, +} from "./codec.generated.js"; + +/** The first bytes of an encoding: "SCIR". */ +const MAGIC = [0x53, 0x43, 0x49, 0x52]; + +/** Whether `value` has a surrogate code unit without its pair. */ +function loneSurrogate(value: string): boolean { + for (let index = 0; index < value.length; index++) { + const unit = value.charCodeAt(index); + if (unit < 0xd800 || unit > 0xdfff) continue; + const next = index + 1 < value.length ? value.charCodeAt(index + 1) : 0; + if (unit > 0xdbff || next < 0xdc00 || next > 0xdfff) return true; + index++; + } + return false; +} + +export class IrWriter { + private bytes = Buffer.allocUnsafe(1 << 16); + private size = 0; + private readonly strings = new Map(); + /** The values of the string fields named `file` written so far. */ + readonly files = new Set(); + private readonly scratch = Buffer.allocUnsafe(8); + /** Whether every object written so far reads back with its own key order + * (`keys`). False once one held a key its type does not declare, or its + * keys in another order than the declaration's. */ + inOrder = true; + + /** Notes an object's own keys against its type's declared fields. The + * decoder builds an object with its fields in declaration order, so an + * object whose keys come in another order, or include one the type does + * not declare, does not read back as it was. */ + keys(own: readonly string[], declared: readonly string[]): void { + if (!this.inOrder) return; + let at = 0; + for (const key of own) { + while (at < declared.length && declared[at] !== key) at++; + if (at === declared.length) { + this.inOrder = false; + return; + } + at++; + } + } + + private reserve(count: number): void { + if (this.size + count <= this.bytes.length) return; + let capacity = this.bytes.length * 2; + while (capacity < this.size + count) capacity *= 2; + const grown = Buffer.allocUnsafe(capacity); + this.bytes.copy(grown, 0, 0, this.size); + this.bytes = grown; + } + + byte(value: number): void { + this.reserve(1); + this.bytes[this.size++] = value; + } + + /** A non-negative integer below 2^53. */ + length(value: number): void { + this.reserve(8); + while (value >= 0x80) { + this.bytes[this.size++] = (value % 0x80) + 0x80; + value = Math.floor(value / 0x80); + } + this.bytes[this.size++] = value; + } + + bool(value: boolean): void { + this.byte(value ? 1 : 0); + } + + num(value: number): void { + if ( + Number.isInteger(value) && + value >= -0x80000000 && + value <= 0x7fffffff && + !Object.is(value, -0) + ) { + this.byte(0); + this.length(value < 0 ? -value * 2 - 1 : value * 2); + return; + } + this.byte(1); + this.scratch.writeDoubleLE(value, 0); + this.reserve(8); + this.scratch.copy(this.bytes, this.size, 0, 8); + this.size += 8; + } + + str(value: string): void { + const known = this.strings.get(value); + if (known !== undefined) { + this.length(known + 1); + return; + } + this.strings.set(value, this.strings.size); + this.length(0); + const utf8 = Buffer.byteLength(value, "utf8"); + // UTF-8 cannot spell a lone surrogate, so a string with one is written + // as its UTF-16 code units. + const units = utf8 !== value.length && loneSurrogate(value); + const count = units ? value.length * 2 : utf8; + this.length(count * 2 + (units ? 1 : 0)); + this.reserve(count); + if (units) this.bytes.write(value, this.size, count, "utf16le"); + else this.bytes.write(value, this.size, count, "utf8"); + this.size += count; + } + + /** What the encoding could not write, such as a node whose kind its + * union does not declare, or null. Such an encoding does not decode. */ + refused: string | null = null; + + /** Notes that `what` could not be written (`refused`). */ + refuse(what: string): void { + this.inOrder = false; + this.refused ??= what; + } + + /** A string field named `file`: a source file the encoding names. */ + file(value: string): void { + this.files.add(value); + this.str(value); + } + + /** Raw bytes, after their count. */ + blob(bytes: Uint8Array): void { + this.length(bytes.length); + this.reserve(bytes.length); + Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).copy(this.bytes, this.size); + this.size += bytes.length; + } + + /** The strings written so far, each once, in the order first written. */ + written(): IterableIterator { + return this.strings.keys(); + } + + /** Starts a new encoding in the same buffer. */ + reset(): void { + this.size = 0; + this.strings.clear(); + this.files.clear(); + this.inOrder = true; + this.refused = null; + } + + /** The bytes written so far. Throws when the encoding refused a value. */ + finish(): Buffer { + if (this.refused !== null) throw new Error(`IR codec: cannot encode a ${this.refused}`); + return this.bytes.subarray(0, this.size); + } +} + +export class IrReader { + private offset = 0; + private readonly strings: string[] = []; + /** The last presence byte `tag` read. */ + lastTag = 0; + + constructor(private readonly bytes: Buffer) {} + + private fail(what: string): never { + throw new Error(`IR codec: ${what} at byte ${this.offset}`); + } + + byte(): number { + if (this.offset >= this.bytes.length) this.fail("unexpected end"); + return this.bytes[this.offset++]!; + } + + length(): number { + let value = 0; + let scale = 1; + for (;;) { + const byte = this.byte(); + value += (byte % 0x80) * scale; + if (byte < 0x80) return value; + scale *= 0x80; + if (scale > 2 ** 56) this.fail("varint too long"); + } + } + + bool(): boolean { + return this.byte() !== 0; + } + + num(): number { + const tag = this.byte(); + if (tag === 0) { + const zigzag = this.length(); + return zigzag % 2 === 0 ? zigzag / 2 : -(zigzag + 1) / 2; + } + if (tag !== 1) this.fail("invalid number tag"); + if (this.offset + 8 > this.bytes.length) this.fail("unexpected end"); + const value = this.bytes.readDoubleLE(this.offset); + this.offset += 8; + return value; + } + + str(): string { + const reference = this.length(); + if (reference > 0) { + const known = this.strings[reference - 1]; + if (known === undefined) this.fail("invalid string reference"); + return known; + } + const spelled = this.length(); + const count = Math.floor(spelled / 2); + if (this.offset + count > this.bytes.length) this.fail("unexpected end"); + const value = + spelled % 2 === 0 + ? this.bytes.toString("utf8", this.offset, this.offset + count) + : this.bytes.toString("utf16le", this.offset, this.offset + count); + this.offset += count; + this.strings.push(value); + return value; + } + + /** Raw bytes blob wrote, as a view of the bytes read. */ + blob(): Buffer { + const count = this.length(); + if (this.offset + count > this.bytes.length) this.fail("unexpected end"); + const out = this.bytes.subarray(this.offset, this.offset + count); + this.offset += count; + return out; + } + + /** A presence byte below `limit`, kept in lastTag. */ + tag(limit: number, where: string): number { + const tag = this.byte(); + if (tag >= limit) this.fail(`invalid presence byte for ${where}`); + this.lastTag = tag; + return tag; + } + + /** Whether every byte was read. */ + done(): boolean { + return this.offset === this.bytes.length; + } +} + +function header(w: IrWriter): void { + for (const byte of MAGIC) w.byte(byte); + w.str(IR_CODEC_LAYOUT); +} + +function readHeader(r: IrReader): void { + for (const byte of MAGIC) if (r.byte() !== byte) throw new Error("IR codec: not an IR encoding"); + const layout = r.str(); + if (layout !== IR_CODEC_LAYOUT) + throw new Error( + `IR codec: the encoding has layout ${layout}, this compiler reads ${IR_CODEC_LAYOUT}`, + ); +} + +/** Whether `bytes` start like an encoding this module writes. */ +export function isIrEncoding(bytes: Uint8Array): boolean { + return MAGIC.every((byte, index) => bytes[index] === byte); +} + +export function encodeModuleBinary(mod: IrModule): Buffer { + const w = new IrWriter(); + header(w); + encodeIrModule(w, mod); + return w.finish(); +} + +export function decodeModuleBinary(bytes: Buffer): IrModule { + const r = new IrReader(bytes); + readHeader(r); + const mod = decodeIrModule(r); + if (!r.done()) throw new Error("IR codec: bytes after the module"); + return mod; +} + +export function encodeFunctionBinary(fn: IrFunction): Buffer { + const w = new IrWriter(); + header(w); + encodeIrFunction(w, fn); + return w.finish(); +} + +/** The writer encodeFunctionInOrder reuses: its result is a copy. */ +const inOrderWriter = new IrWriter(); + +/** `fn`'s encoding when it reads back as `fn` is, every object with its + * own key order (IrWriter.inOrder), or null. */ +export function encodeFunctionInOrder(fn: IrFunction): Buffer | null { + return encodeFunctionStrings(fn)?.bytes ?? null; +} + +/** encodeFunctionInOrder's encoding of `fn`, with the strings it holds, + * each once, and the files its fields named `file` hold (IrWriter.file), or + * null. */ +export function encodeFunctionStrings(fn: IrFunction): { + readonly bytes: Buffer; + readonly strings: readonly string[]; + readonly files: readonly string[]; +} | null { + const w = inOrderWriter; + w.reset(); + header(w); + encodeIrFunction(w, fn); + // An encoding that refused a value is not in order either. + return w.inOrder + ? { bytes: Buffer.from(w.finish()), strings: [...w.written()], files: [...w.files] } + : null; +} + +/** A 64-bit digest of `fn`'s content, the same for functions whose fields + * hold the same values in any key order, with the byte count of the + * content it covers. It covers the binary encoding, or the JSON text when + * the encoder refuses a value (IrWriter.refuse). */ +export function functionDigest(fn: IrFunction): string { + const w = inOrderWriter; + w.reset(); + encodeIrFunction(w, fn); + if (w.refused === null) return bytesDigest(w.finish()); + return `json:${bytesDigest(Buffer.from(JSON.stringify(fn), "utf8"))}`; +} + +/** Two 32-bit multiply-xor lanes over `bytes`, FNV-1a's and one with + * another offset and MurmurHash2's multiplier, and the byte count. */ +function bytesDigest(bytes: Uint8Array): string { + let a = 0x811c9dc5; + let b = 0x050c5d1f; + for (let i = 0; i < bytes.length; i++) { + const byte = bytes[i]!; + a = Math.imul(a ^ byte, 0x01000193); + b = Math.imul(b ^ byte, 0x5bd1e995); + } + const hex = (lane: number): string => (lane >>> 0).toString(16).padStart(8, "0"); + return `${bytes.length}:${hex(a)}${hex(b)}`; +} + +export function decodeFunctionBinary(bytes: Buffer): IrFunction { + const r = new IrReader(bytes); + readHeader(r); + const fn = decodeIrFunction(r); + if (!r.done()) throw new Error("IR codec: bytes after the function"); + return fn; +} diff --git a/packages/compiler/src/ir/fields.generated.ts b/packages/compiler/src/ir/fields.generated.ts index ae45b987b..124d4a483 100644 --- a/packages/compiler/src/ir/fields.generated.ts +++ b/packages/compiler/src/ir/fields.generated.ts @@ -802,14 +802,14 @@ export function walkExprFields(node: IrExpr, walker: IrFieldWalker, skipType: bo case "classRef": { walker.string(node.kind); walker.string(node.className); + if (!skipType) { + walkTypeFields(node.type, walker, skipType); + } if (node.captures !== undefined && node.captures !== null) { for (const item6 of node.captures) { walker.string(item6); } } - if (!skipType) { - walkTypeFields(node.type, walker, skipType); - } return; } case "closure": { @@ -890,12 +890,12 @@ export function walkExprFields(node: IrExpr, walker: IrFieldWalker, skipType: bo case "dynFrom": { walker.string(node.kind); walkExprFields(node.value, walker, skipType); - if (node.fnName !== undefined && node.fnName !== null) { - walker.string(node.fnName); - } if (!skipType) { walkTypeFields(node.type, walker, skipType); } + if (node.fnName !== undefined && node.fnName !== null) { + walker.string(node.fnName); + } return; } case "dynHasKey": { @@ -1635,14 +1635,14 @@ export function walkStmtFields(node: IrStmt, walker: IrFieldWalker, skipType: bo } case "block": { walker.string(node.kind); - for (const item30 of node.body) { - walkStmtFields(item30, walker, skipType); - } if (node.labels !== undefined && node.labels !== null) { - for (const item31 of node.labels) { - walker.string(item31); + for (const item30 of node.labels) { + walker.string(item30); } } + for (const item31 of node.body) { + walkStmtFields(item31, walker, skipType); + } return; } case "break": { @@ -2111,7 +2111,7 @@ export type IrFieldsCurrent = [ Unread["negated"]>>, Unread, "kind" | "value" | "type" | "loc">>, Unread, "kind" | "id" | "type" | "loc">>, - Unread, "kind" | "className" | "captures" | "type" | "loc">>, + Unread, "kind" | "className" | "type" | "loc" | "captures">>, Unread, "kind" | "fnName" | "captures" | "adapts" | "type" | "loc">>, Unread["adapts"]>>, Unread, "kind" | "value" | "type" | "loc">>, @@ -2125,9 +2125,9 @@ export type IrFieldsCurrent = [ Unread["exact"]>>, Unread["origin"]>>, Unread, "kind" | "value" | "spelling" | "firstProp" | "type" | "loc">>, - Unread, "kind" | "value" | "fnName" | "fnLength" | "liveRef" | "type" | "loc">>, - Unread["fnLength"]>>, + Unread, "kind" | "value" | "type" | "loc" | "liveRef" | "fnName" | "fnLength">>, Unread["liveRef"]>>, + Unread["fnLength"]>>, Unread, "kind" | "key" | "negated" | "value" | "type" | "loc">>, Unread["negated"]>>, Unread, "kind" | "recv" | "method" | "calleeName" | "calleeNameValue" | "args" | "type" | "loc">>, @@ -2240,7 +2240,7 @@ export type IrFieldsCurrent = [ Unread, "kind" | "arr" | "index" | "loc">>, Unread, "kind" | "localId" | "value" | "initializes" | "loc">>, Unread["initializes"]>>, - Unread, "kind" | "body" | "labels" | "loc">>, + Unread, "kind" | "labels" | "body" | "loc">>, Unread, "kind" | "label" | "loc">>, Unread, "kind" | "arr" | "index" | "value" | "loc">>, Unread, "kind" | "label" | "loc">>, @@ -2264,9 +2264,9 @@ export type IrFieldsCurrent = [ Unread["cases"]>, readonly unknown[]>[number]>, object>, "test" | "body">>, Unread, "kind" | "disc" | "cases" | "labels" | "loc">>, Unread, "kind" | "value" | "loc">>, - Unread, "kind" | "tryBody" | "catchBody" | "catchLocalId" | "finallyBody" | "suppressFinallyErrors" | "dropFinallyErrors" | "loc">>, - Unread["suppressFinallyErrors"]>>, + Unread, "kind" | "tryBody" | "catchBody" | "catchLocalId" | "dropFinallyErrors" | "finallyBody" | "suppressFinallyErrors" | "loc">>, Unread["dropFinallyErrors"]>>, + Unread["suppressFinallyErrors"]>>, Unread, "kind" | "localId" | "init" | "loc">>, Unread, "kind" | "cond" | "body" | "labels" | "loc">>, Unread, "kind" | "elem" | "optionalElements">>, diff --git a/packages/compiler/src/ir/ir.ts b/packages/compiler/src/ir/ir.ts index 63814a60c..c58d287fa 100644 --- a/packages/compiler/src/ir/ir.ts +++ b/packages/compiler/src/ir/ir.ts @@ -1964,14 +1964,10 @@ export interface IrFunction { /** Original TS name (mangling is a backend concern). Lifted lambdas get * synthetic '%'-prefixed names ('%' can't appear in a TS identifier). */ name: string; - /** Generated reflection dispatch contains speculative source-member edges. - * Target-only host operations behind those edges retain runtime refusals. */ - speculativeDispatch?: boolean; - /** A read helper the backend can answer directly for dyn arrays and live - * array capsules, calling the helper only when that read has no answer. - * `index`: a computed read `(value, key)` whose callers pass a number - * converted to a string as the key. */ - fastRead?: "index"; + /** Source function name for native error stack frames. */ + sourceName?: string; + /** Ordinary authored function declarations/expressions own a prototype. */ + ownsPrototype?: true; params: IrParam[]; returnType: IrType; /** All locals including params, pre-collected and scope-flat: ids are @@ -1986,22 +1982,29 @@ export interface IrFunction { * their first (`this`) parameter. The slot indexes the class object's * capture array; no closure parameter is added to the method ABI. */ classCaptures?: (IrParam & { slot: number })[]; + body: IrStmt[]; + loc: SrcLoc; + /** The function's execution tier (ir/tiers.ts). Absent means static: the + * function may use dynamic values only through boundary operations + * (dynFrom, dynCheck, kind tests) and moves, which the validator enforces + * (invariant I1). "dynamic" admits the dynamic operation vocabulary + * (dynKeyGet, dynCall, dyn.* builtins, ...). Lowering assigns the field + * from the body (assignLegacyTiers); stage S2 of the two-tier design + * keeps it out of serialized IR, so loaders derive it again. */ + tier?: "dynamic"; + /** Generated reflection dispatch contains speculative source-member edges. + * Target-only host operations behind those edges retain runtime refusals. */ + speculativeDispatch?: boolean; + /** A read helper the backend can answer directly for dyn arrays and live + * array capsules, calling the helper only when that read has no answer. + * `index`: a computed read `(value, key)` whose callers pass a number + * converted to a string as the key. */ + fastRead?: "index"; /** Async: the body runs on a fiber; `returnType` is the INNER type T (a * `return v` fulfills with v) while ordinary call sites receive * Promise. With `generator` present, call sites receive the lazy * async-generator object instead. */ async?: true; - /** Async module initializers only: a module-global Promise slot where - * the spawn wrapper caches its first evaluation promise. Every later - * static/dynamic import receives that same promise, including while the - * first evaluation is suspended. */ - asyncCacheGlobal?: string; - /** Async cyclic module initializers only: the SCC's shared Promise - * slot. Eager recursive spawning writes member promises from the inside - * out, so the member that actually initiated evaluation writes last and - * becomes the runtime cycle root. Dynamic imports wait on this shared - * completion verdict instead of a build-time-selected member. */ - asyncCycleCacheGlobal?: string; /** Generator (`function*` / `async function*`): the body runs on a fiber created SUSPENDED * (nothing runs until the first `.next()`; backends may instead compile a * synchronous body as a stackless coroutine with the same protocol); `returnType` is the @@ -2030,20 +2033,17 @@ export interface IrFunction { * that generator's tag. */ inheritsTag?: true; }; - /** Source function name for native error stack frames. */ - sourceName?: string; - /** Ordinary authored function declarations/expressions own a prototype. */ - ownsPrototype?: true; - /** The function's execution tier (ir/tiers.ts). Absent means static: the - * function may use dynamic values only through boundary operations - * (dynFrom, dynCheck, kind tests) and moves, which the validator enforces - * (invariant I1). "dynamic" admits the dynamic operation vocabulary - * (dynKeyGet, dynCall, dyn.* builtins, ...). Lowering assigns the field - * from the body (assignLegacyTiers); stage S2 of the two-tier design - * keeps it out of serialized IR, so loaders derive it again. */ - tier?: "dynamic"; - body: IrStmt[]; - loc: SrcLoc; + /** Async module initializers only: a module-global Promise slot where + * the spawn wrapper caches its first evaluation promise. Every later + * static/dynamic import receives that same promise, including while the + * first evaluation is suspended. */ + asyncCacheGlobal?: string; + /** Async cyclic module initializers only: the SCC's shared Promise + * slot. Eager recursive spawning writes member promises from the inside + * out, so the member that actually initiated evaluation writes last and + * becomes the runtime cycle root. Dynamic imports wait on this shared + * completion verdict instead of a build-time-selected member. */ + asyncCycleCacheGlobal?: string; } export interface IrParam { @@ -2280,7 +2280,7 @@ export type IrStmt = /** A bare lexical block `{ ... }` — its own scope. `labels` makes it a * labeled-break target (`lbl: { ... break lbl; ... }` and the wrapper * the frontend puts around labeled non-loop statements). */ - | { kind: "block"; body: IrStmt[]; labels?: string[]; loc: SrcLoc } + | { kind: "block"; labels?: string[]; body: IrStmt[]; loc: SrcLoc } /** `throw v`. Any non-void value type can be thrown; ownership of a * refcounted value MOVES into the runtime's exception cell. Terminates * the path like `return`: control unwinds to the innermost enclosing @@ -2324,15 +2324,15 @@ export type IrStmt = tryBody: IrStmt[]; catchBody: IrStmt[] | null; catchLocalId: string | null; - finallyBody: IrStmt[] | null; - /** Resource-management cleanup: if the guarded body and cleanup both - * throw, raise a SuppressedError instead of replacing the first error. */ - suppressFinallyErrors?: true; /** Iterator closing: if the guarded body threw and the cleanup throws * too, the cleanup's error is dropped and the first one propagates. * After a generator .return() the cleanup's error replaces it, and on * normal exits it propagates. */ dropFinallyErrors?: true; + finallyBody: IrStmt[] | null; + /** Resource-management cleanup: if the guarded body and cleanup both + * throw, raise a SuppressedError instead of replacing the first error. */ + suppressFinallyErrors?: true; loc: SrcLoc; }; @@ -3341,7 +3341,7 @@ export type IrExpr = * every classRef, so a value's construct thunk always has a constructor * to call; backends emit class objects (and thunks) for exactly the * classes some classRef in the module names. */ - | { kind: "classRef"; className: string; captures?: string[]; type: IrType; loc: SrcLoc } + | { kind: "classRef"; className: string; type: IrType; loc: SrcLoc; captures?: string[] } /** `new X(args)` through a class VALUE: call the class object's * construct thunk. `callee` is classval-typed; args are completed * against `%.constructor`'s ABI — sound because every @@ -3513,13 +3513,13 @@ export type IrExpr = | { kind: "dynFrom"; value: IrExpr; + type: IrType; + loc: SrcLoc; + liveRef?: true; fnName?: string; /** A boxed function's `length` when it differs from its parameter * count (builtin function values). */ fnLength?: number; - liveRef?: true; - type: IrType; - loc: SrcLoc; } /** CALLING a dyn value — `fn(a, b)` where fn is checked-dynamic (an * implicit-any JS binding, a dyn record member, a dynKeyGet result). diff --git a/packages/compiler/src/ir/serialize.ts b/packages/compiler/src/ir/serialize.ts index 185472b89..d8ff2e273 100644 --- a/packages/compiler/src/ir/serialize.ts +++ b/packages/compiler/src/ir/serialize.ts @@ -7,6 +7,7 @@ * text parts and decoded from bytes one function at a time; neither direction * materializes the complete document as a single string. */ import type { IrExpr, IrFunction, IrModule, IrStmt } from "./ir.js"; +import { decodeModuleBinary, isIrEncoding } from "./codec.js"; // Version 17 adds the dynamic tier's jsval type and js.* builtins (two-tier // design stage S3) and serializes each function's execution tier. @@ -268,10 +269,12 @@ function functionSpans(bytes: Buffer): FunctionSpans | null { return null; } -/** Decode a UTF-8 IR artifact (compact or readable) without materializing - * it as one string: the header and each function are parsed separately. +/** Decode an IR artifact: the binary encoding (codec.ts), or UTF-8 JSON + * (compact or readable) without materializing it as one string: the header + * and each function are parsed separately. * Split points are ASCII structural bytes, so every range is valid UTF-8. */ export function deserializeModuleBytes(bytes: Buffer): IrModule { + if (isIrEncoding(bytes)) return decodeModuleBinary(bytes); const spans = functionSpans(bytes); if (spans === null) return deserializeModule(bytes.toString("utf8")); const mod = checkedVersion( diff --git a/packages/compiler/src/native/cache.ts b/packages/compiler/src/native/cache.ts index 551a151a1..d18108afb 100644 --- a/packages/compiler/src/native/cache.ts +++ b/packages/compiler/src/native/cache.ts @@ -174,6 +174,8 @@ export class NativeCache { "binary", "dsym", "coverage", + "units", + "unit-files", ]) { const directory = join(this.root, family); let names: string[]; diff --git a/packages/compiler/src/native/prepare.ts b/packages/compiler/src/native/prepare.ts index bf496aa99..753ad1281 100644 --- a/packages/compiler/src/native/prepare.ts +++ b/packages/compiler/src/native/prepare.ts @@ -32,6 +32,7 @@ import { import { compilerOwnedFileRoots } from "../frontend/dts-paths.js"; import { provenanceSources } from "../frontend/provenance-registry.js"; import { processFork } from "../frontend/process-fork.js"; +import { unitReuseEnabled, type UnitStore } from "../frontend/lowering/unit-reuse.js"; import { tokenEquivalentSource, tokenPositionEquivalentSource, @@ -196,6 +197,21 @@ function equivalentFrontendInput( }; } +/** The unit store of a build whose unit key is `key`: its program entry + * under that key, and file entries by content digest. */ +function nativeUnitStore(cache: NativeCache, key: string): UnitStore { + return { + readProgram: () => cache.read("units", key), + writeProgram: (bytes) => cache.write("units", key, bytes), + readFile: (name) => (/^[0-9a-f]{64}$/.test(name) ? cache.read("unit-files", name) : null), + writeFile: (bytes) => { + const digest = contentDigest(bytes); + if (!cache.has("unit-files", digest)) cache.write("unit-files", digest, bytes, digest); + return digest; + }, + }; +} + function implementationIdentity(toolchain: NativeToolchain): string { // Release versions alone cannot distinguish two development builds. return contentDigest( @@ -221,6 +237,9 @@ const FRONTEND_INDEPENDENT_VARIABLES = new Set([ "SCRIPTC_CHECKER_SERVER", "SCRIPTC_EMIT_WORKERS", "SCRIPTC_EMIT_WORKER_FUNCTIONS", + // Reusing lowered units leaves the lowered module as it is. + "SCRIPTC_INCR_LOWERING", + "SCRIPTC_INCR_VERIFY", "SCRIPTC_TIMING", ]); @@ -372,22 +391,28 @@ export function prepareNativeExecutable( const exclusions = { outputPaths, outputDirectories: [...directories] }; const irInput = process.env[BOOTSTRAP_IR_INPUT] ?? ""; let key: string | null = null; + // The unit store's key (unit-reuse.ts): the frontend's inputs without the + // output paths, which lowering does not read. + let unitKey: string | null = null; // The entry this build replaces: its probes give the digests of unchanged files. let previous: FrontendInputSnapshot | undefined; if (cache !== null && irInput === "" && provenanceSources() === null) { try { - key = contentDigest( - JSON.stringify({ - schema: 1, - entry, - options, - ffi, - implementation: implementationIdentity(toolchain), - target: toolchain.target.name, - declarations: toolchain.declarationsRoot ?? null, - environment: frontendEnvironment(), - }), - ); + const inputs = { + schema: 1, + entry, + options, + ffi, + implementation: implementationIdentity(toolchain), + target: toolchain.target.name, + declarations: toolchain.declarationsRoot ?? null, + environment: frontendEnvironment(), + }; + key = contentDigest(JSON.stringify(inputs)); + if (unitReuseEnabled()) { + const { outPath: _outPath, outDir: _outDir, ...lowering } = options; + unitKey = contentDigest(JSON.stringify({ ...inputs, options: lowering })); + } const cached = readFrontendCache(cache, key, exclusions); if (cached !== null && "input" in cached) { timing("frontend-cache-hit"); @@ -413,7 +438,17 @@ export function prepareNativeExecutable( irInput !== "" ? preparedFromIr(irInput, entry, options) : tracker.runSynchronous(() => - prepareExecutableModule(entry, options, ffi, toolchain.target.platform, frontend, timing), + prepareExecutableModule( + entry, + options, + ffi, + toolchain.target.platform, + frontend, + timing, + new Map(), + true, + cache !== null && unitKey !== null ? nativeUnitStore(cache, unitKey) : null, + ), ); if (!prepared.ok) return prepared; if (irInput !== "") timing("ir-input"); diff --git a/packages/compiler/test/unit-reuse.test.ts b/packages/compiler/test/unit-reuse.test.ts new file mode 100644 index 000000000..80393cf0f --- /dev/null +++ b/packages/compiler/test/unit-reuse.test.ts @@ -0,0 +1,315 @@ +import { mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, expect, test } from "vitest"; +import { compile } from "../src/index.js"; + +const VARIABLES = [ + "SCRIPTC_CACHE_DIR", + "SCRIPTC_INCR_LOWERING", + "SCRIPTC_INCR_VERIFY", + "SCRIPTC_LOWER_JOURNAL_DUMP", +]; + +afterEach(() => { + for (const name of VARIABLES) delete process.env[name]; +}); + +/** A program in a scratch directory, built with unit reuse into its own + * cache and without it. */ +class Workspace { + private builds = 0; + constructor(readonly dir: string) {} + + static async create(files: Record): Promise { + const workspace = new Workspace(await mkdtemp(join(tmpdir(), "scriptc-unit-reuse-"))); + for (const [name, text] of Object.entries(files)) await workspace.write(name, text); + return workspace; + } + + write(name: string, text: string): Promise { + return writeFile(join(this.dir, name), text); + } + + async edit(name: string, from: string, to: string): Promise { + const text = await readFile(join(this.dir, name), "utf8"); + expect(text, `${name} spells ${from}`).toContain(from); + await this.write(name, text.replace(from, to)); + } + + private async lower(label: string, reuse: boolean, verify = false): Promise { + for (const name of VARIABLES) delete process.env[name]; + if (reuse) { + process.env["SCRIPTC_CACHE_DIR"] = join(this.dir, "cache"); + process.env["SCRIPTC_INCR_LOWERING"] = "1"; + process.env["SCRIPTC_LOWER_JOURNAL_DUMP"] = join(this.dir, label); + if (verify) process.env["SCRIPTC_INCR_VERIFY"] = "1"; + } + const outPath = join(this.dir, `${label}.ir.json`); + const result = await compile(join(this.dir, "main.ts"), { + outDir: join(this.dir, "out"), + outPath, + outputKind: "ir", + }); + if (!result.ok) + throw new Error(result.diagnostics.map((d) => `${d.code}: ${d.message}`).join("\n")); + return readFile(outPath, "utf8"); + } + + /** The units with a record in the last build, recorded or applied. */ + units: string[] = []; + + /** Builds with reuse, checks the IR against a build without it, and + * returns the units whose stored records applied. A build in verify mode + * follows and must give the same IR. */ + async build(): Promise { + const label = `build-${this.builds++}`; + const reused = await this.lower(label, true); + expect(reused).toBe(await this.lower(`${label}-plain`, false)); + const applied: string[] = []; + this.units = []; + for (const line of (await readFile(join(this.dir, label, "records.jsonl"), "utf8")) + .trimEnd() + .split("\n")) { + // A unit without a record has a line with its reasons. + const record = JSON.parse(line) as { name: string; applied?: boolean; reasons?: string[] }; + if (record.reasons !== undefined) continue; + this.units.push(record.name); + if (record.applied === true) applied.push(record.name); + } + this.units.sort(); + expect(await this.lower(`${label}-verify`, true, true)).toBe(reused); + return applied.sort(); + } + + dispose(): Promise { + return rm(this.dir, { recursive: true, force: true }); + } +} + +const MODULES = { + "shapes.ts": [ + "export function area(width: number, height: number): number {", + " return width * height;", + "}", + "export function label(name: string): string {", + " return `shape ${name}`;", + "}", + "export class Box {", + " constructor(readonly width: number) {}", + " scaled(by: number): number {", + " return this.width * by;", + " }", + " named(): string {", + " return label(`box ${this.width}`);", + " }", + "}", + "", + ].join("\n"), + "report.ts": [ + 'import { Box, area } from "./shapes.js";', + "export function report(sizes: number[]): number {", + " let total = 0;", + " for (const size of sizes) total += area(size, size);", + " return total;", + "}", + "export function describe(box: Box): string {", + " return `${box.named()} ${box.scaled(2)}`;", + "}", + "export function twice(value: number): number {", + " return value * 2;", + "}", + "", + ].join("\n"), + "format.ts": [ + "export function pad(text: string, width: number): string {", + " return text.padStart(width);", + "}", + "", + ].join("\n"), + "main.ts": [ + 'import { Box, label } from "./shapes.js";', + 'import { describe, report, twice } from "./report.js";', + 'import { pad } from "./format.js";', + 'console.log(report([1, 2, 3]), describe(new Box(4)), label("main"), twice(3), pad("x", 4));', + "", + ].join("\n"), +}; + +/** Whether `applied` holds a unit named `name`. */ +const has = (applied: readonly string[], name: string): boolean => + applied.some((unit) => unit.endsWith(`.${name}`) || unit === name); + +test("an unchanged rebuild applies every unit's stored record", async () => { + const workspace = await Workspace.create(MODULES); + try { + // The first build into the cache records nothing; build()'s verify build + // after it finds the tables and records. + expect(await workspace.build()).toEqual([]); + const units = workspace.units; + expect(units.length).toBeGreaterThan(4); + expect(await workspace.build()).toEqual(units); + } finally { + await workspace.dispose(); + } +}, 300_000); + +test("a body edit lowers the units of its file and the units that spell its name", async () => { + const workspace = await Workspace.create(MODULES); + try { + await workspace.build(); + await workspace.edit("shapes.ts", "return width * height;", "return width * height + 1;"); + const applied = await workspace.build(); + // report() spells area(); twice() and pad() spell nothing whose + // declaration changed. The units of shapes.ts lower again. + expect(has(applied, "report")).toBe(false); + expect(has(applied, "twice")).toBe(true); + expect(has(applied, "pad")).toBe(true); + expect(has(applied, "scaled")).toBe(false); + } finally { + await workspace.dispose(); + } +}, 300_000); + +test("a signature edit lowers the units whose text spells a name it reaches", async () => { + const workspace = await Workspace.create(MODULES); + try { + await workspace.build(); + await workspace.edit( + "shapes.ts", + " named(): string {", + ' named(prefix = ""): string {\n if (prefix !== "") return prefix;', + ); + const applied = await workspace.build(); + // The member's surface changed, and with it the type of Box: the units + // that spell named or Box lower again, the others apply. + expect(has(applied, "named")).toBe(false); + expect(has(applied, "report")).toBe(true); + expect(has(applied, "twice")).toBe(true); + } finally { + await workspace.dispose(); + } +}, 300_000); + +test("an edit above every declaration of a file keeps the other files' units", async () => { + const workspace = await Workspace.create(MODULES); + try { + await workspace.build(); + await workspace.edit( + "shapes.ts", + "export function area", + "// A comment.\nexport function area", + ); + const applied = await workspace.build(); + expect(has(applied, "report")).toBe(true); + expect(has(applied, "twice")).toBe(true); + expect(has(applied, "scaled")).toBe(false); + } finally { + await workspace.dispose(); + } +}, 300_000); + +test("an added declaration keeps the units that do not spell its name", async () => { + const workspace = await Workspace.create(MODULES); + try { + await workspace.build(); + await workspace.edit( + "report.ts", + "export function describe", + "export function unused(): number {\n return 1;\n}\nexport function describe", + ); + const applied = await workspace.build(); + expect(has(applied, "scaled")).toBe(true); + expect(has(applied, "label")).toBe(true); + expect(has(applied, "pad")).toBe(true); + expect(has(applied, "twice")).toBe(false); + } finally { + await workspace.dispose(); + } +}, 300_000); + +test("units that use a module's namespace object lower again when its exports change", async () => { + const workspace = await Workspace.create({ + "values.ts": "export let count = 1;\nexport function bump(): void {\n count += 1;\n}\n", + "main.ts": [ + 'import * as values from "./values.js";', + "function keys(): string {", + ' return Object.keys(values).join(",");', + "}", + "function current(): number {", + " return values.count;", + "}", + "values.bump();", + "console.log(keys(), current());", + "", + ].join("\n"), + }); + try { + await workspace.build(); + expect(await workspace.build()).toEqual(workspace.units); + await workspace.write( + "values.ts", + "export let count = 1;\nexport function bump(): void {\n count += 1;\n}\nexport const added = 2;\n", + ); + // Both units spell the namespace binding, whose object gained a key. + const applied = await workspace.build(); + expect(has(applied, "keys")).toBe(false); + expect(has(applied, "current")).toBe(false); + } finally { + await workspace.dispose(); + } +}, 300_000); + +test("a unit whose record holds another file's positions lowers again when that file changes", async () => { + const workspace = await Workspace.create({ + "lib.ts": + 'export function add(a: number, b: number): number {\n return a + b;\n}\nexport const label = "lib";\n', + "main.ts": [ + 'import * as lib from "./lib.js";', + "function show(): string {", + " const { add: plus, label: name } = lib;", + " return `${name} ${plus(1, 2)}`;", + "}", + "function other(): number {", + " return 42;", + "}", + "console.log(show(), other());", + "", + ].join("\n"), + }); + try { + await workspace.build(); + await workspace.edit("lib.ts", "export function add", "// A comment.\nexport function add"); + // The build compares its IR, positions included, with a clean build's. + const applied = await workspace.build(); + expect(has(applied, "other")).toBe(true); + } finally { + await workspace.dispose(); + } +}, 300_000); + +test("the first build into a cache writes the declaration tables and no unit records", async () => { + const workspace = await Workspace.create(MODULES); + try { + const cache = join(workspace.dir, "cache"); + process.env["SCRIPTC_CACHE_DIR"] = cache; + process.env["SCRIPTC_INCR_LOWERING"] = "1"; + const build = async (): Promise => { + const result = await compile(join(workspace.dir, "main.ts"), { + outDir: join(workspace.dir, "out"), + outPath: join(workspace.dir, "main.ir.json"), + outputKind: "ir", + }); + expect(result.ok).toBe(true); + }; + const entries = async (family: string): Promise => + readdir(join(cache, "units-v1", family)).catch(() => []); + await build(); + expect(await entries("program")).toHaveLength(1); + expect(await entries("files")).toEqual([]); + await build(); + expect((await entries("files")).length).toBeGreaterThan(0); + } finally { + await workspace.dispose(); + } +}, 300_000); diff --git a/scripts/generate-ir-codec.mjs b/scripts/generate-ir-codec.mjs new file mode 100644 index 000000000..4cd04f6be --- /dev/null +++ b/scripts/generate-ir-codec.mjs @@ -0,0 +1,440 @@ +import { createHash } from "node:crypto"; +import { readFileSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { join } from "node:path"; +import { fileURLToPath } from "node:url"; + +// Generates packages/compiler/src/ir/codec.generated.ts from the types in +// ir.ts: for every object type an IrModule can reach, an encoder that +// writes its fields by name in declaration order and a decoder that reads +// them back into an object literal of that type. Unions that `kind` +// discriminates are written as the member's index; other unions as the +// index of the arm a runtime test picks. The byte format is in codec.ts. +// `--check` fails when the file is stale. +const root = fileURLToPath(new URL("..", import.meta.url)); +const require = createRequire(join(root, "package.json")); +const ts = require("typescript"); +const irPath = join(root, "packages/compiler/src/ir/ir.ts"); +const target = join(root, "packages/compiler/src/ir/codec.generated.ts"); + +const program = ts.createProgram([irPath], { strict: true, target: ts.ScriptTarget.ES2022 }); +const checker = program.getTypeChecker(); +const source = program.getSourceFile(irPath); + +/** Exported type names of ir.ts, by the type they declare. */ +const exported = new Map(); +ts.forEachChild(source, (node) => { + if ( + (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) && + node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) && + node.typeParameters === undefined + ) { + const type = checker.getTypeAtLocation(node.name); + if (!exported.has(type)) exported.set(type, node.name.text); + } +}); +/** Exported names by the member set of the union or the single type they + * declare: a field of type `IrExpr | undefined` codes its IrExpr part with + * IrExpr's coder. */ +const exportedBy = new Map(); +for (const [type, name] of exported) { + const parts = type.isUnion() ? type.types : [type]; + const key = parts + .map((p) => p.id) + .sort((a, b) => a - b) + .join("|"); + if (!exportedBy.has(key)) exportedBy.set(key, name); +} +const partsKey = (parts) => + parts + .map((p) => p.id) + .sort((a, b) => a - b) + .join("|"); +const used = new Set(["IrModule", "IrFunction"]); +const moduleType = [...exported].find(([, name]) => name === "IrModule")[0]; +const functionType = [...exported].find(([, name]) => name === "IrFunction")[0]; + +const F = ts.TypeFlags; +const NULLISH = F.Null | F.Undefined | F.Void; +const STRING = F.StringLike; +const NUMBER = F.NumberLike; +const BOOLEAN = F.BooleanLike; + +/** An intersection no value has: `IrType & { kind: "record" }` distributes + * over IrType's members, and all but one give `kind` two literal types. */ +function impossible(type) { + return type.isIntersection() && checker.typeToString(type) === "never"; +} + +/** The parts of `type` that are not null or undefined, and which of the two + * it allows. */ +function split(type) { + const parts = type.isUnion() ? type.types : [type]; + return { + present: parts.filter((part) => (part.flags & (NULLISH | F.Never)) === 0 && !impossible(part)), + nullable: parts.some((part) => (part.flags & F.Null) !== 0), + undefinable: parts.some((part) => (part.flags & (F.Undefined | F.Void)) !== 0), + }; +} + +/** The literal value of `kind` on an object type, or null. */ +function kindOf(type) { + const kind = checker.getPropertyOfType(type, "kind"); + if (kind === undefined) return null; + const kindType = checker.getTypeOfSymbol(kind); + return kindType.isStringLiteral() ? kindType.value : null; +} + +/** How a value of the non-nullish `parts` is coded. */ +function shapeOf(parts, where) { + for (const part of parts) + if ((part.flags & (F.Any | F.Unknown)) !== 0) throw new Error(`untyped value at ${where}`); + const objects = parts.filter((part) => (part.flags & (STRING | NUMBER | BOOLEAN)) === 0); + const strings = parts.filter((part) => (part.flags & STRING) !== 0); + const numbers = parts.filter((part) => (part.flags & NUMBER) !== 0); + const booleans = parts.filter((part) => (part.flags & BOOLEAN) !== 0); + if (objects.length === 0) { + const scalarKinds = [strings, numbers, booleans].filter((list) => list.length > 0).length; + if (scalarKinds === 1) + return strings.length > 0 + ? { code: "string" } + : numbers.length > 0 + ? { code: "number" } + : { code: "bool" }; + } + if (objects.length === 1 && strings.length + numbers.length + booleans.length === 0) + return checker.isArrayType(objects[0]) + ? { code: "array", item: objects[0] } + : { code: "object", type: objects[0] }; + if ( + objects.length > 1 && + strings.length + numbers.length + booleans.length === 0 && + objects.every((part) => kindOf(part) !== null && !checker.isArrayType(part)) + ) + return { code: "kinds", members: objects }; + // Arms told apart at run time: typeof for scalars, Array.isArray, a + // `kind` literal, or a property only one object arm declares. + const arms = []; + if (strings.length > 0) arms.push({ test: "string", parts: strings }); + if (numbers.length > 0) arms.push({ test: "number", parts: numbers }); + if (booleans.length > 0) arms.push({ test: "boolean", parts: booleans }); + const arrays = objects.filter((part) => checker.isArrayType(part)); + if (arrays.length > 1) throw new Error(`several array types at ${where}`); + if (arrays.length === 1) arms.push({ test: "array", parts: arrays }); + const records = objects.filter((part) => !checker.isArrayType(part)); + for (const record of records) { + const own = checker.getPropertiesOfType(record).map((p) => p.name); + const others = records.filter((other) => other !== record); + const required = checker + .getPropertiesOfType(record) + .filter((p) => (p.flags & ts.SymbolFlags.Optional) === 0) + .map((p) => p.name); + let key = required.find((name) => + others.every((other) => checker.getPropertyOfType(other, name) === undefined), + ); + // A property every arm has, whose object value has a key only this + // arm's does (IrFfiParam's callback.id and callback.release). + if (key === undefined) + for (const outer of required) { + const inner = (t) => { + const p = checker.getPropertyOfType(t, outer); + return p === undefined ? undefined : split(checker.getTypeOfSymbol(p)).present; + }; + const mine = inner(record); + if (mine?.length !== 1) continue; + const found = checker + .getPropertiesOfType(mine[0]) + .filter((p) => (p.flags & ts.SymbolFlags.Optional) === 0) + .map((p) => p.name) + .find((name) => + others.every((other) => { + const theirs = inner(other); + return ( + theirs === undefined || + theirs.every((t) => checker.getPropertyOfType(t, name) === undefined) + ); + }), + ); + if (found !== undefined) { + key = `${outer}.${found}`; + break; + } + } + if (records.length > 1 && key === undefined) + throw new Error( + `no property tells ${checker.typeToString(record)} apart at ${where}: ${own.join(",")}`, + ); + arms.push({ test: records.length === 1 ? "object" : `key:${key}`, parts: [record] }); + } + return { code: "arms", arms }; +} + +// One encoder and one decoder per object type, union and array the module +// reaches, named by a number in the order they are first reached. +const coders = new Map(); +const order = []; +function coderFor(parts, typeExpr, where) { + const key = partsKey(parts); + const known = coders.get(key); + if (known !== undefined) return known; + const shape = shapeOf(parts, where); + if (shape.code === "string" || shape.code === "number" || shape.code === "bool") return shape; + const exportedName = exportedBy.get(key); + if (exportedName !== undefined) used.add(exportedName); + const name = exportedName ?? `T${order.length}`; + const coder = { ...shape, name, typeExpr: exportedName ?? typeExpr, where }; + coders.set(key, coder); + order.push(coder); + return coder; +} + +/** The type expression of `parts`, `typeExpr` naming the field or element + * type they come from. */ +function presentExpr(typeExpr) { + return `NonNullable<${typeExpr}>`; +} + +/** Lines that write `value` of `type`, named `typeExpr`. A string field + * named `file` is written with IrWriter.file, which notes it as a source + * file the encoding names. */ +function encodeValue(value, type, typeExpr, where, optional, field = null) { + const { present, nullable, undefinable } = split(type); + const coder = coderFor(present, presentExpr(typeExpr), where); + const write = + coder.code === "string" + ? `w.${field === "file" ? "file" : "str"}(${value});` + : coder.code === "number" + ? `w.num(${value});` + : coder.code === "bool" + ? `w.bool(${value});` + : `enc${coder.name}(w, ${value});`; + if (!nullable && !undefinable && !optional) return [write]; + if (nullable && (undefinable || optional)) + return [ + `if (${value} === undefined) w.byte(0);`, + `else if (${value} === null) w.byte(1);`, + `else {`, + ` w.byte(2);`, + ` ${write}`, + `}`, + ]; + const missing = nullable ? "null" : "undefined"; + return [`if (${value} === ${missing}) w.byte(0);`, `else {`, ` w.byte(1);`, ` ${write}`, `}`]; +} + +/** An expression that reads a value of `type`; null-checks are statements + * through `local`. */ +function decodeValue(type, typeExpr, where, optional) { + const { present, nullable, undefinable } = split(type); + const coder = coderFor(present, presentExpr(typeExpr), where); + // A scalar read is cast to its literal type (`17`, `true`, a union of + // string literals), which the bytes cannot spell. + const scalar = (call, plain) => + present.every((part) => part.flags === plain) ? call : `(${call} as ${presentExpr(typeExpr)})`; + const read = + coder.code === "string" + ? scalar("r.str()", F.String) + : coder.code === "number" + ? scalar("r.num()", F.Number) + : coder.code === "bool" + ? present.length === 2 + ? "r.bool()" + : `(r.bool() as ${presentExpr(typeExpr)})` + : `dec${coder.name}(r)`; + if (!nullable && !undefinable && !optional) return read; + if (nullable && (undefinable || optional)) + return `r.tag(3, "${where}") === 0 ? undefined : r.lastTag === 1 ? null : ${read}`; + return `r.tag(2, "${where}") === 0 ? ${nullable ? "null" : "undefined"} : ${read}`; +} + +/** Field reads and writes of one object type. */ +function objectLines(type, typeExpr, where, kind) { + const enc = []; + const dec = []; + const fields = []; + let index = 0; + for (const property of checker.getPropertiesOfType(type)) { + if (property.name === "kind" && kind !== null) continue; + const fieldType = checker.getTypeOfSymbol(property); + const optional = (property.flags & ts.SymbolFlags.Optional) !== 0; + const fieldExpr = `${typeExpr}[${JSON.stringify(property.name)}]`; + const at = `${where}.${property.name}`; + if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(property.name)) + throw new Error(`field name ${property.name} at ${where} is not an identifier`); + const access = `v.${property.name}`; + enc.push(...encodeValue(access, fieldType, fieldExpr, at, optional, property.name)); + const local = `f${index++}`; + dec.push(`const ${local} = ${decodeValue(fieldType, fieldExpr, at, optional)};`); + fields.push({ name: property.name, local, optional }); + } + // The object is built with its fields in declaration order, an absent + // optional field left out where it would stand: an object whose keys + // follow that order (IrWriter.keys) reads back with the same key order. + const literal = [ + ...(kind === null ? [] : [`kind: ${JSON.stringify(kind)}`]), + ...fields.map((f) => + f.optional + ? `...(${f.local} !== undefined ? { ${f.name}: ${f.local} } : {})` + : `${f.name}: ${f.local}`, + ), + ]; + dec.push(`const o: ${typeExpr} = { ${literal.join(", ")} };`); + dec.push("return o;"); + const declared = `KEYS${keyLists.length}`; + keyLists.push( + `const ${declared}: readonly string[] = ${JSON.stringify([...(kind === null ? [] : ["kind"]), ...fields.map((f) => f.name)])};`, + ); + enc.unshift(`w.keys(Object.keys(v), ${declared});`); + return { enc, dec }; +} + +/** The declared field names of each object type, for IrWriter.keys. */ +const keyLists = []; + +const body = []; +function emit(coder) { + const { name, typeExpr } = coder; + if (coder.code === "object") { + const { enc, dec } = objectLines(coder.type, typeExpr, coder.where, null); + body.push( + `function enc${name}(w: IrWriter, v: ${typeExpr}): void {`, + ...enc.map((l) => ` ${l}`), + "}", + `function dec${name}(r: IrReader): ${typeExpr} {`, + ...dec.map((l) => ` ${l}`), + "}", + "", + ); + } else if (coder.code === "array") { + const item = checker.getTypeArguments(coder.item)[0]; + const itemExpr = `${typeExpr}[number]`; + const at = `${coder.where}[]`; + const enc = encodeValue("item", item, itemExpr, at, false); + const dec = decodeValue(item, itemExpr, at, false); + body.push( + `function enc${name}(w: IrWriter, v: ${typeExpr}): void {`, + " w.length(v.length);", + " for (const item of v) {", + ...enc.map((l) => ` ${l}`), + " }", + "}", + `function dec${name}(r: IrReader): ${typeExpr} {`, + " const length = r.length();", + ` const out: ${typeExpr}[number][] = [];`, + ` for (let i = 0; i < length; i++) out.push(${dec});`, + " return out;", + "}", + "", + ); + } else if (coder.code === "kinds") { + const members = [...coder.members].sort((a, b) => (kindOf(a) < kindOf(b) ? -1 : 1)); + const enc = [`function enc${name}(w: IrWriter, v: ${typeExpr}): void {`, " switch (v.kind) {"]; + const dec = [`function dec${name}(r: IrReader): ${typeExpr} {`, ` switch (r.length()) {`]; + members.forEach((member, index) => { + const kind = kindOf(member); + const memberExpr = `Extract<${typeExpr}, { kind: ${JSON.stringify(kind)} }>`; + const lines = objectLines(member, memberExpr, `${coder.where}<${kind}>`, kind); + enc.push( + ` case ${JSON.stringify(kind)}: {`, + ` w.length(${index});`, + ...lines.enc.map((l) => ` ${l}`), + " return;", + " }", + ); + dec.push(` case ${index}: {`, ...lines.dec.map((l) => ` ${l}`), " }"); + }); + // A kind the union does not declare writes nothing that decodes. + enc.push(" }", ` w.refuse("${name} kind " + String((v as { kind: unknown }).kind));`, "}"); + dec.push(" }", ` throw new Error("IR codec: invalid ${name} kind");`, "}", ""); + body.push(...enc, ...dec); + } else if (coder.code === "arms") { + const enc = [`function enc${name}(w: IrWriter, v: ${typeExpr}): void {`]; + const dec = [`function dec${name}(r: IrReader): ${typeExpr} {`, " switch (r.length()) {"]; + coder.arms.forEach((arm, index) => { + const armExpr = + arm.test === "string" || arm.test === "number" || arm.test === "boolean" + ? `Extract<${typeExpr}, ${arm.test}>` + : arm.test === "array" + ? `Extract<${typeExpr}, readonly unknown[]>` + : arm.test === "object" + ? `Exclude<${typeExpr}, string | number | boolean | readonly unknown[]>` + : arm.test.includes(".") + ? `Extract<${typeExpr}, { ${arm.test.slice(4).split(".")[0]}: { ${arm.test.slice(4).split(".")[1]}: unknown } }>` + : `Extract<${typeExpr}, { ${arm.test.slice(4)}: unknown }>`; + const test = + arm.test === "string" || arm.test === "number" || arm.test === "boolean" + ? `typeof v === ${JSON.stringify(arm.test)}` + : arm.test === "array" + ? "Array.isArray(v)" + : arm.test === "object" + ? "true" + : arm.test.includes(".") + ? `typeof v === "object" && ${JSON.stringify(arm.test.slice(4).split(".")[0])} in v && ${JSON.stringify(arm.test.slice(4).split(".")[1])} in (v as { ${arm.test.slice(4).split(".")[0]}: object }).${arm.test.slice(4).split(".")[0]}` + : `typeof v === "object" && ${JSON.stringify(arm.test.slice(4))} in v`; + const parts = arm.parts; + const armCoder = coderFor(parts, armExpr, `${coder.where}|${index}`); + const write = + armCoder.code === "string" + ? "w.str(v);" + : armCoder.code === "number" + ? "w.num(v);" + : armCoder.code === "bool" + ? "w.bool(v);" + : `enc${armCoder.name}(w, v as ${armExpr});`; + const read = + armCoder.code === "string" + ? "r.str()" + : armCoder.code === "number" + ? "r.num()" + : armCoder.code === "bool" + ? "r.bool()" + : `dec${armCoder.name}(r)`; + enc.push(` if (${test}) {`, ` w.length(${index});`, ` ${write}`, " return;", " }"); + dec.push(` case ${index}:`, ` return ${read} as ${typeExpr};`); + }); + enc.push(` throw new Error("IR codec: no arm of ${name} matches");`, "}"); + dec.push(" }", ` throw new Error("IR codec: invalid ${name} arm");`, "}", ""); + body.push(...enc, ...dec); + } +} + +coderFor([moduleType], "IrModule", "IrModule"); +coderFor([functionType], "IrFunction", "IrFunction"); +for (let index = 0; index < order.length; index++) emit(order[index]); + +const lines = [ + "// Generated from packages/compiler/src/ir/ir.ts by scripts/generate-ir-codec.mjs.", + "// Run `pnpm generate:ir-codec` after changing the IR's types.", + `import type { ${[...used].sort().join(", ")} } from "./ir.js";`, + 'import type { IrReader, IrWriter } from "./codec.js";', + "", + "/** A digest of the coders below: an encoding written with other coders is", + " * refused instead of misread, also after a change that keeps IR_VERSION. */", + `export const IR_CODEC_LAYOUT = ${JSON.stringify(createHash("sha256").update(body.join("\n")).digest("hex").slice(0, 16))};`, + "", + "/** Writes `mod` field by field (codec.ts describes the bytes). */", + "export function encodeIrModule(w: IrWriter, mod: IrModule): void {", + " encIrModule(w, mod);", + "}", + "/** Reads a module encodeIrModule wrote. */", + "export function decodeIrModule(r: IrReader): IrModule {", + " return decIrModule(r);", + "}", + "/** Writes one function. */", + "export function encodeIrFunction(w: IrWriter, fn: IrFunction): void {", + " encIrFunction(w, fn);", + "}", + "/** Reads a function encodeIrFunction wrote. */", + "export function decodeIrFunction(r: IrReader): IrFunction {", + " return decIrFunction(r);", + "}", + "", + ...keyLists, + "", + ...body, +]; +const text = lines.join("\n"); +if (process.argv.includes("--check")) { + if (readFileSync(target, "utf8") !== text) + throw new Error("codec.generated.ts is stale; run node scripts/generate-ir-codec.mjs"); +} else writeFileSync(target, text);